# Take payments and refunds

> Record payments and refunds on a sales order, check what's still owed, pay from or move money to store credit, and split a payment across invoices with a payment link.

Web page: https://developer.yoprint.com/guides/payments/

**Read first:** [Invoices and payments](https://developer.yoprint.com/how-yoprint-works/invoices-and-payments.md), for how money works on an order, deposits and payment terms, the ways a customer pays, and store credit.

This guide records payments you already received, works out what's still owed, and refunds, splits or moves that money. It doesn't charge cards. Customers pay by card in the Customer Portal, and your team charges cards in the web app. The API can refund a card payment through the gateway, but its card charge operations are built for YoPrint's own payment screens: see [Cards and the API](#cards-and-the-api). Invoicing an order is a separate guide: see [Invoice an order](https://developer.yoprint.com/guides/invoices.md).

**Diagram:** Steps 1, 3, 4, 5 and 6 change money or its records. Step 2 only reads them.

- **1. Record a payment**: Cash, a check or a bank transfer you already received
- **2. Check the balance**: Deposit due, balance due, and what's been paid
- **3. List, change or delete**: Read back the order's payments, fix a reference, or remove one
- **4. Refund**: Give money back, recorded by hand or through the card gateway
- **5. Store credit**: Pay an order from credit, or move payments onto it
- **6. Split with a payment link**: Record how one payment is allocated across invoices

## 1. Record a payment

> **Caution: Every amount you send here is thousandths**
>
> Payment, refund and credit `amount`s are whole numbers of **thousandths** of the currency, not decimal pairs: `300000` is 300.00 and `12500` is 12.50. Send no decimal point. The order's totals that you read back, such as `order_balance_due`, are decimal pairs. See [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md#sending-amounts).

Get the payment method you were paid with, such as Cash or Bank Transfer. `payment_method_id` is optional, but without it the payment has no method. Each entry has its `name` and `id`:

`GET /v1/api/store/{team_slug}/setting/payment_method` [List payment methods](https://developer.yoprint.com/reference/payment-methods/v1-payment-method-index.md)

```json
{ "data": [{ "id": "XbLngLA87AC5t5", "name": "Bank Transfer", "default": false, "trashed": false }] }
```

Then record the payment:

`POST /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment` [Record a payment on a sales order](https://developer.yoprint.com/reference/payments/v2-sales-order-manual-payment-store.md)

```bash
curl https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/payment \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 75000,
    "date": "2026-11-03",
    "payment_method_id": "Kst6z3ULXLBrmN",
    "reference_id": "CHK-1001"
  }'
```

- `amount` is a whole number of **thousandths**, not a decimal pair: `75000` is 75.00. A number with decimals, such as `75000.5`, is refused with `422` `validation_failed`, and nothing is recorded. `error.fields.amount` says "The amount must be a whole number of thousandths of the currency unit, such as 12500 for 12.50." Its sign is ignored: `-75000` also records 75.00. See [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md).
- `date` (or `created_at`) is required. `reference_id` is your reference, such as a check number. `reference_description` is a note on the payment.
- The order must be published: a draft order refuses with 422 ("Payment Action Not Allowed - due to order internal state").
- No money moves, no receipt is emailed, and the payment is allocated to the order's invoices automatically.
- Fires `payment_received.any_amount` every time. It also fires `payment_received.deposit_amount` when the order's paid total now exactly equals the deposit, and `payment_received.full_amount` when nothing is left to pay. A payment that goes past the deposit, or overpays, fires neither of those two.

## 2. Check the deposit and the balance

`GET /v2/api/store/{team_slug}/v2/sales_order/{order_unique_key}` [Get a sales order or quote](https://developer.yoprint.com/reference/sales-orders/v2-order-show.md)

```bash
curl https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1"
```

- `order_balance_due` is what's left to pay: invoiced total minus paid. A decimal pair.
- `deposit_amount_due` is what's left of the deposit: 0 or less means the deposit is paid. A decimal pair.
- `paid_amount` is what's been paid so far. **Thousandths**, not a decimal pair.
- See [How the balance works](https://developer.yoprint.com/how-yoprint-works/invoices-and-payments.md#how-the-balance-works) and [Deposits and payment terms](https://developer.yoprint.com/how-yoprint-works/invoices-and-payments.md#deposits-and-payment-terms) for how each is worked out, and [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md) for reading `dp`.

**Example.** An order's lines are worth 600.00, and its terms ask for a 50% deposit, so the deposit is 300.00. Only the tees, 300.00 with no tax, are invoiced so far. After a 300.00 payment, `paid_amount` is `300000`, `deposit_amount_due` is 0 (the deposit is paid), and `order_balance_due` is 0 too: the other 300.00 isn't owed until it's invoiced.

## 3. List, change and delete payments

`GET /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment` [List the payments of a sales order](https://developer.yoprint.com/reference/payments/v2-sales-order-payment-index.md)

Returns every money record on the order at once, newest first, no pagination: payments, refunds and customer credit moves.

`GET /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment/{payment_reference}` [Get one payment of a sales order](https://developer.yoprint.com/reference/payments/v2-sales-order-payment-show.md)

`PUT /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment/{payment_reference}` [Update a recorded payment](https://developer.yoprint.com/reference/payments/v2-sales-order-manual-payment-update.md)

```json
{ "date": "2026-11-04", "reference_id": "CHK-1002" }
```

`DELETE /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment/{payment_reference}` [Delete a payment or refund](https://developer.yoprint.com/reference/payments/v2-sales-order-manual-payment-destroy.md)

- Update changes a payment's date, references and, on a recorded payment, its method. Delete removes a payment or refund record, and the answer has no data.
- Card payments can't be deleted here: refund them, or move their money to customer credit (see below).

**What you can change, and what's blocked**

| Field                                            | On update                                                                                                                                                         |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `date` or `created_at`                           | Required (one of them).                                                                                                                                           |
| `payment_method_id`                              | Only on a recorded payment (`ORDER_PAYMENT`). Send `null` to clear it. Ignored on other records.                                                                  |
| `reference_id`, `reference_description` (a note) | Yes. Send `null` to clear either.                                                                                                                                 |
| `amount`                                         | No. Send the record's own amount, or leave it out. A different amount is refused (`payment_amount_not_editable`): delete the payment and record it again instead. |

A Customer Portal payment, credit linked to one, or a record synced from an integration such as Shopify can't be changed or deleted here.

## 4. Refunds

### A recorded refund

`PUT /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment/{id}/refund` [Record a refund of a payment](https://developer.yoprint.com/reference/payments/v2-sales-order-manual-payment-refund.md)

```bash
curl -X PUT https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/payment/hAwWd7vPrqjuHD/refund \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25000,
    "date": "2026-11-05",
    "reference_description": "Returned two shirts"
  }'
```

- Records that you gave back money outside YoPrint. No money moves, and the refund can't be more than the payment's amount minus its own refunds and the credit already spent from it.
- `amount` is thousandths: `25000` is 25.00. A number with decimals is refused with `422`. Its sign is ignored, and the answer shows the refund as a negative amount (`-25000`).
- `{id}` in the path is the payment's `id`. For a card payment, use the card refund below instead: this one only records a refund and returns no money.
- The money comes first from the payment's own unallocated amount, then from unspent customer credit issued from it (recorded as `ORDER_CREDIT_RECOVERY` on the order), then from its invoices, newest first.
- Fires `payment.refund_issued`.

### A card refund through the gateway

`PUT /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment_gateway_refund/{id}` [Refund a card payment through the gateway](https://developer.yoprint.com/reference/payments/v2-sales-order-payment-gateway-refund.md)

```bash
curl -X PUT https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/payment_gateway_refund/JMQ642WFe4sANJ \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25000,
    "locking_token": "refund-1001-a"
  }'
```

- Refunds real money back to the card, through the gateway that took the payment. Send both `amount`, in thousandths (`25000` is 25.00), and a one-time `locking_token` of your own. Without `amount` the call fails with HTTP 500 before the gateway is called.
- Fires `payment.refund_issued`. If the provider later reports the refund failed, YoPrint gives the money back to the payment (`REFUND_REVERSAL`) instead of leaving it lost.

> **Caution: A dispute freezes the payment**
>
> While a card payment has an open dispute or review, it can't be refunded or moved to customer credit: YoPrint refuses with `customer_payment_disputed_funds_unavailable` or `invoice_payment_funding_withheld`. Wait for the provider's decision, then try again.

## 5. Store credit

### Pay from credit

`POST /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/credit/pay_with_customer_credit` [Pay an order from the customer's credit](https://developer.yoprint.com/reference/payments/v2-sales-order-credit-pay-with-customer-credit.md)

```json
{ "amount": 50000 }
```

Uses the customer's credit to pay part of a published order. `amount` is thousandths (`50000` is 50.00). It can't be 0 or more than the credit available, and a number with decimals is refused with `422`. Fires the same `payment_received.*` events, with the same exact-match rule, as recording a payment.

### Move payments to credit

Despite the operations' names, this isn't a card refund: it takes money already paid on the order and keeps it as the customer's store credit, to spend on another order. The order's paid amount goes down by that much.

`POST /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/credit/refund_to_customer_credit` [Move part of an order's payments to customer credit](https://developer.yoprint.com/reference/payments/v2-sales-order-credit-refund-to-customer-credit.md)

`PATCH /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/credit/refund_all_to_customer_credit` [Move all of an order's payments to customer credit](https://developer.yoprint.com/reference/payments/v2-sales-order-credit-refund-all-to-customer-credit.md)

```json
{ "amount": 25000, "reference_description": "Overpayment" }
```

- Both need the order published. The first moves part of what's paid on this order into the customer's credit, to use on another order. Its `amount` is thousandths (`25000` is 25.00). The second moves everything at once, deleting the order's payment links first, and takes no amount.
- Only money not yet allocated to an invoice can move with the first call. Payments are allocated when they're recorded, so delete the payment link first, or use the second call instead.
- Neither sends a webhook. A frozen payment (an open dispute or review) blocks both the same way as a card refund, above.

### Add or take out credit on a customer

`POST /v1/api/store/{team_slug}/customer/{hash_id}/credit` [Add or take out customer credit](https://developer.yoprint.com/reference/customer-credit/v1-customer-credit-store.md)

```bash
curl https://secure.yoprint.com/v1/api/store/your-team/customer/Rm4rRTDxipKY3n/credit \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "deposit",
    "amount": 50000,
    "payment_method_id": "6nz3Vu9sFR4dxM",
    "reference_id": "CHQ-1001"
  }'
```

- `action` is `deposit` or `withdraw`. A withdrawal can't be more than the credit available now (credit held by an open dispute isn't available).
- `amount` is thousandths: `50000` adds 50.00. A number with decimals is refused with `422`.
- It isn't idempotent: each call adds a new entry. List the credit history before you retry after a timeout.

### Read the credit history

`GET /v1/api/store/{team_slug}/customer/{hash_id}/credit` [List a customer's credit history](https://developer.yoprint.com/reference/customer-credit/v1-customer-credit-index.md)

Of the credit operations here, only adding or taking out credit by hand fires a `customer_credit` webhook (`customer_credit.deposited` or `customer_credit.withdrew`). Paying from credit fires the same `payment_received.*` events as any payment.

## 6. Split a payment across invoices with a payment link

A payment link records how much of an already-received payment went to which invoice. The web app calls it a payment allocation. It's **not** a page or a URL your customer opens, and it moves no money.

`GET /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment_link/get_create_template` [Get what a new payment link can allocate](https://developer.yoprint.com/reference/payment-links/v2-sales-order-payment-link-get-create-template.md)

Returns the order's payments that still have money to allocate, each with `transaction_id` and `unallocated_amount`, to build the next call from.

`POST /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment_link` [Create a payment link for a sales order](https://developer.yoprint.com/reference/payment-links/v2-sales-order-payment-link-create.md)

```bash
curl https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/payment_link \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "X-Yoprint-Decimal-Format: exact-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "note": "Split deposit",
    "unallocated_transactions": [
      {
        "action": "create",
        "transaction_id": "hAwWd7vPrqjuHD",
        "invoice_id": "QRbvVd2ZKcvSTB",
        "total": { "dp": 3, "value": 50000 }
      }
    ]
  }'
```

> **Caution: total.value is always thousandths**
>
> `total` looks like a decimal pair, but YoPrint reads `total.value` as thousandths whatever `dp` says. Send `{ "value": 50000, "dp": 3 }` for 50.00: `{ "value": 1250, "dp": 2 }` would be read as 1.250, not 12.50. See [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md#request-quirks).

Needs at least one entry, each naming a payment and an invoice of the same order.

`GET /v2/api/store/{team_slug}/v2/payment_link/{hash_id}` [Show one payment link with allocations](https://developer.yoprint.com/reference/payment-links/v2-payment-link-show.md)

Returns the link's own `total` (dp 4, the sum) and each allocation's `total` and `unallocated_amount` (dp 3).

`PUT /v2/api/store/{team_slug}/v2/payment_link/{hash_id}` [Update a payment link](https://developer.yoprint.com/reference/payment-links/v2-payment-link-update.md)

```json
{
  "note": "Deposit split across two invoices",
  "allocated_transactions": [
    { "action": "update", "id": "SAo2jTR8BZUnyc", "total": { "dp": 3, "value": 12500 } }
  ],
  "unallocated_transactions": [
    { "action": "create", "transaction_id": "tUq7cR8dTLKFPm", "invoice_id": "7ELS3Vmu6QfJxT", "total": { "dp": 3, "value": 5000 } }
  ]
}
```

**How update applies the three actions**

- `allocated_transactions` changes existing allocations by `id` (`update` or `delete`). `unallocated_transactions` creates new ones. Allocations you leave out stay as they are.
- Deletes run first, then updates, then creates, so one call can move money from one invoice to another.
- An `update` row's `total.dp` must be exactly 3, or it's refused with 422. A `create` row's `dp` isn't checked. Either way, `value` is read as thousandths.

`DELETE /v2/api/store/{team_slug}/v2/payment_link/{hash_id}` [Delete a payment link](https://developer.yoprint.com/reference/payment-links/v2-payment-link-delete.md)

Deletes the link and its allocations. The payments stay on the order, unallocated, and the invoices simply show less paid: no money moves.

## Cards and the API

| You want to                                                                  | Through the API                                                                                                      |
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Record a card payment you took outside YoPrint, such as on your own terminal | Yes. Record it as in [step 1](#1-record-a-payment), with a payment method such as Credit Card. No money moves.       |
| Refund a card payment that went through the team's gateway                   | Yes, with [a card refund through the gateway](#a-card-refund-through-the-gateway). Real money goes back to the card. |
| Charge a card                                                                | Not from your own server. See below.                                                                                 |
| Delete a card payment                                                        | No. Refund it, or move its money to customer credit.                                                                 |

The reference lists three card operations: Prepare a card payment, Charge a card through the payment gateway, and Start a Square Terminal checkout. They're marked **Internal** because they're built for YoPrint's own payment screens:

- Prepare and Charge need a card token or a confirmed payment from the gateway's own browser library (Stripe, Square, PayPal or Authorize.net), which only a card form in a web page produces.
- Start a Square Terminal checkout sends the charge to the shop's Square Terminal, where the customer pays on the device.

Don't build on them. They still work with any member's key, and they charge real money, so don't give a key to anyone you wouldn't trust with that. See [Roles and permissions](https://developer.yoprint.com/concepts/roles-and-permissions.md#operations-any-member-can-call).

`POST /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/payment/{payment_reference}` [Charge a card through the payment gateway](https://developer.yoprint.com/reference/payments/v2-sales-order-payment-gateway-execute.md)

A card payment a customer makes in the Customer Portal, or that your team takes in the web app, shows up in the order's payments like any other. Read it with List the payments of a sales order, and refund it through the gateway.

## Pitfalls

- **Recording a payment has no `operation_key`.** Unlike a shipment create, there's nothing to make a retry safe: a retried create after a timeout can record the payment twice. List the order's payments first.
- **A payment link entry can be silently skipped or capped.** One asking for more than the payment has left, or targeting an invoice with nothing left to pay, is skipped without an error. One asking for more than an invoice still owes, with enough left on the payment, is capped to what the invoice owes. The link can be created even though nothing inside it was saved: read the response.
- **"Note" is a different field depending on where you are.** A payment's, refund's or credit move's note is `reference_description`. A payment link's note is `note`.
- **Update works on a draft order, but the operations around it don't.** Recording a payment, deleting one, and both kinds of refund need the order published. A card refund checks that only after the payment provider has refunded the card.
- **A recorded refund's `{id}` must be a payment.** A refund or a credit move in its place fails with HTTP 500, not a clean error.
- **A recorded refund returns no money.** On a card payment, use Refund a card payment through the gateway, which does.
