AforoPaymentMethod
Display and manage the customer's stored payment methods. Lists methods, sets a default, removes a method, and surfaces a SetupIntent client secret as an event so the parent page wires its own Stripe / Razorpay / PayPal SDK to confirm new methods.
What it renders#
A card (or list) of the customer's stored payment methods with brand, last-4 digits, expiry, holder name, and a Default badge. In expanded mode, each non-default row carries a "Set default" button; each row except the last carries a "Remove" button. A primary CTA ("Update" in compact, "+ Add" in expanded) kicks off the Add/Update flow.
- List: fetched from
GET /api/v1/portal/embed/payment-methodsvia the bridge-token session JWT (the customer id is derived from the JWT, never from the request). - Default badge: blue pill with text alternative (color is supplementary per FR-WIDGET-X-2). Screen-reader announces "Default payment method".
- Set default: optimistic UI update + POST to
{id}/set-default. - Remove:
window.confirmgate (a destructive action requires confirmation) + DELETE call. The last remaining method cannot be removed — the customer must Add a replacement first. - Empty state: when the customer has no methods, a centered "No payment method on file" affordance + "+ Add method" CTA.
POST /payment-methods/setup-intent and emits an aforo.payment-method.update_requested event with the SetupIntent clientSecret. The parent page wires its existing Stripe Elements / Razorpay Drop-in / PayPal Smart Buttons to confirm. Inline provider iframes are deferred to Phase 1.Mount examples#
Props reference#
tenantSlug— required. Your Aforo tenant slug.embedKey— required. Embed key minted in the Embed Studio.bridgeToken— required. Short-lived customer-session bridge token your backend mints per page-view.mode—'compact'(default) shows only the default method;'expanded'shows the full list with per-row actions.theme—'light''dark''auto'(default, honorsprefers-color-scheme).themeOverrides— partial token overrides. See Theming.onUpdateRequested(clientSecret, provider)— callback fired alongside the postMessage event when the customer clicks "Add" or "Update".onError(err)— optional error callback.
Compact vs expanded mode#
Compact shows a single card with the customer's default method and a single "Update" CTA. Sized for a sidebar or modal. Use this when payment management is one of many sub-tasks on the page.
Expanded shows the full list with per-row "Set default" + "Remove" actions and a header-level "+ Add" CTA. Use this on a dedicated "Payment methods" page where managing multiple cards is the primary task.
The Add/Update flow#
Because every provider has different iframe + 3DS semantics, v1.0.0 keeps the SDK provider-agnostic. The flow is:
- Customer clicks "Update" (compact) or "+ Add" (expanded).
- Widget posts
POST /payment-methods/setup-intentwith an idempotency key. The backend mints a provider-side SetupIntent. - Widget emits
aforo.payment-method.update_requestedwith{ clientSecret, provider }. - Parent page wires its existing Stripe Elements / Razorpay Drop-in / PayPal Smart Buttons to confirm the intent. The parent runs 3DS / SCA challenges as needed.
- On successful confirmation, parent page posts
aforo:payment-method-attachedback to the widget. The widget refetches the list and emits an advisoryaforo.payment-method.updatedevent.
aforo.payment-method.updated event is advisory and meant for UI affordance only. The truth that the method capture succeeded comes from your provider's webhook reaching Aforo. Do not trigger downstream business actions (e.g. unblocking subscriptions, retrying invoices) from this client event.Events#
Outbound (widget → parent), all carry version: '1':
aforo.payment-method.ready— fires once on first successful list fetch.aforo.payment-method.update_requested— customer clicked Add/Update, SetupIntent created.aforo.payment-method.method_set_default— customer changed default.aforo.payment-method.method_removed— customer removed a method.aforo.payment-method.updated— advisory notification after a parent-confirmed Add (see warning above).aforo.payment-method.error— any read or mutation failure with a typed code.
Inbound (parent → widget):
aforo:payment-method-attached— parent confirmed a provider-side capture. Widget refetches the list.
Limitations (v1.0.0)#
- Provider iframes are not mounted inline. Add/Update emits an event; the parent page owns the provider SDK. Inline provider iframes land in Phase 1.
- Last-method protection. The expanded-mode Remove button is disabled when only one method remains. Customers must Add a replacement first. This is deliberate to avoid leaving the customer with no way to pay.
- No SCA / 3DS UI in the SDK. 3DS challenge UI is rendered by the parent's provider SDK on its own iframe / new window. The widget does not intercept the challenge.
- One-shot ownership check. Set-default and Remove validate ownership by re-listing the customer's methods. For tenants with very many methods, a dedicated
/ownerendpoint would be more efficient. v1.0.0 keeps the simpler shape.