For the complete documentation index, see llms.txt. This page is also available as Markdown.

Custom Checkout with Stripe Elements

Instructions on how to setup Sphere's API while using Stripe Elements in a custom checkout

To create a custom checkout session with taxes calculated using the Sphere API, follow these steps:

  1. Call the Sphere Tax Calculation API to retrieve applicable tax rates.

  2. Develop a custom payment form using Stripe Elements, either for one-time payments (Step 3a) or subscriptions (Step 3b), and link the Stripe tax rates obtained from the Sphere API calculations.

Custom Checkout with Stripe Elements

Stripe Elements are Stripe's pre-built, customisable UI components for collecting payment details. This guide shows how to add Sphere tax calculation to a custom checkout built on Elements, so the amount your customer pays includes the right tax and the resulting Stripe records carry Sphere's calculation.

There are two flows, depending on what you are selling:

You are charging for…
Stripe object
Where Sphere's tax goes
Section

A one-time purchase

PaymentIntent

The PaymentIntent amount

Step 2a

A subscription

Subscription (created via a Subscription Schedule)

tax_amounts on the first invoice's line items

Step 2b

A subscription sold through Stripe Checkout

Checkout Session, mode: 'subscription'

Stripe Tax Rate objects on line_items[].tax_rates, removed after completion

Step 2c

Both flows start with the same call to Sphere (Step 1) and end with the same Stripe Elements confirmation on the client.


Prerequisites

  1. A Sphere API key and the setup described in Tax Calculation: products with Product Tax Codes, and registrations for the regions you sell into.

  2. A Stripe integration on API version 2025-03-31.basil or later. Field names in this guide (confirmation_secret, lines[].pricing) follows the current Stripe API version, 2026-08-26.dahlia.

  3. The customer's full billing address before you create the payment. By default the Payment Element collects only a postal code and country; use the Address Element or set the Payment Element's fields.billingDetails.address to full.


Step 1: Calculate tax with Sphere

Call the Tax Calculation API with the customer's address and the items in the cart. The response gives you the tax per line, broken down by jurisdiction, and a sphere_tax_calculation_id that you store on the Stripe object so Sphere can match the calculation to the payment or invoice.

For business customers pass tax_id (and include_taxability_reason: true to see when a reverse charge applied). Field-by-field detail, tax IDs and error codes are on the Tax Calculation page.


Step 2a: One-time payment

A PaymentIntent has no invoice or line items, so tax goes into the amount. Your server adds Sphere's tax to the subtotal, creates the PaymentIntent, and records the calculation id in metadata.

What we will do

  1. The client sends the customer's address and cart to your server.

  2. The server calls Sphere (Step 1) and sums the returned tax_amounts.

  3. Server creates a PaymentIntent whose amount is subtotal + tax, with sphere_tax_calculation_id in metadata.

  4. Server returns the client_secret plus subtotal, tax and total for display.

  5. Client shows the breakdown and confirms with the Payment Element. Tax is paid as part of the single charge.

Server

Trimmed to the fields this guide uses.

Client

Show the subtotal, tax and total your server returned, then confirm as in Stripe's quickstart:

If the address changes before payment, call Sphere again and update the PaymentIntent (stripe.paymentIntents.update(id, { amount, metadata })). The amount can be changed until the payment is confirmed.


Step 2b: Subscription

Stripe finalises the first invoice of a subscription inside the subscriptions.create call, before tax can be added. Creating the subscription through a Subscription Schedule starting now gives you the first invoice as a draft: Sphere writes the tax onto its lines, your server finalises it and returns the payment client_secret to the client exactly as in Stripe's standard Elements flow.

What we will do

  1. Create the subscription through a Subscription Schedule with start_date: 'now'. This creates a real Subscription and its first invoice, but the invoice is a draft rather than finalised.

  2. Set auto_advance: false on the draft so Stripe does not finalise it while you work (it would otherwise do so after about an hour).

  3. Call Sphere with the invoice id. Sphere reads the draft, calculates tax from its line items and the customer's address, and writes the result onto each line as tax_amounts. No Tax Rate objects are created, and sphere_tax_calculation_id is recorded on the invoice's metadata.

  4. Finalise the invoice and read its confirmation_secret.

  5. Client confirms with the Payment Element exactly as in Stripe's standard subscription flow; on payment the subscription is active and later renewals are taxed by Sphere's webhook.

Server

1. Create the schedule

2. Hold the draft

3. Sphere - the invoice id, and the connected account when you have more than one:

4. Finalise

1. Schedule - note the invoice is a draft with auto_advance: true:

2. Invoice - "auto_advance": false, still "status": "draft".

3. Sphere - status is applied when tax was written, unchanged when the invoice already carries the same calculation, and no_tax when none applies. All three are safe to finalise:

The invoice's lines now carry the tax; the display_name, percentage and jurisdiction Sphere sent appear on the customer's invoice.

4. Finalised invoice

All responses trimmed to the fields this guide uses.

Client

Unchanged from Stripe's subscription guide: mount the Payment Element and call stripe.confirmPayment with the clientSecret. When payment succeeds the subscription is active.

Schedule settings. end_behavior: 'release' with a 12-month duration leaves a plain subscription after a year; call subscriptionSchedules.release after finalising if you want that immediately. Step 2 is required: the draft is created with auto_advance: true and Stripe would otherwise finalise it after about an hour, with or without tax.


Step 2c: Subscription via Stripe Checkout

Stripe Checkout creates and finalises the first invoice when the customer completes the hosted page, so there is no draft to write tax_amounts onto. Instead, collect the address before creating the session, create Stripe Tax Rate objects from Sphere's response, pass them on the session's line items, and remove them from the subscription as soon as Checkout completes so that renewals are taxed by Sphere.

You will need to be listening to the checkout.session.completed webhook for this method, which is the normal way of handling payment success/failure. You'll need to modify your webhook handler so that on success , you remove the Stripe Tax Rate objects we create and attach as part of this process. Even though we must use them with the Stripe Hosted Checkout, they prevent Sphere from keeping your tax calculations up to date as the customer and legislation change.

What we will do

  1. Collect the customer's address.

  2. Create a Stripe Tax Rate object to add Tax to the subscription

  3. Create the Checkout Session in subscription mode

  4. Direct the customer to the Stripe Hosted Checkout page.

  5. On checkout.session.completed, update the subscription for Sphere to maintain your tax calculations.

  1. Collect the address first (for example with the Address Element) and run Step 1.

  2. Create a Tax Rate object for each tax_rate in the response. Specify the tax rate attributes such as percentage, display name, and jurisdiction.

Managing Tax Rate objects. Where possible, consider reusing an existing active rate with the same percentage, jurisdiction, country, state, tax_type and inclusive if you you have one. This will prevent the creation of many Tax Rates in your Stripe account. You can also archive (active: false) rates you no longer use.

  1. Create the Checkout Session in subscription mode, with the calculation id on both the session and the subscription. Here we add the Tax Rate ids on each line item and sphere_tax_calculation_id on both the session and the subscription, then send the customer to session.url.

subscription is populated once the customer completes the page.

  1. Direct the customer to the Stripe Hosted Checkout page. The Hosted Checkout functions the same way as it normally does, though you will notice the tax being properly calculated and presented to the user.

  2. Remove the rates on completion. Checkout copies tax_rates onto the subscription items, and Stripe would apply them to every renewal. To ensure Sphere can function the checkout.session.completed, we clear them. The first invoice will still have the Tax Rates attached, but these were correct at time of purchase. Sphere ensures tax rates remain correct on future renewals.


Renewals

Nothing to do. Stripe creates each renewal invoice as a draft and holds it for about an hour; Sphere's Stripe integration writes tax onto it during that window, then Stripe finalises and charges as normal. The same applies to post-trial invoices and plan-change credits billed on the next invoice. Keep automatic_tax off and Tax Rate objects off your subscriptions, or Sphere's tax cannot be applied. Alternatively your billing system can manually call Sphere at renewal time, for stronger syncronous guarantees.

If you used Step 2c, this depends on the checkout.session.completed cleanup having run.


Common "gotchas" to watch out for

Avoid
Why

stripe.subscriptions.create(...) directly for card-on-file subscriptions

The first invoice is finalised inside the call. Use a Subscription Schedule (Step 2b).

Attaching Tax Rate objects as tax_rates outside Step 2c, or leaving them on a subscription after Checkout completes

Cannot coexist with tax_amounts on the same invoice, so Sphere cannot tax renewals; the objects also accumulate in your account unless reused and archived.

Enabling automatic_tax on any object

Mutually exclusive with Sphere on an invoice.

Relying on the Payment Element's default address collection

Postal code and country only. Collect the full address first.

Last updated

Was this helpful?