Skip to main content
One idea at a time

Checkout starts a payment workflow

A workspace owner selects the paid plan. Taskboard asks Stripe for a hosted checkout session, then gives the browser the resulting URL. The browser opens Stripe's page to enter payment details.

Using hosted checkout keeps card details outside Taskboard's request handlers. Taskboard still owns important decisions: who can purchase for this workspace, which product is available, and how the resulting subscription maps back to the workspace.

The browser selects a plan, not a price​

The client submits a supported plan identifier. The server maps that identifier to configured Stripe price information. It must not accept an arbitrary amount or provider price ID as trusted billing input.

Illustrative request body
{ "plan": "team" }

The billing handler verifies current workspace membership and the billing role. CSRF protection still applies because this is an authenticated mutation. A workspace ID in the URL identifies the requested tenant; it does not prove the caller can purchase for it.

Taskboard attaches server-selected workspace metadata to the provider operation. A later webhook can use that relationship to locate the billing record, with additional provider identity checks. Provider references must not be accepted from an unrelated customer's event.

A redirect does not activate the plan​

Stripe can redirect the browser to a success page while Taskboard has not yet processed the subscription update. The browser can also close before returning. Plan activation therefore follows verified provider state rather than the presence of a success URL parameter.

Repeated checkout requests use local and provider idempotency rules. Taskboard stores a random provider key in the reservation and reuses it. An unresolved reservation older than twenty-three hours requires reconciliation instead of an automatic retry, and a replay of an expired checkout returns a conflict. Returning a session URL still proves only that checkout was created. It does not prove payment settlement.

Where amounts appear in application data, use integer minor units with an explicit currency. For example, 1200 with USD describes twelve dollars. Currency rules differ, so the amount alone is insufficient. Do not calculate money with binary floating-point expressions such as 0.1 + 0.2.

The Stripe adapter is in Read api/src/billing.ts. Automated provider doubles prove local behavior; live account acceptance requires configured Stripe credentials and provider-side verification.

Take away

The server controls billing authority and prices. Creating checkout begins the workflow; verified subscription state determines access.

Can the success page send a request to grant the paid plan?

It can ask for current billing status. It must not grant entitlement based on the redirect, because browser-controlled requests cannot establish a payment outcome.