← All notes
E-commerce Development7 min

Don't rebuild the subscription payment form

Subscription self-service usually means skip, pause, and update the card. Skip and pause are your rules. The card update is a hosted Customer Account workflow — especially now that Shopify's Intents API can open payment-method replacement on a SubscriptionContract without your extension storing instruments.

Self-service is where teams quietly rebuild checkout

Most subscription builds start with a reasonable request: let the customer skip a delivery, change a date, or update the card on file without emailing support. The skip and date changes are product logic. The card update is not. It is a PCI surface wearing a settings-page costume.

The failure mode we keep seeing is a custom "account portal" that signs people in with a one-off magic link or a copied customer ID, then collects a new card through whatever payment form the team already had lying around. It works in a staging store with one test subscription. It fails when a vaulted instrument expires mid-cycle, when the buyer authenticated through the storefront but not through your portal, or when the merchant later needs Built for Shopify compliance on the same flow.

Shopify's Customer Account surface is the place that already has the buyer's session, the subscription contract, and a hosted path to replace a payment method. Rebuilding that path is the same category of mistake as rebuilding checkout: you inherit the risk without inheriting the maintenance.

Own the schedule rules; invoke the card swap

The useful split is the same seam we use on cart work. The platform owns money instruments and the authenticated customer. Custom code owns the business rules that make this subscription different from every other one — which products can skip, whether a pause freezes billing or only fulfillment, what happens when a cycle lands on a holiday blackout.

In a Customer Account UI extension, that means rendering the schedule and plan controls yourself, and opening Shopify's payment-method replacement intent when the buyer needs to change the card. The Intents API takes a SubscriptionContract id and a field of paymentMethod, then runs a workflow your extension does not style and does not store. You await completion, check that the response code is ok, and refresh the contract view.

Treat closed and error as first-class outcomes. A buyer who dismisses the sheet is not a failed mutation. An extension that assumes every invoke succeeds will show a stale last-four and a false sense that the next billing attempt is safe.

What belongs in the extension vs the intent
Build in your extension
  • Skip, pause, and blackout rules
  • Plan and quantity changes the catalog allows
  • Copy that explains the next charge date
  • Refresh after a completed intent
Hand to Shopify
  • Customer authentication
  • Vaulted card capture and replace
  • PCI and instrument storage
  • The payment-method UI chrome

"Bad" here means costly to own — not morally wrong. The payment method workflow is one of those surfaces.

Authenticate once, on the Customer Account API

A second login for subscription management creates two identities that disagree. The storefront thinks the buyer is signed in; your portal has a different cookie; support sees two timelines for the same email. Customer Account API authentication collapses that into the session Shopify already maintains across the shop and the account pages.

That is not only an engineering preference. Subscription apps that expose buyer-facing self-service are being held to Customer Account API auth for Built for Shopify status, with a December 2026 deadline. Designing a parallel auth path now is designing a migration later.

Practically: extensions query the Customer Account GraphQL endpoint with a pinned API version, never unstable. Admin API tokens stay on the server for merchant-side repairs. The buyer-facing path should not need a long-lived admin credential to prove who is looking at their own contract.

What still has to be yours

Invoking an intent does not finish the product. You still need a clear model of contract state after skip, pause, swap, and failed billing. You still need webhooks or polling that assume events arrive twice. You still need ops tooling for the cases the buyer cannot fix — a stuck payment method, a contract that should have cancelled, a gift subscription whose recipient is not the payer.

Those are the parts worth scheduling. They encode how this merchant sells recurring product. The card form does not. If a scoping conversation spends more time on the look of the payment fields than on what pause means for inventory and billing, the seam is in the wrong place.

When the platform cannot express the subscription at all — per-account pricing that is not B2B company location, a configurator that invents the SKU each cycle, a billing rhythm Shopify will not model — then more of the stack becomes custom. Even then, prefer handing the instrument update to a hosted flow. The goal is a narrow PCI surface, not a prettier card iframe.

Acceptance checks we want on the first demo

Before we call subscription self-service done, we want a short path a merchant can walk without us narrating. Sign in through Customer Account, open an active contract, skip or reschedule once, replace the payment method through the intent, cancel the intent once on purpose, then complete it. Confirm the UI only claims success when the response code is ok, and that a closed sheet leaves the previous instrument in place.

Add the failure the support team will see: a declined replacement, a contract with no replaceable method, a buyer who lands on the extension from an order page instead of a full-page account target. If those states only work on the happy path, the portal will train customers to email the shop the first week it ships.

The note we write in the handoff is boring on purpose: auth is Customer Account, money instruments are intents or other hosted flows, custom code owns schedule semantics and recovery. That sentence prevents the next contractor from "improving" the portal by pasting in a checkout form.

Questions

Why not collect a new card in a custom subscription portal?
Card capture is a PCI and vaulting problem Shopify already solves inside Customer Account. A custom form usually also invents a second login, which drifts from the storefront session and becomes a compliance migration later.
What should a Customer Account UI extension own?
Schedule and plan semantics — skip, pause, blackout dates, allowed quantity changes — plus clear copy about the next charge. Open Shopify's payment-method intent for instrument changes, await completion, and refresh only when the response code is ok.
How should buyers authenticate for subscription self-service?
Through the Customer Account API session Shopify already maintains. Buyer-facing subscription apps are expected to use that auth path for Built for Shopify; a parallel magic-link portal is a second identity to keep honest.

Sources

  1. Shopify Customer Account Intents API
  2. Intents API changelog — subscription payment method replacement
  3. Built for Shopify — Customer Account API for subscription self-service
  4. Customer Account API

Have something to build?

Tell us what you're working on and we'll tell you honestly whether we're the right fit.

Work with us