=== Payment Forms for Stripe ===
Contributors: 404ops
Tags: stripe, payments, donations, payment form, checkout
Requires at least: 5.9
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.0.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Stripe payments and donations with unlimited forms, 0% added platform fees, and no nags. Cards, wallets, bank debits, BNPL and PayPal.

== Description ==

Payment Forms for Stripe builds Stripe-powered payment and donation forms without a paywall on the basics.

**Free, no account with us required:**

* Unlimited forms and unlimited submissions.
* **0% added platform fees.** 404ops never takes a cut of a payment. Your normal Stripe processing fees still apply; the optional fee-recovery control below can pass those Stripe fees to the payer when appropriate.
* Fixed amounts or a "name your price" custom amount, with a min/max you control.
* Built-in spam protection on the checkout endpoint — a honeypot, a minimum time-to-submit, and a per-IP rate limit — with no third-party CAPTCHA account required, and a filter to add one if you want it.
* Email notifications on every completed payment: one to you, one to the payer, both stating the amount, currency, reference and form. Sent from Stripe's verified webhook, so a payer who closes the tab without returning to your site is still recorded and still receipted.
* **Buy now, pay later, and wallets — free.** Card, Link, Cash App Pay, US bank account (ACH), SEPA Direct Debit, iDEAL, **Klarna**, **Affirm**, **Afterpay / Clearpay** and **PayPal**, each selectable per form. Your selections are a merchant allow-list: Stripe only displays methods that are also enabled and dynamically eligible for that Checkout Session. Every one is subject to Stripe's own rules — a method has to be enabled on your Stripe account, most settle only in particular currencies, Affirm and Afterpay have amount floors and ceilings, and Klarna, Affirm and Afterpay cannot be used on recurring or installment forms. The form editor checks all of that when you save and tells you exactly which combination will not work, rather than letting the checkout fail in front of a customer.
* Stripe Tax, if you have it turned on in your Stripe account — enable it per form on the form editor's Payment methods tab.
* **Fee recovery.** Ask the payer to cover the Stripe processing fee, per form, so you receive the full amount you asked for. The surcharge is grossed up correctly — adding "2.9% + 30c" to a $10.00 donation leaves you 2c short, because Stripe's percentage applies to the surcharge too; this adds $0.61 instead, so you net exactly $10.00. By default the payer sees an unticked checkbox with the exact total next to it and decides for themselves; you can set a form to always add the fee instead, which states it plainly on the form (check your local rules before doing that for card payments). Either way the fee is its own named line on Stripe's checkout page and on the receipt, and it is broken out in your Payments list and CSV export. Not available on redirect forms, or on Pro recurring/installment payment types.
* Stripe-hosted Checkout for every transaction — this plugin never renders a card field and card data never touches it or your server.
* Embedded, overlay (modal), or single-button redirect presentation, placed via shortcode (`[ops404_payment_form id="123"]`) or block.
* **File uploads.** Add a File field and payers can attach a receipt, a photo or a PDF to their payment. Only images, PDFs and plain text/CSV are accepted, checked by contents and not just by filename; every file is stored under a random, unguessable name in a directory closed to the web; and only a logged-in administrator can download one.
* Everything the payer typed, kept on your own site: custom field answers are stored with the payment, previewed in the Payments list, shown in full on each payment's detail view, and included in the CSV export. No more taking an order and having to dig through Stripe's dashboard to find out what size to post.
* An on-page confirmation when Checkout returns the payer to your site: what they paid and a reference they can quote to you, in place of the form so nobody pays twice — or a plain "you have not been charged" if they cancelled or the payment was declined.
* **Your own thank-you, per form.** Keep the built-in confirmation, replace its wording with your own message, or send the payer to a page on your site instead — set under "After payment" on the form editor's General tab. A custom message replaces only the wording: the amount and the reference stay, because they are what stops somebody paying twice. A redirect has to point at a page on your own site (Stripe sends the payer there with the payment reference attached, so an off-site address would hand that reference to somebody else, and is refused when you save). Put the form's shortcode on that page and it shows the amount-and-reference confirmation there too.
* Automatic Stripe receipt emails to the payer, toggleable under Settings → Emails.
* An Overview dashboard, a Payments history screen, and Revenue / Form performance reports, each with CSV export and a test/live mode filter so test transactions never inflate your reported revenue.
* A **free** AI form builder: describe the form you want in plain English, using your own OpenAI, Anthropic, Gemini, or OpenRouter API key — or a local, no-cost Ollama model, or any other OpenAI-compatible endpoint you run yourself. It only ever proposes a draft — nothing is created on your site until you review and approve it. Your key (where one is needed at all) is encrypted at rest and never leaves your server except to the provider you chose. Connected once on the shared AI Connection screen, the same connection is available to every other 404ops plugin you have active.
* Your own Stripe API keys (test and live), entered directly in Settings — no third-party OAuth relay, no 404ops-hosted middleman.

**Pro** (from $49/year for one site, with 5-site and unlimited tiers, renewing at the price you paid — https://404ops.com/plugins/404ops-payment-forms; enter the key under Settings → License): recurring subscriptions, fixed-count installment plans, ad-hoc Stripe invoices, discount coupons (a coupon on a recurring or installment form discounts every renewal, not just the first payment), an abandoned-checkout follow-up list, and Subscriptions/Coupons reports (the Subscriptions report shows your first 100 subscriptions, disclosed on the screen itself).

**No nags.** This plugin registers no marketing admin notices, shows no upsell banners and has no "upgrade now" popup. The only place Pro is sold is the Settings → License tab. It is *named* in two other places, both factual rather than promotional: a one-line note under the form editor's Payment type menu tells you that recurring and installment payments need a Pro licence, and a merchant whose licence has lapsed is told — per form and site-wide — exactly what that means for the forms they already built. The menu itself does not list the Pro payment types as greyed-out options you cannot pick; without a licence the free plugin does not ship the code behind them at all, so there is nothing there to unlock. That lapse warning is the only admin notice this plugin can ever raise, and it is a service heads-up to an existing customer, not a sales pitch.

== External services ==

This plugin relies on third-party services. Nothing below is hidden behind an "optional" label it does not deserve: Stripe is contacted because that is how the plugin takes a payment at all, and an AI provider is contacted only after you have chosen one and saved a key for it.

Nothing is ever sent to 404ops. The free plugin contacts no 404ops-owned server, reports no usage statistics, loads no scripts, styles or fonts from any remote host, and phones home nowhere.

= Stripe — required =

Stripe is the payment processor this plugin is built on. Card entry happens on Stripe's own hosted Checkout page, so card numbers never reach your site or this plugin.

Requests go from your server to `https://api.stripe.com`, authenticated with the Stripe API keys you enter under Settings → Stripe. They happen at three moments:

* When you save a Stripe secret key, the plugin reads your account back once to confirm the key works.
* When a visitor submits one of your payment forms, the plugin creates a Checkout Session and redirects them to Stripe to pay.
* When Stripe posts its signed webhook back to your site to confirm, fail or follow up on a payment, the plugin may read the related object back from Stripe.

What is sent when a Checkout Session is created: the amount and currency, your form's name as the line-item description, the processing-fee line item if you have fee recovery switched on, your per-form payment-method allow-list (Stripe then displays only methods that are also enabled and dynamically eligible), whether Stripe Tax is on, the success and cancel URLs on your own site, the payer's email address (Stripe uses it to prefill Checkout and, for live one-time payments, to send its own receipt), and — as Checkout Session metadata — the answers the payer typed into that form's custom fields. The request also includes a fixed integration identifier for Stripe Workbench grouping; it is not a password, API key, or customer data.

Stripe is a service of Stripe, Inc. — https://stripe.com/
Terms of service: https://stripe.com/legal/ssa
Privacy policy: https://stripe.com/privacy

= AI providers — optional, and silent until you connect one =

The AI form builder makes no request to anybody until you pick a provider and save a key for it on the AI Connection screen. When you then ask it to draft a form, what leaves your server is your own typed description plus a fixed instruction describing the form structure the model must return. No payment record, no customer data, no Stripe key and no site content is included. The reply comes back as a draft for you to review; nothing is created on your site until you approve it.

Only the one provider you connected is contacted:

* **OpenAI** — `https://api.openai.com` — https://openai.com/ · terms https://openai.com/policies/terms-of-use/ · privacy https://openai.com/policies/privacy-policy/
* **Anthropic (Claude)** — `https://api.anthropic.com` — https://www.anthropic.com/ · terms https://www.anthropic.com/legal/commercial-terms · privacy https://www.anthropic.com/legal/privacy
* **Google Gemini** — `https://generativelanguage.googleapis.com` — https://ai.google.dev/ · terms https://ai.google.dev/gemini-api/terms · privacy https://policies.google.com/privacy
* **OpenRouter** — `https://openrouter.ai` — https://openrouter.ai/ · terms https://openrouter.ai/terms · privacy https://openrouter.ai/privacy
* **Ollama, or any other OpenAI-compatible endpoint** — whatever address you configure, which is normally a server you run yourself. No third party is involved and no API key is required. If you point it at somebody else's hosted endpoint, their terms and privacy policy are the ones that apply.

Your API key is sent only to the provider it belongs to, always in a request header and never in a URL, and is stored encrypted in your own database.

== Installation ==

1. Upload the plugin files to `/wp-content/plugins/payment-forms-for-stripe`, or install through the WordPress plugins screen directly.
2. Activate the plugin through the "Plugins" screen in WordPress.
3. A short setup wizard opens on first activation: enter your Stripe API keys (test or live mode) and set your currency.
4. Open **404ops → Payment Forms** in the admin menu. Every 404ops plugin on the site shares that one "404ops" menu, so this plugin no longer takes a sidebar slot of its own.
5. The plugin's other screens — Forms, Payments, Reports and Settings — are reached from the navigation row across the top of every one of its pages, not from the sidebar. Create a form under Forms, then place its shortcode or block on any page or post.

== Frequently Asked Questions ==

= Does this plugin make external requests? =

The free plugin can contact two service categories. Stripe is contacted when you verify keys, start a Checkout Session, or follow up on an event from Stripe's signed webhook. An AI provider is contacted only after you connect one and ask it to draft a form — using a key you supply, or no key at all for a local Ollama model or your own OpenAI-compatible endpoint. The free plugin makes no other external request and never contacts 404ops.

The **External services** section above lists every hostname, exactly what is sent to each, when, and each service's terms and privacy policy.

= Where is my data stored? =

Forms, payment records (including the custom field answers submitted with each payment), and (only if you use the AI builder) a log of your prompts and drafts are stored in three tables this plugin creates in your own WordPress database. Your Stripe secret key, webhook signing secret, and AI API key are stored in the WordPress options table encrypted at rest, and are never logged or included in the data export. This plugin never sees or stores card numbers — all card entry happens on Stripe's own hosted Checkout page.

= Is the file upload field safe? =

It is the part of this plugin that was designed most defensively, because an endpoint that lets anonymous visitors write files to your server is the most dangerous thing a payment form can have.

* **An allow-list, never a block-list.** Only `jpg`, `jpeg`, `png`, `gif`, `webp`, `pdf`, `txt` and `csv` are accepted. A filter (`ops404_payment_forms_upload_extensions`) can take types AWAY from that list but can never add one — widening it is a code change, on purpose.
* **Contents are checked, not just the filename.** A PHP script renamed `receipt.jpg`, a double extension like `evil.php.jpg`, or a file that is a valid image and a valid script at the same time are all refused. So is SVG, in every form: it is an executable document that happens to be an image, and this plugin does not accept one until it can sanitise one properly.
* **The stored filename is random**, 32 characters from a cryptographic random source, and carries exactly one extension — the validated one. Nothing a visitor types becomes a filename or a path.
* **The directory is closed to the web** with `.htaccess` (Apache 2.2 and 2.4), `web.config` (IIS) and an `index.php`, rewritten every time so they cannot go stale.
* **Uploads are rate-limited per IP**, more tightly than checkouts, so nobody can fill your disk.
* **Only an administrator can read a file back**, through a nonce-protected link, and it is always sent as a download — never rendered in your admin page.
* **Files that were never paid for are deleted automatically.** One becomes eligible two hours after it was uploaded (`ops404_payment_forms_upload_pending_ttl` changes that), and is removed by a sweep that runs once a day — so an abandoned upload lives at most until the next day's sweep, not two hours to the minute. A file attached to a real payment is deleted when that payment's data is (a data-erasure request, your retention setting, or uninstalling with "Remove all plugin data" turned on).

The maximum size is 5 MB, or your server's own upload limit if that is lower. `ops404_payment_forms_upload_max_bytes` changes it.

= What exactly is stored from a form submission, and how do I honour a data request? =

Only the custom fields you chose to collect (including any files attached to them), plus the payer's email address. Each stored answer keeps a snapshot of the field's label and type as it was at the time, so a payment stays readable years later even if you rename or delete the field — or the whole form. Nothing else about the visitor is recorded: no IP address, no browser, and never any card details (Stripe never gives those to this plugin).

Because those answers can be personal data, this plugin registers with WordPress's own privacy tools under **Tools → Export Personal Data** and **Tools → Erase Personal Data**:

* **Export** returns every payment for that email address, including every answer submitted with it.
* **Erase** removes the email address, every submitted answer, and every uploaded file, including from checkouts that were started and abandoned. The payment's amount, date, status and Stripe reference are deliberately kept — that is your financial record, you are generally required to keep it, and once the address and answers are gone it identifies nobody. WordPress reports this back to you as a retained item with an explanation, rather than claiming a complete deletion it did not perform.

If you would rather nothing at all was kept past a certain age, "Auto-purge payment records" under Settings → Advanced deletes whole payment rows on a schedule, answers included.

= Does uninstalling the plugin delete my data? =

Not by default. Deactivating or deleting the plugin always leaves your forms, payment history, and settings in place. If you want a clean removal, turn on "Remove all plugin data" under Settings → Advanced before you delete the plugin — that removes its tables, options, and scheduled tasks.

= How does the spam protection work, and can it block real customers? =

Three checks run before a submission is allowed to create a Stripe Checkout Session: a hidden honeypot field no human ever sees, a minimum time between the form being rendered and being submitted (2 seconds by default), and a per-IP rate limit (15 submissions per 5 minutes by default). All three are tuned to let a borderline case pay rather than refuse it — one-button "redirect" forms skip the timing check entirely, a submission that carries none of these fields is treated as legitimate, and a rate-limited visitor gets a "please wait a few minutes" message rather than a dead end.

Nothing is blocked silently. Anything refused is counted on the Overview screen's health panel with a breakdown of which check fired, so if you are expecting payments that are not arriving, that panel tells you straight away.

= Can I add my own CAPTCHA, or change the spam settings? =

Yes, with filters — no setting is buried in a UI you have to hunt through:

`ops404_payment_forms_spam_check` — your own check. Return a `WP_Error` (its message is shown to the payer) or `true` to refuse, or leave the value alone to allow. This is where a Turnstile, reCAPTCHA, hCaptcha or Akismet integration goes; the plugin deliberately ships no third-party CAPTCHA dependency of its own.

`ops404_payment_forms_min_submit_seconds` — the minimum time-to-submit, in seconds. Return `0` to switch the check off.

`ops404_payment_forms_rate_limit` — return `array( 'limit' => 15, 'window' => 300 )` with your own numbers. A `limit` of `0` switches the limiter off.

`ops404_payment_forms_client_ip` — **important if your site is behind Cloudflare, a load balancer, or any other reverse proxy.** By default the rate limiter uses `REMOTE_ADDR`, which in that setup is the proxy rather than the visitor, so every visitor shares one allowance. Point this filter at the header your proxy sets (only ever a header you know your proxy overwrites — trusting a forgeable one would let anyone bypass the limit).

`ops404_payment_forms_submission_blocked` — an action fired on every refusal, with the reason and the client IP, if you want to log or alert on them.

= Who gets emailed when someone pays? =

Two emails go out when Stripe confirms a payment, both on by default and both switchable on Settings → Emails:

* **You** get a notification stating the amount, currency, reference, form and payer address, with a link to the payment in your Payments list. It goes to your site's admin address unless you set another one.
* **The payer** gets a receipt from your site stating the amount, currency, reference and what they paid for.

The payer receipt is not a duplicate of Stripe's. Stripe only emails a receipt for **live-mode one-time** charges — never in test mode, and never for a subscription or installment payment. This one covers those. Test-mode emails are clearly marked as test payments so you cannot mistake one for a real sale.

Both are sent when Stripe's signed webhook confirms the payment, never when the payer's browser returns to your site — a payer can close the tab on Stripe's page and never come back, and a return URL is just text anyone can type. If a payment completed, you are told; if it did not, nobody is emailed a receipt for it.

If your site cannot send email at all — a common WordPress state — the Overview screen raises a warning rather than letting the notifications fail silently. An SMTP plugin is the usual fix.

= Can I change the wording of those emails, or where they go? =

Yes:

`ops404_payment_forms_email_template` — the raw template before token substitution. Keep the `{{tokens}}` (`{{site_name}}`, `{{form_name}}`, `{{amount}}`, `{{currency}}`, `{{reference}}`, `{{payer_email}}`, `{{mode}}`, `{{mode_notice}}`, `{{paid_at}}`, `{{admin_url}}`) and they will still be filled in. Use this to reword or translate.

`ops404_payment_forms_email_subject` / `ops404_payment_forms_email_body` — the finished text, after substitution. Use these to append a signature or return HTML.

`ops404_payment_forms_email_headers` — headers for either email. Add `Content-Type: text/html; charset=UTF-8` here if your body filter returns HTML.

`ops404_payment_forms_admin_email_recipients` — who gets the admin notification. Return extra addresses, or an empty array to suppress it for a given payment.

`ops404_payment_forms_email_sent` — an action fired after each send, with `wp_mail()`'s result.

`ops404_payment_forms_payment_succeeded` — an action fired once per payment at the moment it is recorded as succeeded, with the `Payment` object. This is the hook to use for anything else that should happen on a real payment; it is not reachable from the payer's browser return, so anything on it can trust the payment is genuine.

`ops404_payment_forms_checkout_failed` — an action fired with the form id and the reason when Stripe refuses to create a Checkout Session. The payer is told only that the payment could not be started — Stripe's own message describes YOUR account's configuration and is never repeated back to a stranger — so this is where to hook an alert if you want to know about it straight away. The same reason is written to your PHP error log when `WP_DEBUG` is on.

= Do I need a 404ops account to use this? =

No. Every free-tier feature works with only your own Stripe account and, if you choose to use the AI builder, your own AI provider connection (a paid provider's API key, or a local model that needs no key at all).

== Screenshots ==

1. Every payment form you have built, with its mode and status.
2. Payments as they arrive: amount, form, Stripe mode and outcome.
3. The overview: revenue over time, and which form is earning it.
4. Configure fixed or pay-what-you-like amounts and currency.

== Changelog ==

= 1.0.4 =
* Updates the Pro licensing client to select updates for this plugin when several 404ops plugins share a site.

= 1.0.3 =
* Improved: each form's selected payment methods now filter Stripe's dynamically eligible methods instead of forcing a static list, so Dashboard-disabled or transaction-ineligible methods are not offered.
* New (Pro): a quiet, dismissible admin notice now appears once this add-on's licence hasn't revalidated successfully in a few days, before Pro features would otherwise silently lapse with no warning. Updated the shared licence client.
* Fix (Pro): a licence's cached "valid" state is now bounded to 14 days since its last successful check, so a site whose WP-Cron never runs no longer keeps a revoked or expired licence reading active indefinitely.

= 1.0.2 =
* Fix (Pro): licence activation and update checks used the wrong authentication scheme and an incorrect product filter against the licence server, which could reject valid requests. Updated the shared licence client.

= 1.0.1 =
* Fix (Pro): first-time licence activation could fail against the real licence server on a brand-new key (updated the shared licence client).

= 1.0.0 =
* Initial release.

== Upgrade Notice ==

= 1.0.3 =
Pro now shows an early warning notice before licence-related features would lapse, and no longer keeps a revoked or expired licence active forever on a site whose WP-Cron never runs. Pro users should update.

= 1.0.2 =
Fixes Pro licence activation and update checks against the real licence server. Pro users should update.

= 1.0.1 =
Fixes first-time Pro licence activation. Pro users on a licence that has never activated before should update.

= 1.0.0 =
Initial release.
