QR Coupon Journeys: payment methods

Status: design proposal, not an implemented or enabled payment feature. Part of QR Coupon Journeys, using the reusable Essentials journey and phone card design. Official provider documentation was checked on 18 September 2026. API field names and wallet eligibility must be tested against the account's selected stable Stripe API version before release.

The promise

COURSES3M activates a time-limited coupon entitlement with zero due today. Many items in the catalogue remain free. Collecting payment details prepares the account for optional paid items, each of which requires its own purchase confirmation. A separately offered extra feature lets the person choose to continue on Personal automatically when the coupon benefit ends.

The renewal checkbox starts unchecked. Declining it does not remove the coupon's free benefit or require a later cancellation. A mockup showing it checked illustrates a person who opted in; it is not the initial state. Saving a method and authorizing a subscription are different consents. The default branch creates no Stripe subscription or invoice. Only the explicitly selected renewal branch creates a trial subscription, which can have a zero-value initial invoice.

The existing grant remains personal, tierDays: 90, with expiry clamped by the coupon's own validity window. Render the actual expiry from the immutable server quote. Do not turn 90 days into three calendar months or promise a full 90 days when the coupon cap shortens it. A campaign that guarantees a full new three-month term needs its own approved policy. A pre-existing longer benefit must not be cut short or charged over by this offer; resolve that eligibility before quoting.

Use this copy beside the selection, before any wallet or card authorization:

0 today. Your card will not be charged to activate this offer. Your included Personal access ends on . There is lots of free content to explore. Save a payment method for optional paid items; you will see the price and confirm each separate purchase before you pay.

Storage consent is a separate, initially unchecked control:

Save my payment method securely with Stripe for purchases I choose and confirm in MeshWeaver.

The optional feature has its own equally visible control:

☐ Continue with Personal after my included access ends: per month, starting {first renewal date and time, viewer time zone}. I authorize automatic monthly charges until I cancel. Cancel in Account → Billing before to avoid the first charge. After billing begins, cancellation stops the next renewal and access continues through the paid period.

Resolve the actual price, currency, tax treatment, cadence, first charge date and cancellation cutoff before displaying this text. For v1, use the approved fixed monthly Personal price with an exact tax-inclusive quoted total; if the total cannot be established, do not enable renewal. Never use the catalogue-purchase explanation to conceal the separate recurring commitment.

Unchecked, the final action is Save payment method & activate free access, with No automatic renewal beside it. Checked, it is Activate free access & schedule Personal renewal, with 0 today · then /month from beside it. Use official wallet branding; only the opted-in branch can use subscription-specific language. The receipt distinguishes Renewal off from Renewal scheduled, and repeats the complete accepted terms and management link. No charge today must not become a blanket promise of no future charges after renewal consent.

An adjacent, short help note explains that a bank may display a temporary verification reservation. Stripe documents a small or zero-value authorization when validating saved cards; this can appear as pending and then disappear. Therefore promise no activation payment, not that a bank will never show an entry. Do not invent a release deadline for the reservation. Stripe: temporary card verification authorizations.

This design requests upfront method setup for this campaign, as requested. State that requirement before account creation and allow the person to leave without charge. A declined save consent must not be treated as acceptance. Whether to allow activation without saving is a separately reviewed campaign policy, not a hidden fallback. Wallet restrictions below mean that a wallet choice is not always a reusable method: never enable renewal merely to make Apple Pay available.

A real three-way choice

The product presents Apple Pay, Google Pay, and Card via Stripe. Stripe processes all three; it is not a third wallet. Show both wallets when the environment supports both, one when only one is available for the selected purpose, and card entry when neither is available. Explain unavailable options without presenting a working-looking button that opens a generic chooser.

Use a reusable framework payment-method control backed by Stripe's Express Checkout Element (ECE) for the official wallet buttons and Payment Element for card entry. ECE explicitly supports mode: setup with a SetupIntent; amount is required for payment/subscription modes, not setup. Wallet display depends on supported browser, country, currency, account configuration and customer setup, so capabilities must come from the SDK rather than an iPhone/Android guess. Keep the wallet sheet invocation in the user's gesture. Test the selected stable SDK's supported availability event before pinning it in the control. ECE integration and setup mode, ECE eligibility and browser support.

Combining the two Elements avoids duplicate wallet controls: wallets appear in ECE, card fields in the Payment Element. Configure this campaign's supported methods deliberately; do not accidentally introduce buy-now-pay-later or bank debit mandates by inheriting every newly enabled account method. Use the provider's controls rather than collecting PAN, CVC or wallet tokens in MeshWeaver forms. Combining Payment Element and Express Checkout.

Wallet reuse is purpose-specific

Apple Pay: Stripe documents that saved Apple Pay methods cannot be reused for on_session purchases: a customer-present purchase must open Apple Pay again and obtain fresh authorization. Apple Pay does support off-session recurring use after an appropriate initial setup. Therefore the unchecked renewal branch must not promise “save Apple Pay once for later in-app purchases”. If upfront reusable storage is mandatory, show that limitation and offer Card via Stripe or a verified reusable alternative. Apple Pay can still be used afresh for future purchases. A wallet-preference-only activation path would change the required-setup policy and must be approved as a separate feature, not quietly substituted. Stripe Apple Pay saved-method restrictions.

For opted-in Apple Pay renewal, configure the supported recurring request with the accepted amount, start date, interval and management URL. Request merchant tokens (MPANs); the issuer can return a device token instead, so handle token expiry or replacement. Checkout requests merchant tokens when needed; direct Elements integration needs its use-case configuration. Test the actual selected integration instead of assuming any Apple Pay setup creates a durable recurring token. Stripe merchant-token integration.

Google Pay: Stripe advertises recurring support, but a rendered button or a one-time wallet token is not evidence that this account's setup path supports future merchant-initiated charges. Verify setup success, attachment and the intended reuse with the chosen SDK, account, currency and token type. If recurring setup is unavailable, retain a genuine wallet choice for supported purchases and offer card setup for renewal; do not invent a reusable mandate. Stripe Google Pay support. Google's MIT API has dedicated recurring terms and token-lifecycle requirements. Their existence does not establish that a particular Stripe adapter exposes that feature. Do not add a parallel direct Google token-processing service to route around a missing Stripe capability. Google Pay merchant-initiated transactions.

Hosted fallback: the existing provider already creates hosted Checkout sessions. Add an honest Continue securely with Stripe fallback if the embedded control cannot load. Checkout mode=setup stores details using a SetupIntent and supports wallets when eligible. The hosted page chooses which methods it can show; three portal buttons must not all redirect there while claiming to have selected a particular wallet. A fully hosted fallback is useful, but does not satisfy the primary three-choice experience by itself. Stripe hosted card setup and wallets. The fallback preserves the branch and consent snapshot. With renewal off it is setup-only; with renewal on it may be one trial subscription Checkout with the accepted trial_end and required method collection. Never run hosted subscription creation after another path already created that same subscription. The primary three-way control and hosted fallback share one attempt owner.

Ownership and additive contracts

The current core IPaymentProvider.CreateCheckout(PaymentCheckoutRequest) only models an amount and optional recurrence, returning a hosted URL. StripeGateway currently creates payment and subscription sessions. Do not overload a zero amount or a null recurrence to mean “save only”. That would make a setup completion look like a paid order to today's fulfilment logic.

Implement the provider-neutral extension in core first, then consume it in the Stripe module and Store. All names below are proposed contracts, not existing APIs:

Contract Responsibility
IPaymentMethodSetupProvider Optional capability; creates/retrieves setup sessions, describes supported UI, and maps verified setup deliveries. Store remains compilable without Stripe installed.
PaymentMethodSetupRequest Server-owned buyer/reference, origin and return path, currency, intended use CustomerPresentPurchaseOnly or AuthorizedPersonalRenewal, and distinct consent references. Setup itself has no charge amount.
PaymentMethodSetupSession Provider setup reference and discriminated hosted/client-rendered handoff. Client-rendered material is buyer-only, short-lived and never persisted in ordinary node content.
PaymentMethodSetupFact Server-verified setup status, provider customer and method references, display-safe summary and account/mode scope. It is not a payment or entitlement.
PaymentMethodSetupControl Framework control with provider adapter; manages loading, available methods, save confirmation, challenge, failure, cancellation and accessible status.
CouponFeatureSelection Immutable optional feature IDs, checkbox choices and accepted quote hash, stored separately from storage consent. OptionalSubscriptionRenewal is off unless explicitly accepted.
DeferredSubscriptionRequest Provider-neutral renewal capability: exact accepted first charge instant, tier, currency, cadence, tax-inclusive recurring total, zero due now, accepted mandate, method/customer and idempotent claim reference.
DeferredSubscriptionFact Verified provider subscription identity, trial end, recurring terms, initial zero-due invoice, setup authorization and cancellation status. A zero invoice is not a purchased billing period.

Retain the existing IPaymentProvider surface so installations and compiled content using it do not break. The Stripe adapter registers the optional setup capability beside it. Store uses the neutral service and registered typed action nodes. C# flows stay cold IObservable<T> end to end; HTTP leaves run through IIoPool. Browser SDK promises stay inside the existing JavaScript/Blazor interop boundary, not in hub-facing C# APIs. The framework control owns Element mounting, disposal, locale updates and return handling; individual coupon pages do not duplicate them.

Quoted branches and activation sequence

  1. Authenticate, resolve the journey and campaign, and validate the intended buyer and target site. Read eligibility, existing paid subscriptions and the actual benefit window on the server. Freeze the prefilled offer, benefit dates, selected optional features and applicable billing terms in an expiring quote. At acceptance, store the quote hash and both consent decisions. Reprice or date changes require fresh confirmation; do not shift the free end date after a wallet round trip. A URL cannot provide a trusted customer ID, renewal choice or paid status.
  2. On explicit consent, create a durable claim/setup attempt with a deterministic idempotency key. Resolve or create a Stripe Customer scoped to provider account, test/live mode and the site's billing owner. Reuse the existing plan-checkout claim discipline so a second tab or ordinary plan checkout cannot create another subscription. Reserve coupon capacity through the existing control-plane mechanism; do not consume a second redemption on retries.
  3. Renewal unchecked: create a SetupIntent with usage=on_session for a method that supports that use, with no subscription request. Renewal checked: create a SetupIntent with usage=off_session and the separately accepted recurring mandate, then mount the purpose-appropriate wallet/card controls. If a setup already exists for the other purpose, changing the checkbox invalidates that attempt and requires the corresponding authorization; a browser edit cannot upgrade a saved on-session method's consent. Stripe SetupIntent usage and customer attachment.
  4. Confirm setup through Stripe.js, including any required bank challenge. Saving and later redisplaying a method need the stated consent; retain its version and timestamp. Use stripe.confirmSetup for the direct SetupIntent path, not confirmPayment. Honour provider redisplay consent when offering the method in subsequent checkout. Stripe save-method consent and setup confirmation.
  5. Await a verified setup fact and matching quote, revalidate the coupon, and redeem through the existing coupon control plane exactly once. Setup success is a prerequisite, not itself an entitlement. Keep the redemption ID and activation outcome so a retry repairs incomplete activation rather than spending the coupon again. Reconcile the method-save and coupon-grant operations as a saga; they are not an atomic cross-system transaction.
  6. With renewal off, finish after verified grant activation. With renewal on, create/verify the single deferred subscription described below, using the same immutable claim and mandate. Do not mark renewal scheduled until its provider facts match. A grant followed by subscription failure remains a valid free grant with Renewal not enabled and a resumable repair; never charge early to repair the failure. The opted-in step is not another coupon redemption.
  7. If the coupon expires or exhausts before grant activation, report Method saved; offer could not be activated with recovery and remove-method controls. Do not create a subscription in that outcome. If a hosted alternative created one before the grant, compensation must cancel its future billing and verify that cancellation; unresolved compensation is an operator alarm. Never substitute an immediate paid purchase. A pending operation stays visibly pending.

The hosted setup adapter must preserve this same consent and intended-use contract. Prove the chosen Checkout API version can pass the required setup policy, or use the direct SetupIntent path; do not weaken it silently. API versions can use different ui_mode names, so record and test the exact version/field pair rather than mixing examples from different versions.

Optional Personal renewal with zero due today

The proposed default implementation is setup first, coupon grant second, one trial subscription third. It keeps genuine in-page wallet choices, validates permission for recurring use before creating a subscription, and uses one extended claim/subscription control plane. These are serialized phases of one canonical subscription operation, not independent manual activation and paid-checkout fulfilment. The provider scheduling evidence references that grant and binds its provider handle through the same owner; it is not a second subscription model or a paid activation. The transition preserves the frozen coupon expiry and never applies today's paid-month defaults.

Create the subscription with the server-approved fixed Personal price and trial_end equal to the accepted benefit end. The typed feature is the only route permitted to transition this claim's manual coupon grant into a future recurring plan. An unrelated existing paid or scheduled plan is an eligibility refusal, not permission to duplicate, replace or cancel it. Missing or near-expired trial dates, unsupported provider timing, and changed quotes return for confirmation; never omit trial_end as a fallback.

Stripe's established trial parameters support a future exact end date. Hosted Checkout collects a payment method by default; retain required collection in the opted-in hosted alternative. Stripe's newer Trial Offers API was still preview-only and excluded Checkout when checked; this design does not depend on that preview. Select and test a supported stable version explicitly. Checkout free trials, Stripe Trial Offers scope and restrictions.

Prove zero due before creation and again from returned provider facts. Quote the actual recurring item, tax-inclusive price and first billing date from trusted configuration. Preview the intended initial and first paid invoices where supported. Refuse setup fees, one-time invoice items, prorations, unrelated pending items, metered amounts, residual debt, taxes or account configuration that would make the initial collection positive or change the accepted renewal total. Do not combine separate catalogue purchases into activation. A coupon-created zero price is not the same thing as a trial on the future approved price. Stripe invoice previews.

A trial subscription normally creates an immediate zero-value invoice. One-time invoice items can still charge during that trial, which is why they are forbidden in this flow. Verify subscription/customer/account identity, trialing status, exact end, fixed recurring terms, initial invoice amount due zero, and no captured activation payment. A mismatched response is a failed scheduling attempt requiring cancellation and investigation, not a success to explain away. The deterministic request must prevent charges; refunding an accidental charge is not how the zero-today guarantee is implemented. Stripe trial invoice behavior.

Use one idempotency key for the subscription-creation step and persist its returned ID before reporting success. Retry by retrieving that subscription, not creating another after a timeout. After the provider's idempotency retention ends, reconcile the durable claim and provider facts before any new creation. Stamp claim, buyer, feature/quote hash and mandate reference on the subscription and the relevant setup/Checkout metadata. Only the accepted claim can own it.

If Stripe reports a pending_setup_intent, complete its required authentication and verify the result before declaring the payment arrangement ready. A null pending intent is not proof of the user's terms consent; retain that independently. Cancellation cleanup must include unfinished setup intents, which Stripe does not automatically cancel with the subscription. Subscriptions without initial payment.

The renewal mandate authorizes the scheduled monthly subscription invoices without asking the buyer to click Pay every month. It authorizes no other paid catalogue item. Send a confirmation and a trial-ending reminder with the exact price, date and cancellation link, using the product's transactional notification lane. Show Turn off renewal immediately in Account → Billing; confirm the provider's cancellation while preserving the original free grant until its expiry. After paid renewal, cancellation stops the next charge and preserves the paid period. Do not copy today's subscription-deleted handler blindly if it would revoke the separate free grant. Stripe trial messaging and cancellation requirements.

First paid renewal is fulfilled from a verified invoice/subscription event, using the accepted terms and canonical subscription control plane. A zero trial invoice cannot extend access by a paid month. Handle payment failure and action-required status with a recovery link and explicit access policy; do not declare payment success because a subscription exists. Cancel/missing-method end behavior, webhook order, renewals and local expiry must be tested together. Stripe subscription lifecycle events.

Optional purchases after activation

An in-app purchase is a new Store order with a server-priced item, quantity, currency, taxes and final total. Show the total and an explicit Pay action for that purchase. A saved method accelerates entry; storage consent alone grants no permission for background billing, coupon-expiry conversion, automatic top-ups or an agent-triggered charge. Only the separate renewal mandate authorizes that specified Personal subscription. Every other paid item uses a new purchase confirmation, including for a person who opted into Personal renewal. Reuse the normal provider payment path and verified fulfilment after a fresh buyer confirmation. Handle new SCA when required and never claim saved setup guarantees future authorization.

Offer settings to inspect the masked method, replace it, remove it and revoke future redisplay consent. Removal does not terminate already granted free access. When a method supports an active renewal, offer replacement or explicit renewal cancellation and report the resulting billing state; removing a displayed card must not misleadingly claim a subscription was cancelled. A user can buy later with a new method if an old wallet token is unavailable. Do not expose raw provider credentials or claim that the method is portable across unrelated sites or Stripe accounts.

Domains, security and durable evidence

For Elements, register every production and test domain/subdomain displaying wallet buttons in Stripe's payment-method-domain configuration. HTTPS is required. Where embedding introduces a different origin, check both origins and the supported iframe permission rules. Stripe performs Apple merchant validation; do not introduce a separate Apple Merchant ID/certificate process for this Stripe web integration. Hosted Checkout has different domain requirements and does not require the portal to register a domain merely for linking to Stripe's page. Stripe domain registration, Stripe Apple Pay hosted versus embedded requirements.

Extend the existing signed webhook inbox and payment-path audit for setup_intent.succeeded, setup failure/cancellation, and hosted checkout.session.completed with mode=setup. Every success must match the setup reference, buyer, Customer, account, test/live mode, and campaign attempt. Retrieve provider state when necessary. A browser return parameter is status-display input, never proof of saved credentials, paid money or coupon ownership.

For opted-in renewal, audit subscription creation/update/deletion, trial-ending, invoice.paid, invoice.payment_failed and invoice.payment_action_required deliveries as well. Compare the accepted feature and quote hash before attaching billing to the coupon recipient. Use distinct states for Method saved, Coupon granted, Renewal scheduled and Paid renewal; none is an alias for another. An old setup event arriving after cancellation cannot re-enable renewal. Snapshot cancellations and consent withdrawals in the same durable saga.

Persist provider event IDs and terminal attempt outcomes. Test duplicates, reordering, delayed events and a crash between setup success and redemption. Signature failures remain visible to operators; a replay cannot activate a second grant. Do not route a setup-mode Checkout completion through today's paid-order handler. Sanitize provider errors and exclude client secrets from logs, analytics, QR URLs, local storage, manifests, caches and normal mesh content. Setup client secrets must only reach the intended buyer over TLS. Stripe SetupIntent client-secret handling.

The GUID identifies the shareable journey, not the Stripe Customer or payment method. Scan and login are reads/navigation; state changes require the authenticated activation action and anti-forgery protection. Rate-limit setup creation and scope return URLs to approved HTTPS origins. Security review of the new setup and buyer-authorization boundary is a release dependency.

Home-screen installation and store distinction

The phone icon opens the journey's sharing card in the installed web app. It is separate from Apple Pay/Google Pay and from adding a pass to Apple Wallet/Google Wallet. The latter is not required by this proposal.

On iPhone, the install button opens localized instructions for Safari's Share → Add to Home Screen flow, including Open as Web App where offered. It cannot silently install an icon. Apple's current iPhone instructions. On compatible Android Chromium browsers, invoke a captured beforeinstallprompt only following the person's tap. If unavailable, show the browser's supported manual steps; never report an installation merely because the button was clicked. Chrome team's installation-prompt guidance.

Use a stable manifest identity, explicit card start URL, standalone display, appropriate icons and an apple-touch-icon. Authenticate again when necessary after installation and resume the same journey. Do not rely on the browser tab and installed app sharing a live session. Cache a neutral shell only; a revoked or expired card must not appear active from an offline snapshot. iOS can install multiple identities for the same web app; do not use installation identity as buyer identity. WebKit home-screen identity and icons.

“In-app purchase” here means a purchase inside the browser/home-screen web app. Publishing a future native iOS/Android app introduces a separate distribution and billing-policy review. Apple Pay is not StoreKit In-App Purchase; Google Pay is not Google Play Billing. Digital course sales in native apps have region- and distribution-dependent rules, so this web implementation must not be claimed as native-store approval. Stripe Apple Pay and native digital content, Stripe Google Pay versus Play billing.

Release acceptance matrix

Scenario Required evidence
iPhone Safari with Apple Pay; Android Chrome with Google Pay Test the actual supported purpose: no unsupported saved Apple Pay on-session reuse; recurring wallet setup authorized and matched to the opted-in branch; zero activation payment and one grant.
Both wallets eligible for selected branch Both genuine buttons available; choice preserved. Card via Stripe remains reachable.
No wallet, unsupported browser/country, disabled method Clear availability state and functional card fallback; no invented eligibility based on operating system.
Embedded load failure / hosted fallback Honest wording; same branch/claim/consents; no duplicate subscription and no setup completion misclassified as paid checkout.
SCA succeeds, fails, is cancelled, or returns after login expiry No premature grant or payment; authenticated safe resume with visible status.
Double-tap, concurrent tabs, hosted fallback, ordinary plan checkout, webhook duplicates/out-of-order, process restart One grant and at most one scheduled subscription; durable repair after partial completion; no consent upgrade.
Customer/account/mode mismatch, forged return, invalid signature Refused and observable; no method or entitlement disclosed to another viewer.
Coupon revoked, expired or exhausted during setup No substitution charge; honest saved-method status and removal option.
Renewal unchecked; coupon reaches actual expiry No Stripe subscription/invoice/payment created by this feature; coupon access expires and free catalogue remains available.
Renewal checked; initial trial invoice Exactly zero due today, correct method and mandate; no one-time items, debt, tax surprise or paid-period grant from zero invoice.
Renewal checked; first and later monthly invoices Test clocks confirm accepted first date/amount/currency/cadence, one renewal charge per invoice and canonical access extension.
Price, tax total, grant window or selected feature changes during handoff Old quote rejected; fresh visible terms and consent required; no shortened trial or silent renewal selection.
Cancel before first renewal; cancel after a paid renewal Provider confirms no next charge; free grant or already paid period stays available as applicable.
Renewal fails or requires SCA; wallet token deactivated Clear recovery and access state; no false success, repeated subscription creation or unrelated charge.
Optional later purchase Final total + fresh explicit confirmation, correct buyer/method, SCA recovery and normal verified fulfilment.
Storage consent accepted; renewal declined Reusable method only for stated purpose; no recurring mandate inferred from storage.
Method removed or renewal checkbox changed Free grant retained; subscription cancellation/replacement is explicit and independently verified.
Install dismissed, already installed, iOS/Android standalone, offline/revoked card Truthful install state; safe authentication resume; expired/revoked offer never presented as active from cache.
English/German, screen reader, keyboard and narrow phone Readable no-charge promise and expiry, accessible errors, labelled card controls and wallet selection.

Use Stripe sandbox/test mode for automation, challenge cases and webhook replay. Real-device wallet validation is required: Apple Pay testing uses an eligible real wallet card with Stripe test keys (not a Stripe test-card number added to Apple Wallet); Google Pay has its own test-suite requirements. Use test clocks for opted-in trial conversion and separate real-device checks for wallet collection/token behavior; one is not evidence for the other. No user-facing live charge is necessary to prove zero-today setup. Verify production domains and authorized smoke paths for both branches; any opted-in smoke subscription must have a named owner, deliberate test consent and verified cancellation before its first bill. Stripe sandbox reminder emails are not delivered, so validate message content/routing separately rather than waiting for an email that cannot arrive. Stripe Apple Pay testing, Stripe Google Pay testing.

Delivery order: additive core contract and control → Stripe adapter and verified setup events → Store activation and optional-renewal saga with separate consents → genuine wallet/card choice → phone install/return validation → security review and full matrix → enable the campaign through the supported release pipeline. Payment-method tests are a release gate, not work deferred until after launch.

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.