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

# Payments API Guide

> Integration guide for Payments

<Warning>Some operations in this guide have Discovery handlers but no public gateway route yet: `POST /payments/{id}/support-documents`. Check the availability notice on each API reference page before using them.</Warning>

<Note>Start with [shared fields](/concepts/shared-fields), then the [Mexico](/countries/mexico) or [Colombia](/countries/colombia) guide. The CFDI, RFC, SAT catalog, and payment-complement examples below describe Mexico; they are not universal country requirements.</Note>

Process, track, and manage payments with automatic invoice generation and Mexican tax compliance. The Payments API handles payment registration, processing, refunds, and automated CFDI creation.

## Overview

The Payments API provides comprehensive payment management with flexible automation options. Register payments manually, create payment requests, process refunds, and automatically generate compliant invoices.

## Key Features

* **Payment Registration** - Record payments with optional invoice automation
* **Payment Requests** - Create shareable payment links
* **Client Search** - Find and update existing clients to prevent duplicates
* **Payment Splitting** - Split payments between master and connect teams (marketplace)
* **Refund Processing** - Full or partial refund support
* **Invoice Automation** - Automatic PUE/PPD invoice creation
* **PPD Complement Linking** - Link payments to existing PPD invoices for automatic complement generation
* **Multiple Processors** - Support for various payment gateways
* **Status Tracking** - Real-time payment status updates
* **Idempotency** - Prevent duplicate payment processing

## Endpoints

### List Payments

```http theme={null}
GET /payments
```

Retrieve a paginated list of payments with powerful filtering capabilities.

**Query Parameters:**

| Parameter | Type | Description |
| - | - | - |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `next` | string | Pagination cursor for next page |
| `team` | string | gigstack Connect: Target team ID |
| `status` | string | Filter by payment status (`requires_payment_method`, `succeeded`, `canceled`) |
| `currency` | string | Filter by currency code (e.g., `MXN`, `USD`) |
| `client_id` | string | Filter by client ID |
| `email` | string | Filter by client email address |
| `tax_id` | string | Filter by client tax ID (RFC) |
| `client_name` | string | Filter by client name |
| `metadata.{key}` | string | Filter by metadata field (e.g., `metadata.order_id=ORD-123`) |
| `created` | object | Filter by creation date (e.g., `created={gte:1700000000000}`) |
| `order_by` | string | Field to sort by (`timestamp`, `amount`) |
| `sort` | string | Sort direction (`asc`, `desc`) |

**Metadata Filtering:**

You can filter payments by any metadata key using dot notation or underscore notation:

```bash theme={null}
# Dot notation
GET /payments?metadata.order_id=ORD-12345

# Underscore notation (alternative)
GET /payments?metadata_order_id=ORD-12345
```

**Example Request:**

```bash theme={null}
curl -X GET "https://api.gigstack.io/v2/payments?limit=20" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

**Example with Filters:**

```bash theme={null}
# Filter by client email
curl -X GET "https://api.gigstack.io/v2/payments?email=cliente@ejemplo.com" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Filter by tax ID (RFC)
curl -X GET "https://api.gigstack.io/v2/payments?tax_id=XAXX010101000" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Filter by metadata
curl -X GET "https://api.gigstack.io/v2/payments?metadata.order_id=ORD-12345" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Multiple filters
curl -X GET "https://api.gigstack.io/v2/payments?status=succeeded&currency=MXN&metadata.project_id=PROJ-001" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

**Example Response:**

```json theme={null}
{
    "message": "Payments retrieved successfully",
    "data": [
        {
            "id": "payment_1234567890",
            "client": {
                "id": "client_1234567890",
                "name": "Juan Pérez García",
                "tax_id": "PEGJ800101ABC"
            },
            "status": "succeeded",
            "total": 1160.0,
            "subtotal": 1000.0,
            "taxes": 160.0,
            "currency": "MXN",
            "payment_form": "03",
            "invoices": ["invoice_1234567890"],
            "created_at": 1677651234,
            "succeeded_at": 1677651234,
            "payment_processor": "stripe",
            "short_url": "https://gigstack.xyz/Xk3mP9"
        }
    ],
    "has_more": false,
    "total_results": 1
}
```

### Search Payments

```http theme={null}
GET /payments/search
```

Full-text search across payments using Typesense. This endpoint provides fast, typo-tolerant search capabilities.

**Query Parameters:**

| Parameter | Type | Description |
| - | - | - |
| `query` | string (required) | Search query (searches across client name, email, payment ID, description, metadata) |
| `limit` | integer (1-100) | Number of results per page (default: 10) |
| `page` | integer | Page number for pagination (default: 1) |
| `fields` | string | Comma-separated list of fields to search in |
| `status` | string | Filter by payment status |
| `currency` | string | Filter by currency code |
| `client_id` | string | Filter by client ID |

**Example Request:**

```bash theme={null}
# Basic search
curl -X GET "https://api.gigstack.io/v2/payments/search?query=juan" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Search with filters
curl -X GET "https://api.gigstack.io/v2/payments/search?query=consulting&status=succeeded&currency=MXN" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

**Example Response:**

```json theme={null}
{
    "message": "Payments searched successfully",
    "data": [
        {
            "id": "payment_1234567890",
            "client": {
                "id": "client_1234567890",
                "name": "Juan Pérez García"
            },
            "status": "succeeded",
            "total": 1160.0
        }
    ],
    "found": 15,
    "page": 1,
    "per_page": 10,
    "success": true
}
```

### Register Payment

```http theme={null}
POST /payments/register
```

Register a payment that has already been received. This endpoint marks the payment as 'succeeded' immediately and is used for recording payments that have already been completed through external means (bank transfers, cash, etc.).

**Key Features:**

* Payment is marked as 'succeeded' immediately
* Requires `payment_form` field (Mexican SAT payment form code)
* Optional invoice automation
* Client search to prevent duplicate client records
* Used for payments already received

**Automation Types:**

* `pue_invoice` - Create PUE (Pago en Una sola Exhibición) invoice immediately
* `none` - No automation, register payment only

**Request Body:**

```json theme={null}
{
    "client": {
        "id": "client_1234567890"
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "exchange_rate": 1.0,
    "payment_form": "03",
    "items": [
        {
            "id": "service_1234567890",
            "quantity": 2,
            "unit_price": 1000.0
        }
    ],
    "idempotency_key": "payment-register-12345",
    "date": 1677651234000,
    "metadata": {
        "order_id": "ORD-12345",
        "customer_reference": "REF-789"
    }
}
```

**Optional Fields:**

* `idempotency_key` (string) - Stable key for one business payment. A repeated registration returns `400 resource_conflict` with the existing payment ID in the error details; retrieve and reconcile that payment. Keep the same key on retries. See [the tested payment recipe](/recipes/payment#prevent-a-second-payment-record).
* `date` (number) - Unix timestamp (in milliseconds) for when the payment was received. Must be in the past. Defaults to current time if not provided.
* `exchange_rate` (number) - Exchange rate for currency conversion. If not provided, the rate from the payment date (or current date if no date specified) will be fetched automatically from our rates collection.
* `ppd_invoice_id` (string) - UUID of an existing PPD invoice to link this payment to. When provided, a payment complement (complemento de pago) will be automatically generated and linked to the PPD invoice. The referenced invoice must have `payment_method='PPD'`, `status='valid'` and `invoice_type='I'` — a payment complement only ever settles an income CFDI, never an egress one.

**Payment Form Codes:**

* `01` - Cash
* `02` - Check
* `03` - Electronic transfer
* `04` - Credit card
* `05` - Electronic money
* `06` - Digital money
* `99` - To be defined

**Client Search Feature:**

The `search` parameter enables upsert-like behavior to find existing clients before creating payments, helping to avoid duplicate client records:

* `on_key` (string, required) - Field to search on (e.g., 'tax\_id', 'email', 'name')
* `on_value` (string, required) - Value to match against the specified field
* `update` (boolean, optional) - If true and a match is found, update the existing client with the provided data. If false, return the existing client without modifications. Default: false

**Search Behavior:**

* If a single client matches the search criteria, that client is used for the payment
* If `update: true`, the matched client is updated with any new data provided in the request
* If multiple clients match the search criteria, a 409 Conflict error is returned to prevent ambiguity
* If no client matches and client data is provided, a new client is created automatically

**Example with Client Search:**

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {
      "search": {
        "on_key": "tax_id",
        "on_value": "PEGJ800101ABC",
        "update": false
      },
      "name": "Juan Pérez García",
      "email": "juan.perez@ejemplo.com",
      "tax_id": "PEGJ800101ABC",
      "tax_system": "601"
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [
      {
        "description": "Professional services",
        "quantity": 1,
        "unit_price": 5000.00,
        "product_key": "80141503",
        "unit_key": "E48",
        "taxes": [
          {
            "type": "IVA",
            "rate": 0.16
          }
        ]
      }
    ]
  }'
```

### Request Payment

```http theme={null}
POST /payments/request
```

Create a payment request that generates a shareable payment link. This endpoint creates a payment in 'requires\_payment\_method' status and provides customers with various payment options to complete the transaction.

**Key Features:**

* Payment is created with 'requires\_payment\_method' status
* Generates shareable payment link
* Supports multiple payment methods
* Optional email notifications
* Used for requesting future payments
* Optionally returns the payer to your site after paying (`success_url`)

**Payment Methods:**

* `card` - Credit/debit card payments *(requires Stripe integration)*
* `bank` - Mexican bank transfer (SPEI)
* `oxxo` - OXXO convenience store payments *(requires Stripe integration)*
* `stripe-spei` - Customer balance payments *(requires Stripe integration)*

The selected processor determines which methods are accepted. The default processor
is `stripe`; other supported processors have their own integrations and method lists.
Use the [request-payment contract](/reference/createPaymentsRequest) for the current
processor/method combinations. In particular, a payment method being valid in the
shared enum does not make it valid for every processor.

**Request Body:**

```json theme={null}
{
    "client": {
        "id": "client_1234567890"
    },
    "currency": "MXN",
    "items": [
        {
            "id": "service_1234567890",
            "quantity": 1
        }
    ],
    "automation_type": "pue_invoice",
    "allowed_payment_methods": ["card", "bank", "oxxo"],
    "send_email": true,
    "emails": ["client@example.com"],
    "idempotency_key": "payment-request-12345",
    "success_url": "https://tienda.com/pedido/1234/gracias",
    "metadata": {
        "invoice_number": "INV-2024-001"
    }
}
```

### Returning the payer to your site (`success_url`)

By default the hosted payment page is where the journey ends. For a shop
checkout that is a dead end: the customer pays and is left on a page with no
order reference and no way back, which reads as a failed purchase and leads to
duplicate payments.

Set `success_url` to your order confirmation page and gigstack sends them back:

```json theme={null}
{
    "success_url": "https://tienda.com/pedido/1234/gracias"
}
```

The payer sees the destination host and is redirected a few seconds after the
payment is confirmed, and can also return immediately with a button. For
asynchronous methods (SPEI, OXXO) confirmation may arrive after the payer has
closed the page, so treat your webhook — not this redirect — as the source of
truth for whether a payment succeeded.

The value is validated on write. A `400` is returned unless the URL:

* uses `https`
* carries no credentials (`https://user:pass@host`)
* contains no whitespace or control characters
* resolves to a fully qualified, publicly reachable host — loopback, private
  and link-local addresses are rejected
* is at most 2048 characters

It is returned as `success_url` on payment reads, and is accepted only on
`POST /payments/request`. `/payments/register` records payments that already
settled, where there is no payer to redirect.

**Example Request:**

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/request \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {"id": "client_1234567890"},
    "currency": "MXN",
    "items": [
      {
        "description": "Consulting services",
        "quantity": 1,
        "unit_price": 10000.00,
        "product_key": "80141503",
        "unit_key": "E48",
        "taxes": [{"type": "IVA", "rate": 0.16}]
      }
    ],
    "automation_type": "pue_invoice",
    "allowed_payment_methods": ["card", "bank"],
    "send_email": true,
    "emails": ["client@example.com"]
  }'
```

**Response with Payment Link:**

```json theme={null}
{
    "message": "Payment request created successfully",
    "data": {
        "id": "payment_1234567890",
        "short_url": "https://gigstack.xyz/Xk3mP9",
        "status": "requires_payment_method",
        "total": 11600.0
    }
}
```

### Get Payment

```http theme={null}
GET /payments/{id}
```

Retrieve a specific payment by ID.

**Example Request:**

```bash theme={null}
curl -X GET https://api.gigstack.io/v2/payments/payment_1234567890 \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Mark Payment as Paid

```http theme={null}
POST /payments/{id}/paid
```

Mark a pending payment as paid.

**Example Request:**

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/payment_1234567890/paid \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Cancel Payment

```http theme={null}
DELETE /payments/{id}
```

Cancel a payment request.

**Example Request:**

```bash theme={null}
curl -X DELETE https://api.gigstack.io/v2/payments/payment_1234567890 \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Refund Payment

```http theme={null}
POST /payments/{id}/refund
```

Choose whether you are **recording money already returned outside gigstack** or asking
an eligible Stripe payment to be refunded. The default only records the refund.
The endpoint does not directly cancel an invoice or create a credit note. Recording
a refund can trigger published refund Journeys and the team’s automatic refund
handling, which may act on associated fiscal documents. Check those settings before
deciding whether a separate fiscal action is needed.

| Field | Required | Meaning |
| - | - | - |
| `reason` | Yes | Reason for this refund |
| `amount` | Yes | Amount for this refund in the payment's currency units; at least `0.01` |
| `external_processor_refund` | No | `false` (default) records only; `true` requests a Stripe refund and requires a linked payment intent |

Do not send `items`: it is not an accepted input. The payment must be `succeeded`.
The cumulative refunds cannot exceed the payment amount. Use a test key and a payment
created in that mode for a test run; substituting a live payment ID is not a test.
The original payment’s `automation_type: "none"` does not disable refund Journeys or
team automatic refund handling. Test mode can still run published test Journeys and
deliver processor webhooks or team notifications. Before testing, check the account’s
refund settings, webhook destinations and recipients; see the
[refund walkthrough](/recipes/refund#before-testing-check-refund-automations).

#### 1. Read the payment and its prior refunds

Use `GIGSTACK_BASE_URL` and `GIGSTACK_API_KEY` from the [quickstart](/quickstart), and
set `PAYMENT_ID` to the intended payment. The examples require Bash, `curl`, and `jq`.

```bash theme={null}
curl --fail-with-body "$GIGSTACK_BASE_URL/payments/$PAYMENT_ID" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" > payment-before-refund.json

jq '.data | {id, status, currency, total, total_refunded, refunds, livemode}' \
  payment-before-refund.json
```

**Check the units:** `data.total` is in currency units, but the current
`data.total_refunded` is in **minor units** (100 minor units per currency unit in this
handler). Each `refunds[].total` is in currency units. For a payment of `1160` with
`total_refunded: 50000`, the already-refunded amount is `500` and the remaining amount
is `660`. Do not subtract `total_refunded` directly from `total`.

#### 2. Record an external refund

Run this only after your bank transfer or other external refund has completed. It
records MXN 100 returned against a Mexican-peso example payment; it moves no money:

```bash theme={null}
curl --fail-with-body "$GIGSTACK_BASE_URL/payments/$PAYMENT_ID/refund" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"reason":"Returned MXN 100 by bank transfer","amount":100,"external_processor_refund":false}' \
  > refund.json

jq '.data | {refund, payment}' refund.json
```

**Expected contract:** HTTP `200`, a new `data.refund.id`,
`data.refund.total: 100`, and `data.refund.external_processor_refund: false`.
The nested `data.payment.amount` and `data.payment.total_refunded` in this response
are both in minor units. An additional staging check exercised this record-only
path with a refund of MXN 116 against a fresh MXN 1,160 payment and verified the
returned refund and cumulative amount by reading the payment again. The MXN 100
variation shown here is source-checked; the [refund walkthrough](/recipes/refund)
contains the exercised amount and [test evidence](/verification#additional-record-only-refund-check).

#### 3. Request a Stripe refund instead

This is an **alternative** to step 2 for a payment with a linked Stripe payment intent.
Sending it after step 2 would create another refund. Choose the amount still owed:

```bash theme={null}
curl --fail-with-body "$GIGSTACK_BASE_URL/payments/$PAYMENT_ID/refund" \
  -H "Authorization: Bearer $GIGSTACK_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"reason":"Customer return","amount":100,"external_processor_refund":true}' \
  > refund.json
```

A manually registered cash or bank payment has no Stripe charge to refund and is
rejected when this flag is `true`. Do not infer support for other payment processors
from their availability for checkout. A successful response records the refund after
the Stripe call returns; verify the processor's refund status before promising the
customer that the money has reached their account.

#### 4. Reconcile the result

Read `GET /payments/{id}` again and find the returned refund ID in `data.refunds`.
Compare the increment in `total_refunded` with the requested amount multiplied by 100.
For an external-processor refund, also reconcile the underlying Stripe payment.
A later Stripe webhook can set `partial_refunded` or `refunded` and append a processor
refund entry alongside the API-created entry. Do not count those entries as separate
refunds or sum them to infer money returned; compare the cumulative amount with Stripe.

**There is no refund idempotency key in this API.** Do not blindly repeat the POST
following a timeout or `500`: Stripe may have accepted the request before the local
record was saved. Compare the before/after refund list and the processor record;
if the outcome remains unknown, ask support to reconcile it before sending another
refund. Serialise refund requests for the same payment in your application.

### Support Documents

```http theme={null}
POST /payments/{id}/support-documents
GET  /payments/{id}/support-documents
```

Attach and list evidence for a payment (proof of payment, payment confirmation…). Same fields, limits and response as [invoice support documents](/guides/invoices#support-documents).

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/payment_1234567890/support-documents \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "file=@comprobante-transferencia.pdf" \
  -F "documentType=payment_proof"
```

## Payment Structure

### Payment Status

| Status | Description |
| - | - |
| `requires_payment_method` | Waiting for payment method |
| `succeeded` | Payment completed successfully |
| `canceled` | Payment canceled |

### Payment Processors

* **stripe** - Stripe payment gateway
* **mercado\_pago** - MercadoPago
* **paypal** - PayPal
* **manual** - Manual/bank transfer

## Complex Payment Examples

### Register Payment with Client Search and Update

This example demonstrates how to search for an existing client by tax ID and update their information if found, or create a new client if not found:

```json theme={null}
{
    "client": {
        "search": {
            "on_key": "tax_id",
            "on_value": "PEGJ800101ABC",
            "update": true
        },
        "name": "Juan Pérez García",
        "email": "juan.perez.updated@ejemplo.com",
        "tax_id": "PEGJ800101ABC",
        "tax_system": "601",
        "phone": "+52 55 1234 5678",
        "address": {
            "street": "Av. Reforma",
            "exterior": "456",
            "zip": "06600",
            "city": "Ciudad de México",
            "state": "CDMX"
        }
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [
        {
            "description": "Monthly subscription",
            "quantity": 1,
            "unit_price": 1500.0,
            "product_key": "80141503",
            "unit_key": "E48",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.16
                }
            ]
        }
    ]
}
```

**Key Points:**

* With `update: true`, if a client with tax\_id "PEGJ800101ABC" exists, their email, phone, and address will be updated
* If no client is found, a new client will be created with all the provided information
* The search ensures you don't create duplicate clients when processing recurring payments

### Register Payment with Multiple Items

```json theme={null}
{
    "client": {
        "id": "client_1234567890"
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "04",
    "items": [
        {
            "id": "service_001",
            "quantity": 2,
            "unit_price": 1000.0
        },
        {
            "description": "Installation service",
            "quantity": 1,
            "unit_price": 500.0,
            "product_key": "72121400",
            "unit_key": "E48",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.16
                }
            ]
        },
        {
            "description": "Express shipping",
            "quantity": 1,
            "unit_price": 200.0,
            "product_key": "78102200",
            "unit_key": "E48",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.16
                }
            ]
        }
    ]
}
```

### Register Payment with Withholding Taxes

```json theme={null}
{
    "client": {
        "id": "client_1234567890"
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [
        {
            "description": "Professional services",
            "quantity": 1,
            "unit_price": 10000.0,
            "product_key": "80141503",
            "unit_key": "E48",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.16,
                    "withholding": false
                },
                {
                    "type": "ISR",
                    "rate": 0.1,
                    "withholding": true
                },
                {
                    "type": "IVA",
                    "rate": 0.106667,
                    "withholding": true
                }
            ]
        }
    ]
}
```

### Payment Request with Custom Invoice Config

```json theme={null}
{
    "client": {
        "id": "client_1234567890"
    },
    "currency": "MXN",
    "items": [
        {
            "id": "service_1234567890",
            "quantity": 1
        }
    ],
    "automation_type": "pue_invoice",
    "allowed_payment_methods": ["card", "bank"],
    "send_email": true,
    "emails": ["client@example.com"],
    "invoice_config": {
        "serie": "B",
        "folio": "456"
    },
    "metadata": {
        "project_id": "PROJ-2024-001",
        "department": "Engineering"
    }
}
```

### Register USD Payment with Exchange Rate

```json theme={null}
{
    "client": {
        "id": "client_1234567890"
    },
    "automation_type": "pue_invoice",
    "currency": "USD",
    "exchange_rate": 18.5,
    "payment_form": "03",
    "items": [
        {
            "description": "International consulting",
            "quantity": 10,
            "unit_price": 100.0,
            "product_key": "80141503",
            "unit_key": "HUR",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.0,
                    "factor": "Exento"
                }
            ]
        }
    ]
}
```

### Payment Splitting (Marketplace)

Split payments between a master team (platform) and a connect team (merchant) in marketplace scenarios. This is only available for master teams with marketplace-enabled billing accounts.

**Important:** When using `transfer_data`, the `team` and `livemode` fields are automatically extracted from the authentication token. Developers do not need to send these fields in the request body.

```json theme={null}
{
    "client": {
        "id": "client_1234567890"
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [
        {
            "description": "Professional consulting services",
            "quantity": 1,
            "unit_price": 1000.0,
            "product_key": "80141503",
            "unit_key": "E48",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.16
                }
            ]
        }
    ],
    "transfer_data": {
        "master": 60,
        "connect": "ABC123456789",
        "master_to": "client",
        "connect_to": "client"
    },
    "idempotency_key": "marketplace-payment-12345"
}
```

**Transfer Data Configuration:**

* `master` (number, 0-100) - Percentage of the payment for the master team
* `connect` (string) - Tax ID (RFC) or Team ID of the connect team. If not found, a new team will be created
* `master_to` (string: 'client' | 'connect') - Client assignment for master payment:
  * `client`: Use the original client from the request
  * `connect`: Create the connect team as a client for the master payment
* `connect_to` (string: 'client' | 'master') - Client assignment for connect payment:
  * `client`: Use the original client from the request
  * `master`: Create the master team as a client for the connect payment
* `connect_custom_config` (object, optional) - Customize items in the connect payment:
  * `product_key` (string) - SAT product key for connect payment items
  * `unit_key` (string) - SAT unit key for connect payment items
  * `custom_description` (string) - Custom description for connect payment items
  * `custom_price` (number) - Fixed amount for connect payment (overrides percentage calculation)
  * `taxes` (array) - Custom tax configuration for connect payment items

**Split Payment Response:**

```json theme={null}
{
    "message": "Split payment registered successfully",
    "data": {
        "split_reference": "split_abc123xyz",
        "master_payment_id": "payment_master_123",
        "connect_payment_id": "payment_connect_456",
        "master_amount": 696.0,
        "connect_amount": 464.0,
        "total_amount": 1160.0,
        "master_payment": {
            "id": "payment_master_123",
            "client": "client_1234567890",
            "amount": 696.0,
            "team": "team_master_123",
            "split_role": "master"
        },
        "connect_payment": {
            "id": "payment_connect_456",
            "client": "client_connect_789",
            "amount": 464.0,
            "team": "team_connect_456",
            "split_role": "connect"
        },
        "connect_team": {
            "id": "team_connect_456",
            "tax_id": "EMP800101ABC",
            "legal_name": "Empresa Ejemplo SA de CV",
            "is_newly_created": true,
            "onboarding_url": "https://embeded.gigstack.pro/?sessionId=otpOnboarding_7Kd2Pq&c=193847"
        }
    }
}
```

**When a new team is created:**

If the connect team doesn't exist, the response includes an `onboarding_url` that can be sent to the merchant to complete their team setup.

### Advanced: Split Payment with Custom Configuration

```json theme={null}
{
    "client": {
        "search": {
            "on_key": "tax_id",
            "on_value": "PEGJ800101ABC",
            "update": false
        },
        "name": "Juan Pérez García",
        "email": "juan.perez@ejemplo.com",
        "tax_id": "PEGJ800101ABC",
        "tax_system": "601"
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [
        {
            "description": "Platform service with marketplace split",
            "quantity": 1,
            "unit_price": 1000.0,
            "product_key": "80141503",
            "unit_key": "E48",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.16
                }
            ]
        }
    ],
    "transfer_data": {
        "master": 30,
        "connect": "EMP800101ABC",
        "master_to": "client",
        "connect_to": "master",
        "connect_custom_config": {
            "product_key": "01010101",
            "unit_key": "E48",
            "custom_description": "Professional consulting services",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.16,
                    "withholding": false
                }
            ]
        }
    }
}
```

### Fixed Commission Fee (Custom Price)

Instead of percentage-based splitting, you can charge a fixed commission fee using `custom_price`. This is useful when you want to charge a flat platform fee regardless of the transaction amount.

```json theme={null}
{
    "client": {
        "id": "client_1234567890"
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [
        {
            "description": "Product sale",
            "quantity": 1,
            "unit_price": 1000.0,
            "product_key": "80141503",
            "unit_key": "E48",
            "taxes": [
                {
                    "type": "IVA",
                    "rate": 0.16
                }
            ]
        }
    ],
    "transfer_data": {
        "master": 0,
        "connect": "EMP800101ABC",
        "master_to": "client",
        "connect_to": "master",
        "connect_custom_config": {
            "custom_price": 50.00,
            "custom_description": "Platform service fee"
        }
    }
}
```

**Key Points:**

* When `custom_price` is set, it overrides the percentage calculation
* The connect team gets the exact `custom_price` amount (e.g., \$50)
* The master team gets the remainder (e.g., $1110 on a $1160 total)
* The `master` percentage field is ignored when using `custom_price`
* Useful for fixed platform fees, minimum commissions, or tiered pricing

**Response with Custom Price:**

```json theme={null}
{
    "message": "Split payment registered successfully",
    "data": {
        "split_reference": "split_abc123xyz",
        "master_payment_id": "payment_master_123",
        "connect_payment_id": "payment_connect_456",
        "master_amount": 1110.00,
        "connect_amount": 50.00,
        "total_amount": 1160.00,
        "used_custom_price": true
    }
}
```

## Common Scenarios

### 1. Register Completed Payment (Bank Transfer)

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {"id": "client_1234567890"},
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [{
      "id": "service_1234567890",
      "quantity": 1
    }],
    "metadata": {
      "bank_reference": "TRF-2024-0123"
    }
  }'
```

### 2. Create Payment Link

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/request \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {"id": "client_1234567890"},
    "currency": "MXN",
    "items": [{
      "description": "Monthly subscription",
      "quantity": 1,
      "unit_price": 999.00,
      "product_key": "81111500",
      "unit_key": "MON",
      "taxes": [{"type": "IVA", "rate": 0.16}]
    }],
    "automation_type": "pue_invoice",
    "allowed_payment_methods": ["card", "bank", "oxxo"],
    "send_email": true,
    "emails": ["client@example.com"]
  }'
```

### 3. Register Cash Payment

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {"id": "client_1234567890"},
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "01",
    "items": [{
      "id": "service_1234567890",
      "quantity": 5
    }],
    "metadata": {
      "cashier": "employee_001",
      "receipt_number": "CASH-2024-0456"
    }
  }'
```

### 4. Register Payment with PPD Invoice Complement

Register a payment linked to an existing PPD invoice. This automatically generates a payment complement (complemento de pago) CFDI.

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {"id": "client_1234567890"},
    "automation_type": "none",
    "currency": "MXN",
    "payment_form": "03",
    "ppd_invoice_id": "invoice_ppd_1234567890",
    "items": [{
      "id": "service_1234567890",
      "quantity": 1,
      "unit_price": 5000.00
    }]
  }'
```

**Key Points:**

* The `ppd_invoice_id` must reference a valid PPD **income** invoice (`payment_method='PPD'`, `status='valid'`, `invoice_type='I'`)
* The payment complement is generated automatically by proserver after the payment is created
* You can use `automation_type: "none"` since the complement is handled via `ppd_invoice_id`
* The PPD invoice's `payments` array is updated to link back to this payment

### 5. Partial Refund

This example **records** a refund already completed outside gigstack; it does not
return money through Stripe. Read [Refund Payment](#refund-payment) for the alternative
processor request, amount units and timeout recovery.

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/payment_1234567890/refund \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 580.00,
    "reason": "50% discount returned externally",
    "external_processor_refund": false
  }'
```

### 6. Marketplace Payment Split (Platform Fee)

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {
      "search": {
        "on_key": "tax_id",
        "on_value": "PEGJ800101ABC",
        "update": false
      },
      "name": "Juan Pérez García",
      "email": "juan.perez@ejemplo.com",
      "tax_id": "PEGJ800101ABC",
      "tax_system": "601"
    },
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [{
      "description": "Marketplace transaction",
      "quantity": 1,
      "unit_price": 1000.0,
      "product_key": "80141503",
      "unit_key": "E48",
      "taxes": [{"type": "IVA", "rate": 0.16}]
    }],
    "transfer_data": {
      "master": 10,
      "connect": "ABC123456789",
      "master_to": "client",
      "connect_to": "client"
    },
    "metadata": {
      "marketplace_transaction_id": "TXN-2024-001",
      "merchant_reference": "MERCHANT-123"
    }
  }'
```

Response includes split payment details with master (10%) and connect (90%) payment IDs.

### 7. Marketplace Payment with Fixed Commission

```bash theme={null}
curl -X POST https://api.gigstack.io/v2/payments/register \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {"id": "client_1234567890"},
    "automation_type": "pue_invoice",
    "currency": "MXN",
    "payment_form": "03",
    "items": [{
      "description": "Product sale",
      "quantity": 2,
      "unit_price": 500.0,
      "product_key": "43211500",
      "unit_key": "H87",
      "taxes": [{"type": "IVA", "rate": 0.16}]
    }],
    "transfer_data": {
      "master": 0,
      "connect": "VENDOR123ABC",
      "master_to": "connect",
      "connect_to": "client",
      "connect_custom_config": {
        "custom_price": 25.00,
        "custom_description": "Marketplace commission"
      }
    }
  }'
```

Connect (marketplace) receives fixed $25 commission, master (vendor) receives the remainder ($1135 from \$1160 total).

## Payment Workflows

### Immediate Payment (PUE)

```mermaid theme={null}
graph LR
    A[Register Payment] --> B[Create PUE Invoice]
    B --> C[Stamp with SAT]
    C --> D[Send to Client]
```

### Deferred Payment (PPD)

```mermaid theme={null}
graph LR
    A[Register Payment] --> B[Create PPD Invoice]
    B --> C[Receive Payment]
    C --> D[Create Payment Complement]
    D --> E[Stamp with SAT]
```

### Payment Request Flow

```mermaid theme={null}
graph LR
    A[Create Request] --> B[Generate Link]
    B --> C[Client Pays]
    C --> D[Mark as Paid]
    D --> E[Generate Invoice]
```

## Best Practices

1. **Use idempotency keys** - Use one stable `idempotency_key` per business payment on creation; reuse it for retry attempts. Refunds do not accept this key.
2. **Use client search** - Leverage the `search` parameter to avoid duplicate client records when processing recurring payments.
3. **Set automation\_type correctly** - Choose the appropriate automation type based on your invoicing workflow (PUE for immediate, PPD for deferred).
4. **Include all items** - Ensure complete and accurate payment information for proper invoice generation.
5. **Validate clients first** - Verify fiscal data (RFC, tax system) before processing payments to avoid stamp failures.
6. **Use metadata** - Track internal references, order IDs, and other business-specific data for reconciliation.
7. **Handle webhooks** - Process payment status updates to keep your systems synchronized.
8. **Test in staging** - Validate all workflows in the staging environment before deploying to production.

## Related Resources

* [Clients API](/guides/clients) - Manage payment recipients
* [Services API](/guides/services) - Configure payment items
* [Invoices API](/guides/invoices) - Generated invoices from payments
* [Teams API](/guides/teams) - Configure payment settings

## Error Handling

Payment handler errors generally use this envelope; authentication and gateway failures can have a different body. Check the HTTP status before assuming `error.code` exists:

```json theme={null}
{
    "success": false,
    "error": { "code": "invalid_request_body", "message": "…", "details": "…" },
    "timestamp": 1718451000000
}
```

### At a glance

| Status | When | Retry the same request? | What you should do |
| - | - | - | - |
| `400` | Validation/state failure, or `resource_conflict` for an existing registration key | Depends on code | Fix invalid input; for a duplicate, retrieve and reconcile the original payment with the same business reference. |
| `401` | Missing/invalid token, or no resolvable team | No | Re-authenticate. |
| `403` | The payment belongs to another team or the other livemode | No | Use the matching key or `?team=`. |
| `404` | Payment, client or service not found | No | Check the id. |
| `409` | Search matched **multiple** clients/services, **or** a concurrent auto-create is in flight | Depends — see below | See below. |
| `500` | Unhandled server error | Maybe | Report with the process id. |

> Payments themselves do not stamp CFDIs synchronously. When a payment carries an `automation_type` that produces an invoice, the stamping happens **downstream and asynchronously** — a `2xx` from `POST /payments/register` means the payment was recorded, not that a CFDI exists. Subscribe to the `invoice.created` / `invoice.failed` webhook events for the stamping outcome, and expect the CFDI-specific statuses (`412` SAT not connected, `503` PAC unavailable, `429` credit limit) to surface [on the invoice endpoints](/guides/invoices#error-handling) rather than here.

### 409 — Multiple clients match search criteria

```json theme={null}
{
    "success": false,
    "error": {
        "code": "resource_conflict",
        "message": "Multiple clients found matching tax_id=\"…\". Please use a more specific search criteria.",
        "details": ["client_abc123", "client_def456"]
    }
}
```

`details` lists the ids that matched, so you can resolve the ambiguity without a second query. **Not retryable** — the same request will conflict again.

**Do:** pass the exact client `id`, or search on a more selective key (`tax_id` over `name`), or deduplicate the clients.

### 409 — Concurrent resource creation

```json theme={null}
{
    "success": false,
    "error": {
        "code": "resource_conflict",
        "message": "Client creation in progress for tax_id=… . Please retry."
    }
}
```

A different in-flight request is auto-creating the same client (or `Service creation in progress …` for an item). gigstack refused to race it rather than create a duplicate.

**Do:** this one **is** retryable — wait \~1s and send the same request again; the resource will exist. If you are firing many payments for a new client in parallel, create the client once up front and reference it by `id`.

### 400 — Invalid state transition

The lifecycle endpoints reject operations that do not apply to the payment's current status:

| Endpoint | Message |
| - | - |
| `POST /payments/{id}/paid` | `Payment is already marked as paid` |
| `POST /payments/{id}/paid` | `Cannot mark a cancelled payment as paid` |
| `POST /payments/{id}/paid` | `date cannot be in the future` |
| `DELETE /payments/{id}` | `Cannot cancel a payment that has already succeeded` |
| `DELETE /payments/{id}` | `Payment is already cancelled` |
| `POST /payments/{id}/refund` | `Can only refund payments that have succeeded` |

All terminal. Read the payment first (`GET /payments/{id}`) and branch on `status` rather than probing.

### 400 — Refund exceeds the payment

```json theme={null}
{
    "success": false,
    "error": {
        "code": "invalid_request_body",
        "message": "Total refunded amount (1500 pesos) cannot exceed payment amount (1000 pesos)"
    }
}
```

The check is against the **cumulative** total, not this one refund — prior partial refunds count. Read the payment first: the current API returns `total` in currency units and `total_refunded` in minor units. Subtract `(total_refunded ?? 0) / 100` from `total`; see [Refund Payment](#refund-payment).

### 400 — External processor refund not allowed

```json theme={null}
{
    "success": false,
    "error": {
        "code": "invalid_request_body",
        "message": "External processor refund is not allowed for this payment"
    }
}
```

You set `external_processor_refund: true` on a payment that has no `paymentIntent` — there is no upstream charge for gigstack to refund. Manually registered payments (cash, transfer) are in this category.

**Do:** omit `external_processor_refund` to record the refund in gigstack only, and move the money back yourself.

### Failed Client Fiscal Information (400)

When a client's fiscal information fails validation (invalid RFC or EFOS blacklist), the payment is rejected. Update the client (`PUT /v2/clients/{id}`) and retry.

***

For additional assistance, contact [support@gigstack.io](mailto:support@gigstack.io)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.