Charge an Invoice

Charge a stored payment method against an invoice with POST /v2/transactions, then poll GET /v2/transactions when the status is not final.

Use POST /v2/transactions to charge a stored payment method against one invoice.

You need:

  • An x-api-key with organizations:transactions:create
  • A required Idempotency-Key header — see Idempotency
curl -X POST https://api.benjipays.com/v2/transactions \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Idempotency-Key: a1b2c3d4e5f6" \
  -H "Content-Type: application/json" \
  -d '{
    "invoiceId": "1042",
    "paymentMethodId": "507f1f77bcf86cd799439011",
    "amount": 50.00,
    "sendReceipt": true
  }'

Body

FieldRequiredDescription
invoiceIdyesInvoice id from GET /v2/invoices.
paymentMethodIdyesStored payment method for that invoice's customer. Must be a 24-character hex id.
amountnoAmount to charge. Omit this field to charge the invoice open balance. Must be greater than 0, at most two decimal places, and not more than the open balance.
sendReceiptnotrue (or omit) sends a receipt email after a successful charge. false skips the receipt.

Only paymentMethodId is charged. If that method cannot be used, the request fails — another stored method is not tried.

A payment method still pending verification returns 400 with detail This payment method is still pending verification and cannot be used.

Response

HTTP 200 means the charge was attempted. Read data (the transaction) and meta.effects (charge, accounting apply, receipt).

A card decline is still 200. Check meta.effects.charge.status (succeeded, failed, voided, or skipped) and data.status.

RFC 7807 (400, 409, …) means the charge did not run: bad input, invoice not chargeable, invoice locked, missing Idempotency-Key, and similar setup errors.

503 also means the charge did not run. It is returned when idempotency protection is temporarily unavailable, so we refuse the charge rather than risk a duplicate. Wait Retry-After seconds and send the same request again with the same Idempotency-Key.

Save data.id from the 200 body. Use that id when you look the transaction up later.

After you charge

Many card charges are finished on the POST. data.status is then approved, approved_with_error, or declined.

Some charges stay in progress. Poll GET /v2/transactions and find the row with your data.id. Filter with invoiceId, or use status=inprocess (that filter ignores startDate / endDate).

Do not send another POST to check status. Use GET. A new POST can charge again if the original Idempotency-Key is no longer cached.

What you chargeddata.status while you waitStop polling when
CardUsually already finalapproved, approved_with_error, declined
Bank (for example Bambora)queued — accepted, not sent to the bank yet. Then pending — sent to the bank, waiting on settlement (often business days).approved, declined, returned, bank_failed, submit_failed
Benji Payments void or refundapproved_void_pending (usually minutes). This status is not in status=inprocess — look up the transaction by id or invoiceId.approved_voided or void_failed (refunds: a completed or failed refund status)

GET ?status=inprocess returns queued, pending, pending_account_validation, and notified.

Retry the accounting writeback

The card can be charged while the writeback to QuickBooks, Xero, or Business Central fails. That is 200 with meta.effects.charge.status=succeeded and meta.effects.accounting.status=failed.

Keep data.id. When your accounting system is back, retry only the writeback — the payment method is never charged again and no receipt is sent:

curl -X POST https://api.benjipays.com/v2/transactions/66f1a2b3c4d5e6f789012345/retry-accounting \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Idempotency-Key: f6e5d4c3b2a1"

No body. Idempotency-Key is required, and the scope is the same organizations:transactions:create.

The response is always 200 with the current transaction in data and one effect:

meta.effects.accounting.statusWhat it means
succeededApplied. data.accountingPaymentId and data.paymentApplied are set.
failedAccounting is still unavailable. Retry later.
skippedNothing to apply. Read data.status: a voided status (for example approved_voided) means the charge was auto-voided after the first apply failed, so charge again instead of retrying. Otherwise the payment was already applied, or the apply is deferred until a bank payment completes.

RFC 7807 means the charge can never have accounting retried: it was declined, it has no invoice, or it is a void / refund / prepayment. A 404 means the id is unknown or belongs to another organization.

Amount, credit memos, and installments

The amount you charge is the open balance, or the amount you send. Open credit memos are not applied on this request.

If the invoice has an installment plan, you still choose the amount (or the full open balance). Charging does not close installment rows. Example: a $400 invoice with four $100 rows, charge $200 — about $200 remains on the invoice, and the four rows stay open.


Did this page help you?