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

# Accept card payments online

> Tokenize a shopper's card in the browser, charge it through the Vori Payments API, and issue full or partial refunds, in test mode first and then live.

Use the Vori Payments API to tokenize a shopper's card in the browser, charge it through the payment processor configured for the store, and issue full or partial refunds.

> [!NOTE]
>
> Online card payments are a beta feature. Contact Vori Support to enroll.

> [!WARNING]
>
> Never send raw card numbers, expiration dates, or CVV values to Vori or your own server. Tokenize the card in the shopper's browser and send only the one-time token to your server.

## Before you begin

You need:

* A Vori API key with access to the store and payments.
* The store ID.
* An e-commerce merchant account for live payments. Contact Vori Support if the store does not have one.
* A public tokenization key from the store's `ecommerce_credentials` property.

Use `mode: "test"` while developing. Test payments move no money. Switch both tokenization and API requests to `mode: "live"` only after the store's live e-commerce merchant account is ready.

## Get the store's public key

Retrieve the store:

```bash theme={null}
curl "https://api.vori.com/v1/stores/<store_id>" \
  -H "Authorization: Bearer sk_live_<secret>"
```

The response includes up to one public key for each mode:

```json theme={null}
{
  "id": "<store_id>",
  "ecommerce_credentials": [
    {
      "mode": "live",
      "public_key": "<live_public_key>"
    },
    {
      "mode": "test",
      "public_key": "<test_public_key>"
    }
  ]
}
```

The `live` credential is absent until the store has an e-commerce merchant account. A `test` credential can be used only with test tokenization and `mode: "test"` API requests.

## Tokenize the card in the browser

Load the Datacap JavaScript client that matches the payment mode:

* Live: `https://token.dcap.com/v1/client`
* Test: `https://token-cert.dcap.com/v1/client`

Add `data-token` attributes to the card inputs. Pass the store's matching public key, the form ID, and a callback to `DatacapWebToken.requestToken`.

```html theme={null}
<form id="payment-form">
  <label>
    Card number
    <input type="text" inputmode="numeric" data-token="card_number" autocomplete="cc-number" required>
  </label>

  <label>
    Expiration month
    <input type="text" inputmode="numeric" data-token="exp_month" autocomplete="cc-exp-month" maxlength="2" required>
  </label>

  <label>
    Expiration year
    <input type="text" inputmode="numeric" data-token="exp_year" autocomplete="cc-exp-year" maxlength="4" required>
  </label>

  <label>
    CVV
    <input type="text" inputmode="numeric" data-token="cvv" autocomplete="cc-csc" maxlength="4" required>
  </label>

  <button type="submit">Pay</button>
</form>

<script src="https://token-cert.dcap.com/v1/client"></script>
<script>
  const publicKey = "<test_public_key>";
  const form = document.getElementById("payment-form");

  form.addEventListener("submit", (event) => {
    event.preventDefault();

    DatacapWebToken.requestToken(publicKey, form.id, async (response) => {
      if (response.Error) {
        showPaymentError(response.Error);
        return;
      }

      await sendTokenToYourServer(response.Token);
    });
  });
</script>
```

The token is valid for one charge. Send it to your server immediately. Do not store it for later use.

## Create the payment

Your server sends the token to `POST /v1/payments`:

```bash theme={null}
curl -X POST "https://api.vori.com/v1/payments" \
  -H "Authorization: Bearer sk_live_<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "<one_time_token>",
    "amount": "42.17",
    "idempotency_key": "checkout-100234-payment-1",
    "store_id": "<store_id>",
    "mode": "test",
    "metadata": {
      "order_id": "100234"
    }
  }'
```

`amount` is in dollars and must be between `0.01` and `99999.99`.

Create a unique `idempotency_key` for each intended charge. If the request times out or its result is unclear, retry the same request with the same key and token. While the processor is still working, a retry can return HTTP `409 Conflict` with `error_code: "payment_in_progress"` and the payment ID in `error_details.payment_id`. Continue retrying with the same key and token until Vori returns the captured payment or a definitive decline. Do not treat the first retry as the final outcome.

A successful response includes the payment ID, status, payment mode, processor, and card details that are safe to display:

```json theme={null}
{
  "id": "pay_<payment_id>",
  "amount": "42.17",
  "idempotency_key": "checkout-100234-payment-1",
  "store_id": "<store_id>",
  "metadata": {
    "order_id": "100234"
  },
  "status": "captured",
  "mode": "test",
  "processor": "datacap",
  "payment_method": {
    "type": "card",
    "last4": "1111",
    "brand": "visa"
  }
}
```

Store the Vori payment `id` with your order. You need it to retrieve or refund the payment.

> [!CAUTION]
>
> A gateway timeout does not prove the card was not charged. Retry with the same `idempotency_key` and token. Do not create a new key unless Vori confirms the original payment was declined.

## Handle payment results

* `captured`: The charge succeeded.
* `failed`: The processor declined the charge. This status appears when you retrieve the payment, not in the create response.

A declined `POST /v1/payments` request returns HTTP `402 Payment Required` with `error_code: "card_declined"`. The `error_details.payment_id` property identifies the payment, and `error_details.processor_message` contains the processor's explanation when available:

```json theme={null}
{
  "error_code": "card_declined",
  "error_details": {
    "payment_id": "pay_<payment_id>",
    "processor_message": "<processor_message>"
  }
}
```

Treat this HTTP error as a declined payment. If you subsequently retrieve `error_details.payment_id`, the payment response has `status: "failed"`.

Ask the shopper for another payment method and tokenize it again. A new charge requires a new token and a new idempotency key.

Retrieve a payment at any time:

```bash theme={null}
curl "https://api.vori.com/v1/payments/<payment_id>" \
  -H "Authorization: Bearer sk_live_<secret>"
```

List payments in one mode:

```bash theme={null}
curl -G "https://api.vori.com/v1/payments" \
  -H "Authorization: Bearer sk_live_<secret>" \
  --data-urlencode 'mode=test' \
  --data-urlencode 'store_id=<store_id>'
```

A list never mixes live and test payments.

## Refund a payment

Create a full or partial refund with `POST /v1/refunds`:

```bash theme={null}
curl -X POST "https://api.vori.com/v1/refunds" \
  -H "Authorization: Bearer sk_live_<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_id": "pay_<payment_id>",
    "amount": "10.00",
    "idempotency_key": "checkout-100234-refund-1",
    "store_id": "<store_id>",
    "mode": "test",
    "metadata": {
      "reason": "item_unavailable"
    }
  }'
```

The store and mode must match the original payment. The total refunded across all refunds cannot exceed the payment amount.

Vori chooses how to return the money:

* `void`: Cancels a full charge before it settles, when the processor still permits it.
* `return`: Sends money back to the card. Partial refunds always use this type.

Use a unique refund idempotency key for each intended refund. If the result is unclear, retry with the same key. Do not create a new key while the original refund may still be processing.

Retrieve a refund:

```bash theme={null}
curl "https://api.vori.com/v1/refunds/<refund_id>" \
  -H "Authorization: Bearer sk_live_<secret>"
```

List refunds for a payment:

```bash theme={null}
curl -G "https://api.vori.com/v1/refunds" \
  -H "Authorization: Bearer sk_live_<secret>" \
  --data-urlencode 'mode=test' \
  --data-urlencode 'payment_id=pay_<payment_id>'
```

## Go live safely

**1. Confirm the live credential**

Retrieve the store and verify `ecommerce_credentials` contains a `live` public key. If it is missing, contact Vori Support.

**2. Load the production tokenizer**

Change the Datacap script URL to `https://token.dcap.com/v1/client` and use the store's `live` public key.

**3. Use live mode on the server**

Send `mode: "live"` for payments and refunds. Never mix a test token or credential with a live API request.

**4. Verify one controlled payment**

Run a small live charge, confirm its `captured` status, and refund it before opening checkout to shoppers.

## Complete API reference

Download the [OpenAPI specification](https://api.vori.com/openapi.json) for every request property, response property, filter, and error shape.


## Related topics

- [Accept EBT Payments on the POS](https://help.vori.com/point-of-sale-pos/ebt-electronic-benefit-transfer/accept-ebt-payments-on-the-pos.md)
- [Accept and Redeem Gift Certificates on the POS](https://help.vori.com/customer-marketing/gift-cards/accept-and-redeem-gift-certificates-on-the-pos.md)
- [Process Offline Payments in Vori POS](https://help.vori.com/point-of-sale-pos/basic-cashier-functions/process-offline-payments-in-vori-pos.md)
