Skip to content

Checkout with Stripe

Take payment on your own website with Stripe's Payment Element, using the client secret and publishable key from checkout, send the shopper to their order page, and know what happens when they never finish.

How-to guide4 min read

Orders are paid into the shop's own Stripe account. Your website shows Stripe's Payment Element with keys Commerce Orbit gives you; card details go from the shopper's browser to Stripe and never reach your server or ours. You don't need a Stripe account or keys of your own.

The flow

  1. Build the cart with the storefront API until it has lines, an email, a name, a shipping choice and, for delivery, an address, and problems is empty.
  2. Call checkout with { "method": "card" }. Commerce Orbit prices the order on the server, creates it, takes the stock and creates a Stripe PaymentIntent for exactly the order total.
  3. Show the Payment Element with the clientSecret and publishableKey from the answer.
  4. Confirm the payment with Stripe, with a return_url on your website.
  5. Stripe tells Commerce Orbit when the payment succeeds. The order becomes paid, the shopper gets the confirmation email and the shop's team is told.
  6. On the return page, read the order to show what happened.

The amount is always worked out on the server from the cart, so the browser can't change what is charged.

Show the Payment Element

JavaScript
import { loadStripe } from "@stripe/stripe-js";const base = "https://app.bifrostorbit.com/commerce/api/public/northwilds";const token = localStorage.getItem("cart");const response = await fetch(`${base}/carts/${token}/checkout`, {  method: "POST",  headers: { "Content-Type": "application/json" },  body: JSON.stringify({ method: "card" }),});const { data, error } = await response.json();if (error) throw new Error(error.message);const stripe = await loadStripe(data.payment.publishableKey);const elements = stripe.elements({ clientSecret: data.payment.clientSecret });elements.create("payment").mount("#payment");document.querySelector("#pay").addEventListener("click", async () => {  const email = "ingrid@northwilds.com";  const { error } = await stripe.confirmPayment({    elements,    confirmParams: { return_url: `https://northwilds.com/order/${data.reference}?email=${encodeURIComponent(email)}` },  });  if (error) showMessage(error.message);});

Load Stripe with the publishableKey from each checkout answer rather than a key copied into your code: the shop can move from test to live keys, or change accounts, without you changing anything.

The Payment Element shows the payment methods switched on in the shop's Stripe account, such as cards, Apple Pay, Google Pay or Klarna. Some of them, and 3D Secure, send the shopper away and back to your return_url.

Calling checkout from the browser only works from the shop's allowed websites. See The storefront API.

The return page

Stripe sends the shopper back to your return_url with payment_intent, payment_intent_client_secret and redirect_status added. Read the order with the reference and email:

Shell
curl 'https://app.bifrostorbit.com/commerce/api/public/northwilds/orders/NW1042?email=ingrid%40northwilds.com'

If Stripe's message hasn't arrived yet, this call asks Stripe itself, so paymentStatus is paid as soon as the payment went through. Show pending as "We're confirming your payment" and read again after a few seconds. The hosted shop's order page works the same way, so you can also use it as the return_url:

Text
https://app.bifrostorbit.com/commerce/shop/northwilds/orders/NW1042?email=ingrid%40northwilds.com

Orders on invoice

For a shopper whose company has invoice terms (the cart's company.terms is invoice and company.onStop is false), call checkout with { "method": "invoice" }. The answer has "status": "placed" and no payment: the order is placed at once and the invoice is emailed. Go straight to the order page.

Webhooks and duplicates

You don't handle Stripe's webhooks: Commerce Orbit does, with a webhook it created in the shop's Stripe account. It checks each message's signature and handles each Stripe event once, however often Stripe sends it. The PaymentIntent is created with an idempotency key from the order, so a retried checkout request never charges twice for the same order.

To hear about orders on your own systems, see Events.

When things go wrong

  • The payment fails: Stripe shows the reason in the Payment Element, the order becomes failed, and the shopper can try again with the same clientSecret. Don't call checkout again for that.
  • The shopper changes the cart: call checkout again. The unpaid order is cancelled, its stock released, and a new order and PaymentIntent are made for the new total.
  • The shopper never finishes: the order stays pending while Commerce Orbit asks Stripe about it every minute. Once the shop's stock reservation time has passed (Hold stock for in the shop settings, 15 minutes unless the shop changes it, at most 120) it is cancelled and its stock goes back on sale. Nothing was charged.
  • A payment arrives after that: the order stays cancelled and its history says it was paid after it was cancelled, so the shop's team can refund it in Stripe.
  • The shop's Stripe key stops working: checkout answers 409 with "The payment couldn't start: …" and Stripe's reason, and nothing is created. Without any connection it answers "This shop can't take card payments yet".

Test mode

When the shop is connected with Stripe test keys, use Stripe's test cards, such as 4242 4242 4242 4242 with any future date and CVC, and 4000 0025 0000 3155 to try 3D Secure. No money moves. Ask the shop's team to cancel test orders before going live.

Was this page helpful?

Read next