Saved payment methods
Save a card during a charge, then charge it again with the customer present, within the bounds of the payment that saved it.
A card saved during a checkout can be charged again with the customer present: an upsell or an add-on right after the purchase that saved it. There is no hosted page, no redirect, and no card number flows through your servers; you send the two opaque ids the original save returned.
POST /v1/payments/charge-saved-methodThis is not a tool for unattended renewals: every reuse is bounded by the payment that first saved
the card, and a charge weeks later is refused with WINDOW_CLOSED.
Obtaining the ids
Set savePaymentMethodForFutureUse: true on a
card charge. Sending the flag attests that you have the
customer's consent to save the card. The 201 then carries two extra fields:
{
"paymentId": "01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b",
"status": "CAPTURED",
"amount": 7200,
"cardNumber": "555555xxxxxxxx4444",
"cardExpiration": "1133",
"cardholderName": "Jordan Example",
"customerId": "0192a7d1-4e2b-7f08-9a6c-2d3e4f5a6b7c",
"paymentMethodId": "0192a7d1-8c3f-7a21-b5d4-6e7f8a9b0c1d"
}| Field | Notes |
|---|---|
customerId | Identifies the customer on your account, keyed on cardholderEmail (trimmed and lowercased). Persist it alongside your own customer record. |
paymentMethodId | Identifies the saved card. Pair it with customerId to charge again. |
- The ids are scoped to your account. Another account cannot use them, and a wrong pairing of customer and method is rejected.
- Treat both ids as confidential: with your publishable key they are enough to charge the card from a browser. Keep them out of URLs, logs and client-side storage.
- The ids are chargeable once the payment that saved the card is
CAPTURED: at once when the charge answeredCAPTURED, after the capture when you authorized first, and once aPENDINGcharge settles. Until thencharge-saved-methodanswers409withreason: "ORIGINAL_NOT_SETTLED". - A charge that came back as a 3-D Secure challenge (
status: "REQUIRES_ACTION") returns a pending save instead:charge-saved-methodanswers409withreason: "METHOD_NOT_READY"until the payment settles successfully. If the authentication fails, expires or is voided, the id is invalidated and a later charge answers404. - Saving a card that is already on file for the same customer returns the existing
paymentMethodId. The bounds below belong to the card, not to the id: a re-save opens nothing new. - A checkout through the Payment SDK can save a card too; it returns the same
kind of ids, which this endpoint accepts. A hosted checkout
created with
savePaymentMethodForFutureUse: truecan save one as well, but no API response returns its ids.
Charge a saved method
Idempotency-Key: honoured. A 409 refusal with a reason is not stored under the key.
| Field | Type | Required | Rules |
|---|---|---|---|
amount | integer | yes | Cents, at least 100, and at most the amount of the payment that first saved the card. |
customerId | string | yes | UUID v7 returned by the original save. |
paymentMethodId | string | yes | UUID v7 returned by the original save. Must belong to customerId and to your account. |
curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/charge-saved-method \
-u "YOUR_API_KEY:YOUR_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0193c2fa-8e1b-7d5c-9a2f-3b4c5d6e7f80" \
-d '{
"amount": 1500,
"customerId": "0192a7d1-4e2b-7f08-9a6c-2d3e4f5a6b7c",
"paymentMethodId": "0192a7d1-8c3f-7a21-b5d4-6e7f8a9b0c1d"
}'201 Created. The response is deliberately slim: your application already has the card details
from the original save.
{
"paymentId": "0192a3f7-9c0d-7e1f-8a2b-3c4d5e6f7a8b",
"status": "CAPTURED",
"amount": 1500,
"customerId": "0192a7d1-4e2b-7f08-9a6c-2d3e4f5a6b7c",
"paymentMethodId": "0192a7d1-8c3f-7a21-b5d4-6e7f8a9b0c1d"
}| Field | Notes |
|---|---|
paymentId | The new payment. Persist it for refunds. |
status | CAPTURED when the charge settled synchronously. PENDING when your account collects asynchronously: the payment settles to CAPTURED or DECLINED shortly after; poll it. |
amount | Cents charged. |
customerId, paymentMethodId | Echo the request. |
This route never returns REQUIRES_ACTION: an issuer that insists on a 3-D Secure challenge
declines the charge with declinedReason: "Additional authentication required", and the attempt
counts toward the attempt limit.
Bounds
Every reuse is bounded by the saved card and by the payment that first saved it (its original payment), whichever of the card's ids is charged. Reuses made from the browser through the Payment SDK count toward the same bounds.
| Bound | Rule |
|---|---|
| Window | Accepted up to 24 hours after the card was first saved, that is, after it became chargeable. The window never restarts, not even when the same card is saved again. |
| Approved reuses | At most 2 per card. A reuse still in progress holds its place; a declined one does not count. |
| Attempts | At most 5 per card, declined ones included. |
| Amount | Each reuse is at most the original payment's amount, per charge, not cumulative. |
Reuse refusals
A reuse outside its bounds is refused with 409 and a machine-readable reason before the
processor is contacted: nothing is charged, no payment is recorded, your limits are not consumed,
and the refusal is not stored under the Idempotency-Key.
{
"statusCode": 409,
"message": "This saved payment method has reached its reuse limit.",
"reason": "REUSE_LIMIT"
}reason | message | When | What to do |
|---|---|---|---|
ORIGINAL_NOT_SETTLED | The original payment has not settled yet; retry once it is captured. | The payment that first saved the card is still PENDING, REQUIRES_ACTION or AUTHORIZED. | Retry once it is captured; the same key is fine. |
METHOD_NOT_READY | The saved payment method is not ready yet; retry once its checkout is captured. | The method is a pending save whose checkout has not settled (an SDK checkout, a 3-D Secure challenge). | Retry once the checkout is captured; the same key is fine. |
ORIGINAL_NOT_ELIGIBLE | The original payment is not in a state that allows charging this saved method again. | The original payment was refunded in full, disputed, voided, declined or expired. | Do not retry; collect the card again. |
WINDOW_CLOSED | The 24-hour window for charging this saved method again has closed. | More than 24 hours since the card was first saved. | Do not retry; collect the card again. |
REUSE_LIMIT | This saved payment method has reached its reuse limit. | The card was already charged again twice, approved or in progress, from either surface. | Do not retry; collect the card again. |
ATTEMPT_LIMIT | This saved payment method has reached its limit of upsell attempts. | Five attempts were already made with the card, declined ones included. | Do not retry; collect the card again. |
AMOUNT_LIMIT | An upsell cannot exceed the amount of the original payment. | amount exceeds the original payment's amount. | A smaller amount may pass. |
A 409 without reason is an Idempotency-Key still in flight.
Ownership and state errors
These are refused before the processor is contacted, with the
charge decline shape reduced to amount, status
and declinedReason. No payment is recorded and your limits are not consumed.
| Status | declinedReason | When |
|---|---|---|
404 | Payment method not found. | The paymentMethodId does not exist, belongs to another account, or came from a checkout that expired or was abandoned before it settled. The cases are deliberately indistinguishable. |
400 | Payment method does not belong to the given customer. | Both ids are yours, but the method belongs to a different customerId. Usually a pairing bug on your side. |
400 | Payment method is removed., Payment method is expired., Payment method is not chargeable. | The saved card is no longer chargeable and will not become chargeable again. Save the card again the next time the customer is present. |
400 | Payment method is not chargeable on this account. | The saved card can no longer be charged on your account. Save it again the next time the customer is present. |
{
"amount": 1500,
"status": "DECLINED",
"declinedReason": "Payment method not found."
}Declines
When the issuer or your limits refuse the charge, the answer is 400 with the full
charge decline shape. The payment is recorded as
DECLINED, paymentId names it, and the attempt counts toward the five attempts (not toward the two
reuses).
{
"amount": 1500,
"cardNumber": "555555xxxxxxxx4444",
"cardExpiration": "1133",
"customerId": "0192a7d1-4e2b-7f08-9a6c-2d3e4f5a6b7c",
"paymentMethodId": "0192a7d1-8c3f-7a21-b5d4-6e7f8a9b0c1d",
"paymentId": "0192a3f7-9c0d-7e1f-8a2b-3c4d5e6f7a8b",
"status": "DECLINED",
"declinedReason": "Card declined"
}| Status | Body | When |
|---|---|---|
201 | The slim response above. | Charged, or accepted and settling (PENDING). |
400 | Validation error | amount below 100, or an id that is not a UUID v7. |
400 | Charge decline | An ownership or state error (above), a limit, or the issuer. |
404 | Charge decline | Payment method not found. |
409 | Reason-coded conflict | A reuse refusal, or (without reason) an Idempotency-Key in flight. |
422 | Plain error | The key was reused with a different body. |
429 | VELOCITY_LIMIT | A velocity check refused the charge. |
401, 403 | Plain error | Invalid credentials or an inactive account (401); a refund-only account (403). |
From the browser
The same charge can be made from your checkout page through the Payment SDK with the
publishable key, so it works after a page reload. It is bounded by the same window, limits and
amount, and the counts are shared: a reuse from the browser and one from this endpoint are the
card's two. A reuse the browser gets back as PENDING is followed from your server with
GET /v1/payments/{id}; the browser holds no
credential to read it. See hw.chargeSavedMethod()
in the Payment SDK reference.
Hosted checkout
Create a payment link from your server, hand it to the shopper, and confirm the payment before you fulfil the order, with no card data and no browser code on your side.
Refunds
Refund a captured payment in full or in part, read the refund's own status, and know what a dispute does to refunds.