Confirm payment results

Report paid, failed, and pending outcomes safely and idempotently.

Resolve a payment session only from an authoritative provider result. Authenticate Store API requests with the installation token for the app that owns the session.

Payment-session endpoints#

Purpose

Request

Required scope

Read a payment session assigned to your app

GET /api/2025-01/payment-sessions/{session_id}

read_payment_sessions

Report a pending or final result

POST /api/2025-01/payment-sessions/{session_id}/resolve

write_payment_sessions

Every resolution echoes the session's exact major-unit amount and ISO currency_code. Tringify rejects an invalid currency, excess precision, or any value that differs from the durable session.

Field

Type

Required

Description

status

string

Yes

pending, paid, or failed.

amount

string

Yes

The session's exact amount as a decimal string.

currency_code

string

Yes

The session's ISO currency code.

provider_transaction_id

string

For paid

The provider's stable identifier for the captured payment.

failure_code

string

No

For failed: a stable, non-sensitive reason code.

failure_message

string

No

For failed: a safe explanation.

Resolve a successful payment#

resolve-paid.json json
{
  "amount": "1499.00",
  "currency_code": "USD",
  "provider_transaction_id": "provider_payment_…",
  "status": "paid"
}

A paid result requires the provider's stable transaction identifier. Tringify atomically records the captured payment and order transaction; the identifier is retained for reconciliation and original-payment refunds.

Resolve a failed payment#

resolve-failed.json json
{
  "amount": "1499.00",
  "currency_code": "USD",
  "failure_code": "PAYMENT_DECLINED",
  "failure_message": "The provider declined the payment.",
  "status": "failed"
}

Pending and terminal results#

Use pending while the provider payment object can still accept or complete another attempt. An attempt-level failure is not a terminal session result when the same provider order remains retryable. Reconcile the provider's complete payment object before resolving. Repeating the same result is idempotent. paid is immutable and cannot be replaced by failed; an authoritative late paid result may promote a session previously reported as failed, expired, or cancelled. Stale non-paid results after expiry or cancellation are acknowledged without changing the session. If Tringify does not receive your charge response before you have resolved the session, it fails the session and the shopper may start a replacement attempt with a new session_id; see Accept a payment. Keep reconciling the first provider payment and resolve it against its own session_id; never report one provider payment on a different session.

Expiry and late settlement#

expires_at ends the shopper's attempt in checkout, not the provider's ability to settle it. Do not assume a fixed limit: honor the expires_at in each charge request. Without a checkout stock hold it is 30 minutes after the charge request; when the checkout holds stock, it is the end of that hold, which follows the store's configured checkout duration and can be up to 120 minutes away. After it passes, Tringify marks an unresolved session expired and checkout stops waiting for it; another attempt gets a new session_id. Where the provider allows it, make the provider payment stop accepting payment at expires_at. If the provider still captures it later, resolve the session as paid: Tringify accepts the late result and records the money. When the order no longer needs it, because another attempt paid it, it was cancelled, or its stock could not be held again, Tringify refunds that payment through your refund endpoint. Late pending and failed results for an expired session are acknowledged without change. Your app cannot be uninstalled for 7 days after a session's last activity, so it can still report these late captures.

Resolve errors#

HTTP status

Code

When

400

PAYMENT_SESSION_STATUS_INVALID

status is not pending, paid, or failed.

400

INVALID_INPUT

paid without provider_transaction_id.

400

PAYMENT_RESOLVE_CARD_DATA

The body carries card-like data.

403

PAYMENT_SESSION_APP_TOKEN_REQUIRED

The request does not use the installation token of the app assigned to the session.

403

PAYMENT_SESSION_WRONG_APP

The session belongs to another app.

404

PAYMENT_SESSION_NOT_FOUND

There is no such session in this store.

409

PAYMENT_SESSION_AMOUNT_MISMATCH

amount or currency_code differs from the session.

409

PAYMENT_SESSION_ALREADY_RESOLVED

failed for a session that is already paid.

A 2xx response returns the session. Repeating the current result, a stale pending or failed result for a failed, expired, or cancelled session, and pending for a paid session are accepted without change. A different provider_transaction_id reported as paid on a session that is already paid is recorded as a duplicate capture and refunded through your refund endpoint. Retry a 5xx response with the same body.

Receive a refund request#

Tringify sends a signed request to the refund endpoint pinned to the declaration that created the payment. refund_id is the idempotency key. The exact amount and currency come from Tringify's durable refund record and may represent a full or partial refund.

refund-request.json json
{
  "refund_id": "refund_…",
  "session_id": "session_…",
  "store_id": "store_…",
  "order_id": "order_…",
  "provider_transaction_id": "provider_payment_…",
  "amount": "499.00",
  "currency_code": "USD",
  "reason": "Customer return"
}
refund-response.json json
{
  "success": true,
  "provider_refund_id": "provider_refund_…",
  "status": "refunded"
}

Resolve an asynchronous refund#

Return pending when the provider accepted the refund without a terminal result. After provider reconciliation, call POST /api/2025-01/payments/refunds/{refund_id}/resolve with write_payment_sessions. The app and original payment ownership are verified before the transition is accepted.

Field

Type

Required

Description

status

string

Yes

pending, refunded, or failed.

provider_refund_id

string

No

The provider's identifier for the refund.

failure_code

string

No

For failed: a stable, non-sensitive reason code, recorded with the refund.

failure_message

string

No

For failed: a safe explanation, recorded with the refund.

resolve-refund.json json
{
  "provider_refund_id": "provider_refund_…",
  "status": "refunded"
}
resolve-refund-failed.json json
{
  "failure_code": "INSUFFICIENT_BALANCE",
  "failure_message": "The provider account balance cannot cover this refund.",
  "provider_refund_id": "provider_refund_…",
  "status": "failed"
}

A refund may transition from pending to refunded or failed. Repeating the same terminal result is idempotent. A late verified success may replace failed; a completed refunded result is immutable. Report verified late success even if another refund has since completed: Tringify records the money movement and flags discrepancies for the merchant without repeating inventory returns.

HTTP status

Code

When

400

STORE_API_INVALID_REQUEST_BODY

The body is not a JSON object of the fields above, has an unknown field, or includes error_code or error_message. Use failure_code and failure_message.

400

INVALID_INPUT

status is not pending, refunded, or failed, or the refund is not a refund to the original payment.

403

FORBIDDEN

The refunded payment was not collected by your app.

404

NOT_FOUND

There is no such refund in this store.

409

CONFLICT

The transition is not allowed: a refunded refund changing to anything else, a failed refund changing to pending, or a refund Tringify has not yet sent to your app.

A 2xx response means the result was recorded, or that it repeats the refund's current final result.

Refund retries and deadlines#

Tringify repeats a refund request, with the same refund_id, amount, and currency, when it gets no usable answer: a timeout, a transport failure, a refused redirect, a status of 400 or above, an unreadable body, or a successful response without a valid status. It sends at most 10 requests; the waits between them are 2, 4, 8, 16, 32, 64, 128, 256, 256 minutes, about 13 hours in all. A response with success: false and no status counts as failed. Once you answer refunded or failed, the result is final, apart from a later verified success. Once you answer pending, Tringify sends no more requests; resolve the refund through the Store API when the provider reaches a final result.

If all 10 requests go unanswered, the refund stays pending, marked as needing reconciliation, and its amount stays reserved; Tringify does not send it again. Look the refund up at the provider by refund_id and resolve it as refunded or failed. A pending refund has no resolution deadline, but the merchant cannot uninstall your app while one is open. Never answer failed for a refund whose outcome you do not know: answer pending and reconcile.