> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gojinko.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Partner card and VCC payments

> Discover supported payment types, submit a customer card or VCC, and recover safely from payment and booking outcomes.

## When to use

Use this guide to integrate raw customer cards or virtual credit cards (VCCs) through the REST API, using the public API 0.18.x contract. Start with a trip containing the selected items and travelers; see [Flight booking](/guides/flight-booking).

Checkout discovery → typed payment submission → read the outcome → follow its recovery action.

## Discover what the tenant may use

Call checkout with your tenant-bound booking key. The examples use placeholders only.

```bash theme={null}
curl --request POST 'https://api.gojinko.com/v1/checkout' \
  --header 'X-API-Key: <TENANT_BOOKING_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{"trip_id":"<TRIP_ID>"}'
```

An illustrative response excerpt for a tenant enabled for VCC payments:

```json theme={null}
{
  "quoted_cart_id": 123,
  "expires_at": "2026-10-01T12:15:00Z",
  "accepted_payment_types": [
    {
      "type": "card",
      "card_kinds": ["vcc"],
      "requires": {
        "headers": ["Idempotency-Key"],
        "fields": ["trip_id", "quoted_cart_id", "payment.card_kind", "payment.number", "payment.exp_month", "payment.exp_year", "payment.cvc"]
      },
      "authentication": "unavailable"
    }
  ]
}
```

`accepted_payment_types` lists only presently usable combinations. Each entry has `type` (`spt` or `card`), optional `card_kinds` (`customer_card` or `vcc`), required headers and fields, and `authentication` (`customer_handoff` or `unavailable`). A customer-card entry can advertise `customer_handoff`; a VCC has no handoff.

Do not cache this discovery. Read it on each checkout and use the returned quote ID and deadline. Never send card data speculatively: submit a card only when discovery lists `card` and the intended card kind. An absent combination is unavailable, even if it worked before.

## Submit a card payment

Send a typed body to `POST /v1/agent_payment/submit`. Authenticate with a tenant-bound booking key (`jnk_…`), sent as `X-API-Key` or as `Authorization: Bearer`; OAuth/JWT bearer tokens are refused for typed bodies. `Idempotency-Key` is required.

This curl template sends the body through standard input. Keep real card data out of shell history and command arguments; production requests belong in your secure backend.

```bash theme={null}
curl --request POST 'https://api.gojinko.com/v1/agent_payment/submit' \
  --header 'X-API-Key: <TENANT_BOOKING_KEY>' \
  --header 'Idempotency-Key: <UNIQUE_ATTEMPT_KEY>' \
  --header 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "trip_id": "<TRIP_ID>",
  "quoted_cart_id": 123,
  "payment": {
    "type": "card",
    "card_kind": "vcc",
    "number": "<PAN>",
    "exp_month": 12,
    "exp_year": 2030,
    "cvc": "<CVC>"
  }
}
JSON
```

Replace `123` with the discovered `quoted_cart_id` and the expiration with the credential's expiration. Use `card_kind: "customer_card"` for a customer card. `billing_details` is optional inside `payment`.

For a shared payment token (SPT), the typed body is:

```json theme={null}
{
  "trip_id": "<TRIP_ID>",
  "quoted_cart_id": 123,
  "payment": {
    "type": "spt",
    "shared_payment_token": "<SHARED_PAYMENT_TOKEN>"
  }
}
```

The legacy flat SPT body is unchanged.

Use one idempotency key per intended attempt. **The first accepted request wins.** Replay the same accepted request with the same key to recover its outcome. The key is compared against the safe immutable fields (trip, quoted cart, payment type, card kind, amount and currency): changing one of those with the same key returns `409 idempotency_key_reused`. Card credentials are deliberately not part of that comparison, so a replay with the same key and different card details returns the first accepted attempt's outcome rather than a new attempt; send a new key for a new credential. A request rejected before acceptance (`400 credential_format_invalid`) does not consume the key — correct the request and replay it with the same key, unless the `Idempotency-Key` itself was rejected, in which case send a valid key.

A timeout is not permission to create a new attempt. Follow `recovery.action`. Send a replacement credential with a new key only when the outcome permits it, cleanup is complete, and the quote is still valid. Otherwise recheckout when instructed.

## Read the outcome

The typed submit response is a `PaymentAttemptOutcome`:

| Field                           | What to read                                                                                                                                      |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `payment_attempt_id`            | Save this identifier to read the same attempt again.                                                                                              |
| `payment_status`                | One of `not_started`, `pending`, `requires_authentication`, `authorized`, `captured`, `declined`, `cancelling`, `cancelled`, `expired`, `failed`. |
| `booking_status`, `booking_ref` | Booking progress and the booking reference when available. Authorization alone does not mean booked.                                              |
| `payment_error`                 | `{code, message}` or `null`. Branch on the code, not the message.                                                                                 |
| `recovery`                      | Exactly one `action`, plus `replacement_credential_required`.                                                                                     |
| `recovery_status`               | `automatic` or `manual_review`.                                                                                                                   |
| `quote_expires_at`              | The quote deadline, when returned.                                                                                                                |
| `retry_after_seconds`           | When to poll again, when returned.                                                                                                                |
| `authentication`                | Customer-card handoff `url` and `expires_at`, when returned.                                                                                      |
| `money`                         | Amounts and resolution details described below, when available.                                                                                   |

**200 means a known outcome, including a decline.** It does not mean the payment or booking succeeded. **202 means pending.** Wait for the `Retry-After` interval, then poll the attempt:

```bash theme={null}
curl 'https://api.gojinko.com/v1/trip/<TRIP_ID>/payment_attempts/<PAYMENT_ATTEMPT_ID>' \
  --header 'X-API-Key: <TENANT_BOOKING_KEY>'
```

`{trip_id}` path parameters accept either `trip_<n>` or `<n>` (for example, `trip_123` or `123`). Any other format returns 400 with the fixed message `invalid trip_id`.

Honor the returned polling interval (`Retry-After` / `retry_after_seconds`). Keep reading the same attempt while its action is `poll`, including during cancellation or an unknown provider outcome. Do not submit a second payment to resolve a pending booking.

## Customer authentication handoff

`authentication` is returned only for `customer_card` attempts in `requires_authentication`, with `recovery.action: "authenticate"`. Send the customer to the returned `authentication.url` unchanged before `authentication.expires_at`. Never parse or reconstruct it. The link points to Jinko's web app and has this shape:

```text theme={null}
/checkout/authenticate?payment_attempt_id=<pat_…>&t=<capability token>
```

The page completes 3DS on the existing PaymentIntent and shows the server-verified outcome. Keep polling `GET /v1/trip/{trip_id}/payment_attempts/{payment_attempt_id}` while the customer authenticates. VCC attempts never receive an authentication handoff.

After the customer returns, read the attempt. A browser return is not proof of authorization. Closing the browser before the deadline does not itself prove failure; poll. A verified authentication failure requires cleanup. Authentication still incomplete at the deadline becomes abandoned; poll cleanup, then recheckout when instructed.

## VCC policy

VCCs have no customer authentication handoff. If a VCC needs authentication, do not open a hosted payment flow or ask a traveler to authenticate it. Obtain a replacement credential that can complete without a handoff. Wait until the attempt permits replacement and cleanup is complete; then use a new key on a valid quote, or recheckout as directed.

Fulfillment never falls back to hosted payment for a VCC.

## Outcome codes and recovery

Always obey the returned recovery action. A code alone does not establish that cleanup has finished or the quote is still usable.

| `recovery.action` | Caller action                                                                                         |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| `poll`            | Wait and read the existing attempt.                                                                   |
| `authenticate`    | Follow the customer-card authentication URL.                                                          |
| `retry_same_key`  | Retry with the same key; correct pre-acceptance validation errors first.                              |
| `retry_new_key`   | Start the permitted new attempt; replace the credential if `replacement_credential_required` is true. |
| `recheckout`      | Call checkout again and use its fresh quote and discovery.                                            |
| `contact_support` | Give Jinko the attempt and request identifiers for reconciliation.                                    |
| `stop`            | Do not retry this operation.                                                                          |

The public code meanings and caller actions are:

| Code                           | Meaning and caller action                                                                                                                                                                                                             |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `credential_format_invalid`    | 400; fix the request, replay with the same key only if that key was valid and another field needed correction; if the `Idempotency-Key` itself was rejected, send a valid key                                                         |
| `payment_type_not_enabled`     | 403; stop                                                                                                                                                                                                                             |
| `payment_credential_invalid`   | credential unusable; correct before acceptance, replacement + new key after cleanup                                                                                                                                                   |
| `trip_owned_by_other_payment`  | 409; stop                                                                                                                                                                                                                             |
| `idempotency_key_reused`       | 409; stop, read the original attempt                                                                                                                                                                                                  |
| `attempt_in_progress`          | poll; competing attempts get 409                                                                                                                                                                                                      |
| `card_declined`                | replacement + new key on a valid quote once quiescent                                                                                                                                                                                 |
| `payment_credential_rejected`  | SPT/non-card rejection; same                                                                                                                                                                                                          |
| `authentication_required`      | customer card: follow `authentication.url`; VCC: no handoff, replace the credential once allowed                                                                                                                                      |
| `authentication_failed`        | poll cleanup, then replacement or recheckout                                                                                                                                                                                          |
| `authentication_abandoned`     | poll, then recheckout                                                                                                                                                                                                                 |
| `quote_expired`                | 410 before acceptance; recheckout                                                                                                                                                                                                     |
| `attempt_terminal`             | 409 before acceptance (`recovery.action: stop`). After acceptance it arrives in the normal payment outcome: follow `recovery.action` — `poll` while cleanup runs, then `retry_new_key` on a valid quote or `recheckout` if it expired |
| `payment_outcome_unknown`      | poll until `recovery.action` changes; use a new key only when it returns `retry_new_key`, or `recheckout` if the quote expired                                                                                                        |
| `fulfillment_dispatch_delayed` | poll; no second payment                                                                                                                                                                                                               |
| `reconciliation_required`      | contact support                                                                                                                                                                                                                       |
| `temporarily_unavailable`      | 503 before acceptance: retry same key; after: poll                                                                                                                                                                                    |

For `payment_credential_rejected`, “same” means replacement + new key on a valid quote once quiescent. Quiescent means the earlier attempt has finished cleanup and the returned recovery action permits another attempt.

## Failure table

Statuses can change during cleanup. Read the latest outcome and its recovery action before acting.

| Failure                                  | Status / code                                                                             | What the partner may do                                                   | What fulfillment does                                                                 |
| ---------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Decline                                  | `declined` / `card_declined`                                                              | Replace the credential and use a new key on a valid quote once quiescent. | Never books on a decline.                                                             |
| Customer 3DS needed                      | `requires_authentication` / `authentication_required`                                     | Follow `authentication.url`, then poll.                                   | Waits for authorization; no booking merely because the browser returned.              |
| VCC needs authentication                 | `authentication_required`; cleanup may be pending                                         | Poll cleanup; replace the credential only when allowed.                   | Does not book without authorization and never falls back to hosted payment.           |
| Verified 3DS failure                     | `authentication_failed`; cleanup may be pending                                           | Poll cleanup, then replace the credential or recheckout as directed.      | Cleans up the failed attempt; does not book it.                                       |
| Browser closed before deadline           | `requires_authentication` / `authentication_required` while still awaiting authentication | Poll; the browser closing is not a terminal result.                       | Waits for verified authorization or the deadline.                                     |
| Authentication incomplete at deadline    | `authentication_abandoned`; cleanup may be pending                                        | Poll cleanup, then recheckout.                                            | Cancels the incomplete attempt; does not book it.                                     |
| Authorization not admitted before expiry | `expired` / `quote_expired` after cleanup                                                 | Poll any cleanup, then recheckout when instructed.                        | Does not book against an expired quote; releases any authorization requiring cleanup. |
| Provider response lost                   | `pending` / `payment_outcome_unknown`                                                     | Poll the same attempt; never use a new key.                               | Resolves the provider outcome before deciding whether booking may proceed.            |
| Quote expired before attempt             | `not_started` / `quote_expired` (410)                                                     | Recheckout.                                                               | No payment is accepted and no booking starts.                                         |
| Enqueue failure                          | `fulfillment_dispatch_delayed`                                                            | Poll; do not send a second payment.                                       | Recovers dispatch of the existing fulfillment.                                        |
| Reconciliation failure                   | `reconciliation_required`                                                                 | Contact support with the attempt identifier.                              | Leaves resolution to operations; no duplicate payment or booking.                     |

## Money outcomes after booking

`money` reports `authorized`, `captured`, `released`, `refunded`, and `externally_reimbursed` as Money objects with `display`. Use `display` for presentation. Read `resolution_status` (`pending`, `resolved`, `manual_review`, `external_reimbursement_pending`, or `externally_reimbursed`) and `refund_reference` when available; missing amounts are not evidence of zero money movement.

* **Definitive booking failure before capture:** the authorization is voided and released; the payment becomes `cancelled` after cleanup.
* **Definitive booking failure after capture:** the captured amount is refunded; read `refunded` and `resolution_status: "resolved"`.
* **Partial delivery:** exactly the booked items are captured, with the remainder released; `booking_status` is `partial`.
* **Refund to an expired or cancelled card:** operations settles the refund outside the card network. `resolution_status` is `external_reimbursement_pending` until settled, then `externally_reimbursed`; read the `externally_reimbursed` amount and `refund_reference`. Do not treat a failed refund as reimbursement completed or start another payment.
* **Result cannot be verified:** `reconciliation_required` / `manual_review`; contact support. Jinko does not guess whether money moved or a booking succeeded.

## Security and data handling

Send credentials only from your secure backend over HTTPS. Raw card data must never pass through prompts, MCP, CLI arguments, logs, traces, analytics, support tickets, or stored request bodies. The examples contain placeholders, not usable card credentials.

Jinko keeps identifiers only for the payment flow, not raw PAN or CVC. Keep attempt, trip, quote, and request identifiers for recovery and support. Never include the card data when reporting an issue.

Jinko handles card data in transit. No storage does not mean “PCI-free”: account for Jinko’s payment processing and your integration in the applicable PCI assessment.

## Activation

Card kinds are disabled per tenant by default. Arrange enablement with Jinko, then confirm availability through checkout discovery. Enabling a tenant does not make every combination usable for every checkout.

MCP and CLI remain SPT-only. Use the typed REST contract for raw customer cards and VCCs. See [Errors & troubleshooting](/concepts/errors#payment-outcome-codes) for the payment code reference.
