PayPal Advanced Credit Card Payments and Vaulting Flow
PayPal saves cards as reusable tokens for subscriptions and free trials.
PayPal's Advanced Credit Card vaulting flow exchanges a payer's card for a reusable token that a merchant stores server-side and charges later, without asking the payer to re-enter their card details. The integration earns its complexity because of three recurring business needs: a free trial that converts into a paid charge once the trial window closes, a subscription that bills on a schedule, and a one-click repeat checkout that skips the payment form. In each case, the payer is not necessarily present at the moment the card gets charged, so the system needs something durable to charge against that was captured earlier.
PayPal implements this with two distinct API paths, and the split is not an accident of API design. One path, built on the Payment Method Tokens API, saves a card when no purchase is attached. The other, built on the Orders API, saves a card as a byproduct of a checkout that is already happening. Both paths end at the same place, a token a merchant can hand back to PayPal on a future date to initiate a charge, but they get there through different sequences of calls, different compliance obligations, and different failure modes. That fork, Payment Method Tokens versus Orders API, is the first decision any engineer building this integration has to make, and it shapes everything that follows.
The two paths through the vaulting flow
The decision between the two paths is determined by two concrete questions: does a purchase accompany the card save, and what PCI compliance posture does the merchant already hold. Answering those wrong doesn't produce a stylistic inconsistency, it produces either an integration that cannot complete its token exchange or a compliance burden the merchant didn't need to take on.
Path A is the Payment Method Tokens API, which is the without-purchase route. PayPal's documentation for this path requires SAQ D PCI compliance, and it requires no transaction at all: the card is saved on its own, and the merchant can charge it after an arbitrary interval has passed. The canonical case is the free trial, where the card is saved on day zero and the actual charge doesn't fire until the trial period runs out.
Path B is the Orders API, the with-purchase route. PayPal's Orders API vaulting documentation also requires SAQ D when the integration passes raw card data, and it assumes the merchant already has an advanced credit and debit card payments integration in place before vaulting enters the picture. This path attaches the vault instruction to an order that is being created and captured in the same flow, so the card save rides along with a transaction that is already happening.
The JavaScript SDK v5 offers a third option, a way to save a card without a purchase that avoids the SAQ D requirement altogether, aimed at merchants who want the free-trial pattern without taking on PCI compliance at that level. It sits alongside Path A as an alternative for teams whose compliance posture doesn't support direct card-data handling.
Step-by-step: the Payment Method Tokens path (without purchase)
The without-purchase path runs as a two-step token exchange, and the order of those steps matters as much as their content. A setup token is created first, and only then is it converted into a permanent payment token. Skipping a step or retrying or calling one out of sequence lands the integration in a state it wasn't built to handle.
Before any of that, the merchant has to complete a configuration step that has nothing to do with the API calls themselves: PayPal's documentation requires Vault to be enabled under Sandbox App Settings, inside App Feature Options, under Accept Payments, in Advanced Options. Until that toggle is set, no call in the sequence that follows will succeed, regardless of how correctly the request is built.
With that done, the first real step is a POST to the setup-tokens endpoint, which creates a setup token. Three verification modes are available here: no verification, smart authorization, and 3D Secure verification, and each produces a different intermediate response that downstream code has to be ready for. A successful call returns an id, which is the setup token ID, along with a pair of HATEOAS links, one a self GET that retrieves the payment source data, the other a confirm POST that generates the payment token. The first time a given payer saves a card, the response also includes a customer.id, and that identifier becomes the anchor for linking any additional payment sources the same payer saves later. Smart authorization deserves a specific note here: where an issuing bank supports it, PayPal runs a zero-value authorization, and where it doesn't, it runs a minimal-value authorization instead. That minimal-value authorization is not automatically voided. It leaves a temporary hold on the payer's card that the integration needs to account for.
The second step takes the setup token ID produced in step one and POSTs it to the payment-tokens endpoint, and that converts it into a permanent payment token. The response carries a vault_id, and that is the durable identifier the merchant stores on its own servers and reuses for every future charge against that card. From this point forward, the vault_id stands in for the original card number in every subsequent API call, so the raw card number never needs to be stored or transmitted again.
The final step in this path is the actual charge that happens later, whether that's at the end of a free trial or on a billing cycle. The merchant passes the stored vault_id into a new Orders API call to initiate the charge, and the payer doesn't need to be present or involved at all when that call runs. The chain from setup token ID, to customer.id, to vault_id, to the eventual charge is exactly that, a chain, where each step's output is the next step's required input.
Step-by-step: the Orders API path (with purchase)
The with-purchase path folds the vault save into a checkout that may or may not succeed, and that conditional outcome is the single most important thing to understand about this path. Because the save depends on the checkout's result, a test suite built around it has to cover both the branch where a vault entry gets created and the branch where it doesn't.
The flow starts with the creation of an order that carries a vault instruction alongside the usual payment details. The request sets card.attributes.vault.store_in_vault to ON_SUCCESS, and it includes the customer ID and payment source along with it. Where 3D Secure applies, the request also passes either SCA_ALWAYS or SCA_WHEN_REQUIRED in payment_source.card.attributes.verification.method, and if a 3DS contingency gets triggered, the API response comes back with a status of PAYER_ACTION_REQUIRED, leaving the checkout incomplete until the payer acts.
The next step is capture, and this is where the ON_SUCCESS instruction does its work. ON_SUCCESS means the card is only stored in the vault if at least one authorization or capture on that order succeeds. If the order fails or gets voided, no vault entry is created at all, and the merchant's own system has to handle that branch explicitly. An integration that assumes every checkout produces a vault ID will work fine in testing against a well-behaved happy path and then fail silently in production the first time a card gets declined.
Once capture succeeds, the response includes a vault.id, which functions as the same kind of durable token that Path A produces through its payment-tokens step, and the merchant stores it the same way, passing it into future Orders API calls to initiate charges without the payer present. One structural constraint carries through this entire path: the vault is accessible only by the billing agreement holder, a detail that matters directly to any platform or multi-merchant architecture built on top of this flow.
Merchant-level vaulting on platforms and the onboarding dependency
Everything described so far assumes a single merchant vaulting cards for its own use. The picture changes when a third party, a platform or a commerce host, manages vaulting on behalf of a collection of individual merchants. In that arrangement, a merchant has to be onboarded onto the platform before any vault token can be correctly scoped to that merchant.
PayPal's documentation on merchant-level onboarding is specific about the scope of what gets saved: merchant-level vaulting supports a payer using a saved payment method with one merchant, not a saved method that works interchangeably across merchants on the same platform. The vault entry belongs to a single merchant relationship, and the token flow has no mechanism to make it portable beyond that.
The onboarding sequence itself produces its own state that the rest of the integration depends on. Once a merchant finishes the signup flow, PayPal redirects them to the return URL specified in the Partner Referrals API's partner_config_override/return_url field, and that redirect carries three pieces of information: consentStatus, a productIntentId set to addipmt, and isEmailConfirmed. A platform engineer building on top of this needs to treat that redirect as a confirmation step in its own right, not a formality, because a vault token created before onboarding completes will not be scoped to the merchant correctly. The onboarding confirmation has to happen before the setup token flow begins, which makes it one more stateful prerequisite layered on top of the token exchange already described.
The payment_pattern field and network compliance for recurring charges
Having a vault ID is not, by itself, sufficient to guarantee that a recurring charge will actually succeed. The charge request still needs to tell the card networks what kind of transaction it represents, and it does that through a field called payment_pattern.
For vaulted and billing-agreement card transactions, payment_pattern carries two kinds of information to Mastercard and Visa: a frequency signal, indicating whether the charge is a single event or part of a recurring series, and a payer-presence signal, indicating whether the cardholder is actively involved in this particular transaction. Networks use these signals to evaluate authorization requests, so if a charge omits or misrepresents them, it risks a lower authorization rate or a compliance flag, whether or not the vault ID itself is valid. An engineer who has correctly built the token exchange described in the two paths above still has one more field to get right on the charge call itself, because the networks are evaluating the transaction pattern, not just the token.
Why this flow is inherently stateful
Every step described above produces an artifact that the next step requires as input: a setup token ID feeds into a payment token request, a customer.id anchors future payment sources to the same payer, a vault ID emerges only when a capture succeeds. None of this is a single request-response exchange that can be evaluated in isolation. It is a sequence, and a test environment that cannot maintain that sequence across calls cannot meaningfully test the integration built on top of it.
A stateless mock returns the same response no matter what came before it, and that limitation is exactly where it breaks down against this flow. It cannot enforce that a payment token request references a setup token that exists in the mock's recorded state, and it cannot enforce that a vault ID appears in a capture response only when the capture genuinely succeeded. Research into stateful API scanning backs this up directly: a scanning tool that issues requests in isolation misses the vulnerabilities and logic errors that only appear when an object created in one request gets referenced in a later one. The same principle governs correctness testing here, not just security testing.
The ON_SUCCESS branch in the Orders API path shows why this matters in practice. A stateless mock that always returns a vault ID can't exercise the code path where the vault ID is absent because the capture failed, and that absent-vault-ID path is the one most likely to produce a bug that stays invisible until it reaches production. The minimal-value authorization hold that smart authorization creates, on cards where zero-value authorization isn't supported, is another artifact of the same kind: a test environment that doesn't track it as state will not catch an integration that fails to account for that hold later on.
What a test suite for the vaulting flow must cover
A complete test suite for this integration has to include, at minimum, the happy path for each of the two API paths, the ON_SUCCESS branch in which no vault entry gets created, the 3DS contingency path that returns PAYER_ACTION_REQUIRED, the creation of customer.id on a payer's first saved card, and the linking of a second payment source to a customer.id that already exists. Leaving any one of these out means the suite is certifying a flow that doesn't match what the integration will actually encounter once real payers and real declined cards start moving through it.
The three verification modes, no verification, smart authorization, and 3D Secure, each produce distinct intermediate responses and need to be tested as separate cases. A suite that only exercises one mode will miss whatever bugs surface specifically in the other two.
You should put the sandbox configuration step, enabling Vault under App Settings, into the test environment setup itself, not into the list of assumptions a tester carries into the work. An unconfigured sandbox fails in ways that look exactly like code defects, and a test suite that doesn't control for that will send engineers chasing bugs that don't exist in the code.
PayPal's own sandbox supports negative testing for a set of Orders API v2 methods, including create order, update order, show order details, authorize payment for order, and capture payment for order, through a specific request header built for that purpose. The Orders API documentation describes this negative-testing capability as currently in beta. Engineers building a test suite around the vaulting flow can use a documented mechanism, direct from PayPal's own sandbox tooling, to exercise the failure branches that the ON_SUCCESS instruction and the capture step make possible, so they don't have to simulate those failures entirely on their own.