Charge the Stored Card
With both IDs saved, you can charge that card whenever you need to — no customer, no browser, no 3DS.
API Documentation: Create Charge
Request:
curl -i -X POST \
https://sandbox-api.pathly.io/charges \
-H 'Authorization: Bearer <YOUR_JWT_HERE>' \
-H 'Content-Type: application/json' \
-d '{
"id": "b3c9e0a1-7d44-4f2a-9e8b-5c1d2e3f4a5b",
"customer_id": "7a2e5f18-3b6c-4d9e-8f01-2a3b4c5d6e7f",
"payment_method_id": "c41a8b72-5e3d-4a19-b6c8-9d0e1f2a3b4c",
"amount": {
"value": 2500,
"currency": "USD"
},
"description": "Automatic top-up",
"mit": true
}'
Amounts are in minor units — 2500 is $25.00.
Note what is absent from the request:
- No card object. The card lives behind
payment_method_id. You never re-send a card number. - No
three_dsblock. There is no browser to authenticate and no one to answer a challenge. - No CV2. Nobody is there to type it.
mit defaults to false when omitted, which means the charge is treated as customer-initiated. Set it explicitly to true. Sending an unattended charge as a CIT misrepresents it to the card networks.
Reading the response
| Code | Meaning | What to do |
|---|---|---|
| 200 | The charge succeeded | Done |
| 202 | Pending — not successful | Poll GET /charges/{id} or wait for charge.updated. Do not release goods |
| 409 | Refused | Read message; most are not retryable as they stand |
| 422 | Malformed request | Fix the payload — retrying it unchanged will not help |
Success:
{
"status": "success",
"code": 200,
"message": "Successful request",
"data": {
"id": "b3c9e0a1-7d44-4f2a-9e8b-5c1d2e3f4a5b",
"status": "succeeded"
}
}
The 202 is the one most often misread. Several processors settle asynchronously: the charge is real, but its outcome is not yet known. The charge's status moves to succeeded or failed later, and the charge.updated webhook tells you when.
Two refusals worth handling by name
Merchant-initiated payments are not enabled:
Merchant-initiated payments are not enabled for this merchant. An administrator must enable them before a payment can be charged without the customer.
Nothing in your code fixes this — it is the allow-mit prerequisite. Contact us to have it enabled.
The card has no first payment behind it:
This payment method needs a successful first payment before it can be charged as a merchant-initiated transaction (payment method
c41a8b72-…).
The credential has not been established. This specific payment_method_id has never completed a successful customer-initiated charge. Send the customer through the first payment once, then retry.
Depending on how your account is configured, this rule may be enforced, recorded only, or not checked at all. Build for the strict case and you will never be surprised by it.
Rules that are invisible until they bite
A re-added card is a new card. If a customer adds the same card number again, it becomes a different payment_method_id with no history of its own, and it needs its own first payment before it can be charged as an MIT. The credential belongs to the stored payment method, not to the card number. Always reuse the stored ID rather than re-collecting the card.
The amount does not have to match. An MIT can be for more or less than the first payment, and can differ every time. There is no requirement that later charges match the original.
MITs carry no liability shift. That protection came from the 3DS authentication on the first payment. This is the accepted trade for charging an absent customer — but it is a trade, and disputes on merchant-initiated charges land on you.
Deleting a payment method ends the arrangement. DELETE /payment-methods/{payment_method_id} removes the stored credential. Any ID you have kept for it will stop working.
When the customer is present: a pre-bound session
An MIT is for charging without the customer. When the customer is back on your site and choosing to pay, that is a CIT again — and you still should not ask for their card.
Create a session bound to the customer and the card you stored:
curl -i -X POST \
https://sandbox-api.pathly.io/checkout/sessions \
-H 'Authorization: Bearer <YOUR_JWT_HERE>' \
-H 'Content-Type: application/json' \
-d '{
"id": "d5f6a7b8-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"customer_id": "7a2e5f18-3b6c-4d9e-8f01-2a3b4c5d6e7f",
"payment_method_id": "c41a8b72-5e3d-4a19-b6c8-9d0e1f2a3b4c",
"currency": "USD",
"client_reference_id": "order-556",
"success_url": "https://yourapp.com/thanks",
"cancel_url": "https://yourapp.com/cart",
"items": [
{ "description": "Order 556", "quantity": 1, "unit_amount": 4200 }
]
}'
The checkout opens for that customer with that card already selected, and because the customer is present, 3DS can run and the payment carries the liability protection an MIT does not.
Two rules apply:
payment_method_idmust belong tocustomer_id, or the request is refused with "Payment method not found".payment_method_idrequirescustomer_id. Sent on its own it is refused with "customer_id is required when payment_method_id is provided."
Use this for a returning customer at a checkout, and the merchant-initiated charge described at the top of this page when nobody is there.
A worked sequence
1. first payment (session or 3DS charge) → customer pays, 3DS runs
2. webhook session.paid / charge.updated → confirm status is succeeded
3. GET the session or charge → customer_id, payment_method_id
4. save both against your user ← everything else depends on this
───────────────────────────────────────────────────────────────────────
5. POST /charges { …, "mit": true } → 200, succeeded
6. POST /charges { …, "mit": true } → 200, again next month
7. POST /charges { …, "mit": true } → 202, poll or await charge.updated
Steps 1–4 happen once per customer. Step 5 onwards happens for the life of the relationship.