KashuDocs

Payments

A payment charges a card once. It takes the money at once, or it authorizes the amount now so you capture it later. Refunds return part or all of a captured payment.

Charge a saved card

To charge a card you saved for a customer, name the card's pm_ id:

curl https://api.kashupay.com/v1/payments \
  -H "Authorization: Bearer $KASHU_SECRET_KEY" \
  -H "Idempotency-Key: order-1001-charge" \
  -H "Content-Type: application/json" \
  -d '{"amount":4999,"currency":"USD","customer":"cu_...","payment_method":"pm_...","metadata":{"order":"1001"}}'

The answer is the payment, with status succeeded and amount_captured 4999. Amounts are in the currency's minor unit, so 4999 is 49.99 USD. A payment is made in your default store unless you name another in store.

To charge a card the buyer just typed into the card form, send token with the card token instead of payment_method. customer is then required. Kashu saves the card for the customer, and the payment names it in payment_method, so you can charge it again later. Card form and Kashu.js shows how to get a token.

Authorize now, capture later

Send capture_method manual to hold the amount on the card without taking it, for example until you ship:

const payment = await kashu("POST", "/v1/payments", {
  amount: 12000,
  currency: "USD",
  customer: "cu_...",
  payment_method: "pm_...",
  capture_method: "manual",
}, { idempotencyKey: `auth-${order.id}` });

if (payment.status === "requires_capture") console.log("held", payment.amount_capturable);

The payment's status is requires_capture, and amount_capturable is the amount held.

Capture once

Capture when you are ready to take the money. Send an amount to take less than you held, or leave it out to take all of it:

const captured = await kashu("POST", `/v1/payments/${payment.id}/capture`, { amount: 10000 }, { idempotencyKey: `capture-${order.id}` });
console.log(captured.status, captured.amount_captured);

An authorization takes one capture. Capture less than you held, and the rest can no longer be captured: the payment is succeeded, with amount_captured the amount you took. A second capture answers 400 payment_unexpected_state, and an amount over amount_capturable answers 400 amount_too_large.

Void instead

If you will not take the money, release the hold:

curl https://api.kashupay.com/v1/payments/py_.../void \
  -X POST \
  -H "Authorization: Bearer $KASHU_SECRET_KEY" \
  -H "Idempotency-Key: order-1001-void"

The payment's status becomes canceled. Only a payment in requires_capture can be voided. Any other answers 400 payment_unexpected_state.

Refund a payment

Refund part of a captured payment by sending an amount, or all that is left by leaving it out:

curl https://api.kashupay.com/v1/refunds \
  -H "Authorization: Bearer $KASHU_SECRET_KEY" \
  -H "Idempotency-Key: rma-77-refund" \
  -H "Content-Type: application/json" \
  -d '{"payment":"py_...","amount":2500,"reason":"requested_by_customer"}'

You can refund a payment more than once, until its refunds add up to amount_captured. An amount over what is left answers 400 amount_too_large. Only a succeeded payment can be refunded: to release an authorization, void it.

A refund's status is pending while the card network answers, then succeeded or failed. A failed refund names why in failure_reason. The payment keeps status succeeded, and its amount_refunded grows with each refund that succeeds.

Know what each status means

status Meaning
pending The payment has started and has no result yet.
processing Kashu is waiting for the card network's answer. Retry with the same key or wait for the webhook.
requires_action The buyer must finish a step, such as a bank's check, at next_action.redirect_to_url.url.
requires_capture The amount is held. Capture it or void it.
succeeded The money was taken. amount_refunded shows what has been refunded since.
failed No money was taken. failure_code says why.
canceled The hold was voided.

When a card is declined

A declined card is not an HTTP error. The request answers 201 with a payment whose status is failed, failure_code card_declined, and a decline_code such as insufficient_funds or expired_card. A failure_code of processing_error means the payment could not be completed for another reason.

Ask the buyer for another card, then make a new payment with a new Idempotency-Key. The old key stays bound to the failed payment, so sending it again only returns that payment.

One change at a time

Kashu runs one capture, void or refund on a payment at a time. A second request sent while the first runs answers 409 operation_in_progress and changes nothing. Retry it with the same key once the first has finished.

Follow a payment by webhook

Each change to a payment sends an event: payment.created when it is made, payment.succeeded, payment.requires_capture, payment.failed or payment.canceled when its status moves, and payment.updated when only its amounts change, as after a refund. Refunds send refund.created, then refund.succeeded or refund.failed. Webhooks explains how to receive them.