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-keywithorganizations:transactions:create - A required
Idempotency-Keyheader — 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
| Field | Required | Description |
|---|---|---|
invoiceId | yes | Invoice id from GET /v2/invoices. |
paymentMethodId | yes | Stored payment method for that invoice's customer. Must be a 24-character hex id. |
amount | no | Amount 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. |
sendReceipt | no | true (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 charged | data.status while you wait | Stop polling when |
|---|---|---|
| Card | Usually already final | approved, 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 refund | approved_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.status | What it means |
|---|---|
succeeded | Applied. data.accountingPaymentId and data.paymentApplied are set. |
failed | Accounting is still unavailable. Retry later. |
skipped | Nothing 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.
Updated 2 days ago
