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

# Payment Processing Guide

> Create payment requests, process royalty payments, and manage payment workflows for collaborators

## Overview

The Royalti.io API provides comprehensive payment management capabilities for distributing royalty earnings to collaborators. This guide covers creating payment requests, recording payments, tracking payment history, and managing payment settings.

### Key Features

* **Payment requests** - Users can request payments for their earnings
* **Payment records** - Track all payment transactions with receipts
* **Multi-currency support** - Handle payments in various currencies with automatic USD conversion
* **Payment settings** - Configure minimum thresholds and payment methods per user
* **Bulk operations** - Process multiple payments efficiently

## Prerequisites

### Required Setup

Before processing payments, ensure you have:

* **User earnings data** - Calculated royalties with amounts due to users
* **Payment settings configured** - Each user should have payment preferences
* **Payment method integration** - Connect your payment provider (bank transfer, PayPal, Wise, etc.)

### Authentication

<Note>
  All payment endpoints require authentication with a Bearer token.
</Note>

```javascript Node.js theme={null}
const headers = {
  'Authorization': `Bearer ${your_jwt_token}`,
  'Content-Type': 'application/json'
};
```

```python Python theme={null}
headers = {
    'Authorization': f'Bearer {your_jwt_token}',
    'Content-Type': 'application/json'
}
```

## Quick Start: Payment Workflow

<Steps>
  <Step title="Check Who's Owed Payment">
    Get list of users with outstanding balances.

    ```javascript Node.js theme={null}
    const response = await fetch(
      'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC',
      {
        headers: {
          'Authorization': `Bearer ${token}`
        }
      }
    );

    const { Users } = await response.json();

    // Filter users meeting minimum threshold
    const eligible = Users.filter(user => user.Due >= 50);
    ```

    ```python Python theme={null}
    response = requests.get(
        'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC',
        headers=headers
    )

    data = response.json()
    users = data['Users']

    # Filter users meeting minimum threshold
    eligible = [u for u in users if u['Due'] >= 50]
    ```
  </Step>

  <Step title="Create Payment Record">
    Record a payment transaction with details and optional receipt upload.

    ```javascript Node.js theme={null}
    const formData = new FormData();
    formData.append('user', userId);
    formData.append('title', 'Q1 2024 Royalty Payment');
    formData.append('transactionDate', '2024-04-15T10:00:00Z');
    formData.append('currency', 'USD');
    formData.append('amount', 1500.00);
    formData.append('amountUSD', 1500.00);
    formData.append('conversionRate', 1.0);
    formData.append('memo', 'Q1 royalty distribution');

    // Optional: attach receipt file
    formData.append('files', receiptFile);

    const response = await fetch('https://api.royalti.io/payment/', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`
        // Note: Don't set Content-Type for FormData
      },
      body: formData
    });

    const payment = await response.json();
    console.log('Payment ID:', payment.data.id);
    ```

    ```python Python theme={null}
    files = {
        'user': (None, user_id),
        'title': (None, 'Q1 2024 Royalty Payment'),
        'transactionDate': (None, '2024-04-15T10:00:00Z'),
        'currency': (None, 'USD'),
        'amount': (None, '1500.00'),
        'amountUSD': (None, '1500.00'),
        'conversionRate': (None, '1.0'),
        'memo': (None, 'Q1 royalty distribution'),
        'files': ('receipt.pdf', open('receipt.pdf', 'rb'), 'application/pdf')
    }

    response = requests.post(
        'https://api.royalti.io/payment/',
        headers={'Authorization': f'Bearer {token}'},
        files=files
    )

    payment = response.json()
    print(f"Payment ID: {payment['data']['id']}")
    ```
  </Step>

  <Step title="Verify Accounting Updated">
    Check that the user's balance was reduced.

    ```javascript Node.js theme={null}
    const response = await fetch(
      `https://api.royalti.io/accounting/${userId}/stats`,
      {
        headers: {
          'Authorization': `Bearer ${token}`
        }
      }
    );

    const stats = await response.json();
    console.log('Amount paid:', stats.paid);
    console.log('Amount due:', stats.due);
    ```

    <Info>
      When a payment is created, the user's "paid" amount automatically increases and "due" amount decreases.
    </Info>
  </Step>
</Steps>

## Payment Requests

Users can create payment requests for their earnings. Admins can review and approve these requests.

### User Creates Payment Request

Users request payment for their available balance. API keys and admins can create requests on behalf of users.

```javascript Node.js theme={null}
try {
  const response = await fetch('https://api.royalti.io/payment-request/', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${userToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      PaymentSettingId: 'payment-setting-uuid',  // Optional: required for VertoFX or standard payment processing
      currency: 'USD',
      amountUSD: 500.00,
      amount: 500.00,
      memo: 'Monthly payment request'
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Request failed: ${error.message}`);
  }

  const request = await response.json();
  console.log('Request ID:', request.data.id);
  console.log('Status:', request.data.status);  // "pending"
  console.log('Amount:', request.data.amountUSD, request.data.currency);
} catch (error) {
  console.error('Failed to create payment request:', error.message);
}
```

```python Python theme={null}
try:
    response = requests.post(
        'https://api.royalti.io/payment-request/',
        headers={
            'Authorization': f'Bearer {user_token}',
            'Content-Type': 'application/json'
        },
        json={
            'PaymentSettingId': 'payment-setting-uuid',
            'currency': 'USD',
            'amountUSD': 500.00,
            'amount': 500.00,
            'memo': 'Monthly payment request'
        }
    )

    response.raise_for_status()
    request = response.json()
    print(f"Request ID: {request['data']['id']}")
    print(f"Status: {request['data']['status']}")
    print(f"Amount: {request['data']['amountUSD']} {request['data']['currency']}")
except requests.exceptions.RequestException as e:
    print(f"Failed to create payment request: {e}")
```

<Warning>
  **Important:** While `PaymentSettingId` is technically optional during creation, it **must be provided before approval** if you intend to process the payment via standard methods or VertoFX. Approving without a `PaymentSettingId` will create a payment record but won't be able to process it through any payment method.
</Warning>

<Note>
  **Payment Request Fields:**

  * `PaymentSettingId` (Optional) - Reference to a configured payment method. Required for VertoFX or standard payment processing during approval.
  * `currency` (Required) - Currency code (USD, EUR, GBP, etc.)
  * `amountUSD` (Required) - USD amount for accounting and reporting
  * `amount` (Optional) - Amount in the specified currency
  * `memo` (Optional) - Description of the payment request
  * **Constraint:** Users can only have one `pending` payment request at a time
  * **Status flow:** `pending` → `approved` or `declined` (terminal states)
</Note>

### Create for Specific User (Admin/API Key)

Admins and workspace API keys can create payment requests on behalf of specific users:

```javascript Node.js theme={null}
try {
  const response = await fetch('https://api.royalti.io/payment-request/', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKeyOrAdminToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      user: 'target-user-uuid',  // Specify the user to create request for
      PaymentSettingId: 'payment-setting-uuid',
      currency: 'USD',
      amountUSD: 500.00,
      amount: 500.00,
      memo: 'Monthly royalty distribution'
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Request failed: ${error.message}`);
  }

  const request = await response.json();
  console.log(`Created request for user ${request.data.TenantUserId}`);
} catch (error) {
  console.error('Failed to create payment request:', error.message);
}
```

```python Python theme={null}
try:
    response = requests.post(
        'https://api.royalti.io/payment-request/',
        headers={
            'Authorization': f'Bearer {api_key_or_admin_token}',
            'Content-Type': 'application/json'
        },
        json={
            'user': 'target-user-uuid',  # Specify the user
            'PaymentSettingId': 'payment-setting-uuid',
            'currency': 'USD',
            'amountUSD': 500.00,
            'amount': 500.00,
            'memo': 'Monthly royalty distribution'
        }
    )

    response.raise_for_status()
    request = response.json()
    print(f"Created request for user {request['data']['TenantUserId']}")
except requests.exceptions.RequestException as e:
    print(f"Failed to create payment request: {e}")
```

<Info>
  **Admin/API Key Only:** Only workspace admins and API keys can specify a different user. Regular users creating payment requests will always create for themselves, even if a `user` parameter is provided.
</Info>

### List All Payment Requests

Admins can view all payment requests:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/payment-request/?status=pending&page=1&size=20',
  {
    headers: {
      'Authorization': `Bearer ${adminToken}`
    }
  }
);

const { requests, pagination } = await response.json();
```

**Query Parameters:**

* `status` - Filter by status: `pending`, `approved`, `declined`, `processing`, `completed`
* `userId` - Filter by specific user
* `page` - Page number (default: 1)
* `size` - Items per page (default: 20)

### Approve Payment Request

When you approve a payment request, the system automatically:

1. Creates a Payment record
2. Generates a PDF receipt
3. Uploads the receipt to cloud storage
4. Updates the request status to "approved"
5. Optionally processes international payments via VertoFX

<Tabs>
  <Tab title="Standard Approval">
    **Basic approval** creates a payment record with automatic receipt generation:

    ```javascript Node.js theme={null}
    const response = await fetch(
      `https://api.royalti.io/payment-request/${requestId}/approve`,
      {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${adminToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          title: 'March 2024 Royalty Payment',
          transactionDate: '2024-04-01T10:00:00Z',
          memo: 'Q1 royalty distribution'
        })
      }
    );

    const result = await response.json();
    console.log('Status:', result.paymentRequest.status);  // "approved"
    console.log('Payment ID:', result.payment.id);
    console.log('Receipt URL:', result.payment.files[0]);  // Auto-generated PDF
    ```

    ```python Python theme={null}
    response = requests.patch(
        f'https://api.royalti.io/payment-request/{request_id}/approve',
        headers={
            'Authorization': f'Bearer {admin_token}',
            'Content-Type': 'application/json'
        },
        json={
            'title': 'March 2024 Royalty Payment',
            'transactionDate': '2024-04-01T10:00:00Z',
            'memo': 'Q1 royalty distribution'
        }
    )

    result = response.json()
    print(f"Status: {result['paymentRequest']['status']}")
    print(f"Receipt URL: {result['payment']['files'][0]}")
    ```

    <Note>
      All optional parameters default to the values from the payment request if not provided.
    </Note>
  </Tab>

  <Tab title="VertoFX International Payment">
    **International payments** via VertoFX for wire transfers to beneficiaries worldwide:

    ```javascript Node.js theme={null}
    const response = await fetch(
      `https://api.royalti.io/payment-request/${requestId}/approve`,
      {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${adminToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          method: 'vertofx',
          walletId: 'verto-wallet-uuid',
          purposeCode: 'ROYALTY_PAYMENT',
          title: 'International Royalty Payment',
          memo: 'Paid via VertoFX international transfer'
        })
      }
    );

    const result = await response.json();
    console.log('VertoFX Transfer ID:', result.vertoTransferId);
    console.log('Status:', result.paymentRequest.status);
    ```

    ```python Python theme={null}
    response = requests.patch(
        f'https://api.royalti.io/payment-request/{request_id}/approve',
        headers={
            'Authorization': f'Bearer {admin_token}',
            'Content-Type': 'application/json'
        },
        json={
            'method': 'vertofx',
            'walletId': 'verto-wallet-uuid',
            'purposeCode': 'ROYALTY_PAYMENT',
            'title': 'International Royalty Payment',
            'memo': 'Paid via VertoFX international transfer'
        }
    )

    result = response.json()
    print(f"VertoFX Transfer ID: {result.get('vertoTransferId')}")
    ```

    **Required for VertoFX:**

    * `method`: Must be set to `'vertofx'`
    * `walletId`: Your VertoFX wallet ID
    * `purposeCode`: Payment purpose code (e.g., 'ROYALTY\_PAYMENT', 'INVOICE\_PAYMENT')

    <Warning>
      VertoFX payments require prior setup. See the [VertoFX Integration](#vertofx-integration) section below.
    </Warning>
  </Tab>

  <Tab title="With File Attachments">
    **Upload additional receipts** or supporting documents:

    ```javascript Node.js theme={null}
    const formData = new FormData();
    formData.append('title', 'March 2024 Payment');
    formData.append('memo', 'Payment with bank receipt attached');
    formData.append('files', bankReceiptFile);  // File object

    const response = await fetch(
      `https://api.royalti.io/payment-request/${requestId}/approve`,
      {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${adminToken}`
          // Don't set Content-Type for FormData
        },
        body: formData
      }
    );

    const result = await response.json();
    console.log('Files:', result.payment.files);
    // Contains both auto-generated PDF and uploaded receipt
    ```

    ```python Python theme={null}
    files = {
        'title': (None, 'March 2024 Payment'),
        'memo': (None, 'Payment with bank receipt attached'),
        'files': ('receipt.pdf', open('receipt.pdf', 'rb'), 'application/pdf')
    }

    response = requests.patch(
        f'https://api.royalti.io/payment-request/{request_id}/approve',
        headers={'Authorization': f'Bearer {admin_token}'},
        files=files
    )

    result = response.json()
    print(f"Files: {result['payment']['files']}")
    ```
  </Tab>
</Tabs>

**Approval Parameters:**

| Parameter       | Type   | Required    | Description                                  |
| --------------- | ------ | ----------- | -------------------------------------------- |
| title           | string | No          | Payment title (defaults to request title)    |
| transactionDate | string | No          | Transaction date (defaults to current date)  |
| currency        | string | No          | Currency code (defaults to request currency) |
| amount          | number | No          | Payment amount (defaults to request amount)  |
| amountUSD       | number | No          | USD amount (defaults to request amountUSD)   |
| conversionRate  | number | No          | Exchange rate                                |
| memo            | string | No          | Additional notes                             |
| method          | string | No          | Payment method ('vertofx' for international) |
| walletId        | string | Conditional | Required if method='vertofx'                 |
| purposeCode     | string | Conditional | Required if method='vertofx'                 |
| files           | array  | No          | Additional receipt files                     |

### Decline Payment Request

Admins can decline requests with a reason:

```javascript Node.js theme={null}
const response = await fetch(
  `https://api.royalti.io/payment-request/${requestId}/decline`,
  {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${adminToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      reason: 'Insufficient balance after recent refund'
    })
  }
);

const result = await response.json();
console.log('Status:', result.status);  // "declined"
```

```python Python theme={null}
response = requests.patch(
    f'https://api.royalti.io/payment-request/{request_id}/decline',
    headers={
        'Authorization': f'Bearer {admin_token}',
        'Content-Type': 'application/json'
    },
    json={
        'reason': 'Insufficient balance after recent refund'
    }
)

result = response.json()
print(f"Status: {result['status']}")
```

### Bulk Delete Payment Requests

Delete multiple payment requests efficiently:

```javascript Node.js theme={null}
try {
  const requestIds = [
    'request-id-1',
    'request-id-2',
    'request-id-3'
  ];

  const response = await fetch(
    'https://api.royalti.io/payment-request/bulk/delete',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${adminToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        ids: requestIds
      })
    }
  );

  if (!response.ok) {
    throw new Error('Bulk delete failed');
  }

  const result = await response.json();
  console.log(`Deleted ${result.data.deletedCount} requests`);
  console.log('Errors:', result.data.errors);  // If any failed

} catch (error) {
  console.error('Bulk delete error:', error.message);
}
```

```python Python theme={null}
try:
    request_ids = [
        'request-id-1',
        'request-id-2',
        'request-id-3'
    ]

    response = requests.post(
        'https://api.royalti.io/payment-request/bulk/delete',
        headers={
            'Authorization': f'Bearer {admin_token}',
            'Content-Type': 'application/json'
        },
        json={
            'ids': request_ids
        }
    )

    response.raise_for_status()
    result = response.json()
    print(f"Deleted {result['data']['deletedCount']} requests")
    if result['data']['errors']:
        print(f"Errors: {result['data']['errors']}")

except requests.exceptions.RequestException as e:
    print(f"Bulk delete error: {e}")
```

<Info>
  **Bulk Delete Notes:**

  * Delete up to 100 requests per operation
  * Partially successful deletes are allowed (some succeed, some fail)
  * Check the errors array for failed deletes
  * Deleted requests cannot be recovered
</Info>

## VertoFX Integration

VertoFX enables international wire transfers to beneficiaries worldwide with competitive FX rates and fast processing times.

### Prerequisites

Before using VertoFX for payments, you need:

1. **VertoFX Account** - Sign up at [verto.co](https://verto.co)
2. **API Credentials** - Obtain your API key from VertoFX dashboard
3. **Workspace Integration** - Connect your VertoFX account to Royalti.io

<Steps>
  <Step title="Connect VertoFX Account">
    Link your VertoFX account to your workspace:

    ```javascript Node.js theme={null}
    const response = await fetch('https://api.royalti.io/integrations/vertofx', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        apiKey: 'your-vertofx-api-key',
        apiSecret: 'your-vertofx-api-secret',
        environment: 'production'  // or 'sandbox' for testing
      })
    });

    const integration = await response.json();
    console.log('Integration ID:', integration.data.id);
    console.log('Wallets:', integration.data.wallets);
    ```

    ```python Python theme={null}
    response = requests.post(
        'https://api.royalti.io/integrations/vertofx',
        headers={
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        },
        json={
            'apiKey': 'your-vertofx-api-key',
            'apiSecret': 'your-vertofx-api-secret',
            'environment': 'production'
        }
    )

    integration = response.json()
    print(f"Integration ID: {integration['data']['id']}")
    print(f"Wallets: {integration['data']['wallets']}")
    ```
  </Step>

  <Step title="Create Beneficiary">
    Add beneficiary bank details for the payment recipient:

    ```javascript Node.js theme={null}
    const response = await fetch(
      'https://api.royalti.io/integrations/vertofx/beneficiaries',
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: 'John Doe',
          currency: 'EUR',
          country: 'DE',
          bankDetails: {
            accountNumber: 'DE89370400440532013000',  // IBAN
            bankName: 'Deutsche Bank',
            swiftCode: 'DEUTDEFF'
          }
        })
      }
    );

    const beneficiary = await response.json();
    console.log('Beneficiary ID:', beneficiary.data.id);
    ```

    <Note>
      Bank details vary by country. The API validates requirements for each region.
    </Note>
  </Step>

  <Step title="Link Beneficiary to User">
    Associate the VertoFX beneficiary with a Royalti.io user:

    ```javascript Node.js theme={null}
    const response = await fetch(
      'https://api.royalti.io/integrations/vertofx/beneficiaries/link',
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          beneficiaryId: 'vertofx-beneficiary-id',
          tenantUserId: 'user-uuid',
          currency: 'EUR'
        })
      }
    );

    const link = await response.json();
    console.log('User linked to beneficiary');
    ```
  </Step>
</Steps>

### Payment Purpose Codes

VertoFX requires a purpose code for compliance. Available codes:

```javascript Node.js theme={null}
// Get available purpose codes
const response = await fetch(
  'https://api.royalti.io/integrations/vertofx/purpose-codes',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const codes = await response.json();

```

**Common Codes:**

* `ROYALTY_PAYMENT` - Music royalty distributions
* `INVOICE_PAYMENT` - Invoice settlements
* `SERVICE_FEE` - Service charges
* `COMMISSION` - Commission payments

### Processing International Payments

Once setup is complete, approve payment requests with the VertoFX method:

```javascript Node.js theme={null}
const response = await fetch(
  `https://api.royalti.io/payment-request/${requestId}/approve`,
  {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${adminToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      method: 'vertofx',
      walletId: 'your-vertofx-wallet-id',
      purposeCode: 'ROYALTY_PAYMENT',
      currency: 'EUR',
      amount: 1000.00,  // Amount in EUR
      amountUSD: 1090.00,  // USD equivalent
      conversionRate: 1.09
    })
  }
);

const result = await response.json();
console.log('Transfer initiated:', result.vertoTransferId);
```

<Info>
  VertoFX payments are processed asynchronously. The system creates the payment record immediately and updates it with the transfer details once VertoFX confirms the transaction.
</Info>

### Managing Beneficiaries

View and update beneficiary information:

```javascript Node.js theme={null}
// List all beneficiaries
const response = await fetch(
  'https://api.royalti.io/integrations/vertofx/beneficiaries',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const beneficiaries = await response.json();

// Update beneficiary
const updateResponse = await fetch(
  `https://api.royalti.io/integrations/vertofx/beneficiaries/${beneficiaryId}`,
  {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      bankDetails: {
        accountNumber: 'updated-account-number'
      }
    })
  }
);
```

## Automatic Receipt Generation

When you approve a payment request, the system automatically generates a professional PDF receipt and attaches it to the payment record.

### How It Works

<Steps>
  <Step title="Approval Triggers Generation">
    When you call the approve endpoint, receipt generation starts automatically:

    ```javascript Node.js theme={null}
    const response = await fetch(
      `https://api.royalti.io/payment-request/${requestId}/approve`,
      {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${adminToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          title: 'March 2024 Royalty Payment'
        })
      }
    );

    const result = await response.json();
    console.log('Receipt Status:', result.receiptStatus);  // "generated"
    ```
  </Step>

  <Step title="Receipt Details">
    The generated PDF receipt includes:

    * Payment amount and currency
    * Recipient information
    * Transaction date
    * Payment method details
    * Unique payment ID for reference
    * Company branding (if configured)
  </Step>

  <Step title="Access the Receipt">
    The receipt URL is included in the payment record:

    ```javascript Node.js theme={null}
    const payment = result.payment;
    const receiptUrl = payment.files[0];  // First file is always the receipt

    console.log('Download receipt:', receiptUrl);
    // Example: https://storage.googleapis.com/receipts/payment-123.pdf
    ```
  </Step>
</Steps>

### Receipt Format

The PDF receipt follows this structure:

```
┌─────────────────────────────────────┐
│         [Your Company Logo]         │
│                                     │
│         PAYMENT RECEIPT             │
│                                     │
│  Receipt #: PMT-2024-03-001        │
│  Date: March 15, 2024              │
│                                     │
│  PAID TO:                          │
│  John Doe                          │
│  john@example.com                  │
│                                     │
│  PAYMENT DETAILS:                  │
│  Amount: $1,500.00 USD             │
│  Payment Method: Bank Transfer     │
│  Transaction ID: TXN-ABC-123       │
│                                     │
│  DESCRIPTION:                      │
│  Q1 2024 Royalty Distribution      │
│                                     │
│  Thank you for your collaboration! │
└─────────────────────────────────────┘
```

### Customization

<Warning>
  Receipt templates are configured at the workspace level. Contact support to customize your receipt branding and layout.
</Warning>

### Downloading Receipts

**Single Receipt:**

```javascript Node.js theme={null}
async function downloadReceipt(paymentId, token) {
  // Get payment details
  const payment = await fetch(
    `https://api.royalti.io/payment/${paymentId}`,
    {
      headers: { 'Authorization': `Bearer ${token}` }
    }
  ).then(r => r.json());

  // Download the receipt
  const receiptUrl = payment.files[0];
  const receiptBlob = await fetch(receiptUrl).then(r => r.blob());

  // Save to file
  const filename = `receipt-${paymentId}.pdf`;
  // (Browser: trigger download, Node: save with fs)
}
```

**Bulk Receipt Download:**

```javascript Node.js theme={null}
async function downloadAllReceipts(startDate, endDate, token) {
  // Get all payments in date range
  const payments = await fetch(
    `https://api.royalti.io/payment/?startDate=${startDate}&endDate=${endDate}`,
    {
      headers: { 'Authorization': `Bearer ${token}` }
    }
  ).then(r => r.json());

  // Download each receipt
  for (const payment of payments.Payments) {
    if (payment.files && payment.files.length > 0) {
      await downloadReceipt(payment.id, token);
    }
  }
}
```

### Troubleshooting Receipts

<AccordionGroup>
  <Accordion title="Receipt not generated">
    **Possible Causes:**

    * Approval request failed before completion
    * Network timeout during generation
    * Payment created manually (not from request)

    **Solution:**
    Manually upload a receipt using the payment update endpoint:

    ```javascript theme={null}
    const formData = new FormData();
    formData.append('files', receiptFile);

    await fetch(`https://api.royalti.io/payment/${paymentId}`, {
      method: 'PUT',
      headers: { 'Authorization': `Bearer ${token}` },
      body: formData
    });
    ```
  </Accordion>

  <Accordion title="Receipt URL not accessible">
    **Cause:** Receipt URLs are temporary and expire after 24 hours.

    **Solution:** Request a fresh URL by fetching the payment details again:

    ```javascript theme={null}
    const payment = await fetch(
      `https://api.royalti.io/payment/${paymentId}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    ).then(r => r.json());

    const freshUrl = payment.files[0];  // New signed URL
    ```
  </Accordion>
</AccordionGroup>

## Payment Records

### Create Payment

Record a payment transaction:

```javascript Node.js theme={null}
const response = await fetch('https://api.royalti.io/payment/', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    user: userId,
    title: 'Monthly Royalty Payment',
    transactionDate: new Date().toISOString(),
    currency: 'USD',
    amount: 1200.00,
    amountUSD: 1200.00,
    conversionRate: 1.0,
    memo: 'March 2024 royalties',
    paymentRequestId: requestId  // Optional: link to payment request
  })
});

const payment = await response.json();
```

**Required Fields:**

* `user` - TenantUserId receiving the payment
* `transactionDate` - When the payment was made
* `currency` - Currency code (USD, EUR, GBP, etc.)
* `amount` - Amount in the specified currency
* `amountUSD` - Amount converted to USD

**Optional Fields:**

* `title` - Description of the payment
* `memo` - Additional notes
* `conversionRate` - Exchange rate used
* `paymentRequestId` - Link to payment request
* `files` - Receipt or proof of payment files

### List Payments

Get paginated list of all payments:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/payment/?page=1&size=20',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const { payments, pagination } = await response.json();

payments.forEach(payment => {
  console.log(`${payment.title}: $${payment.amountUSD} to ${payment.TenantUser.fullName}`);
});
```

**Query Parameters:**

* `page` - Page number
* `size` - Items per page (max: 100)
* `userId` - Filter by recipient
* `startDate` - Filter by date range (YYYY-MM-DD)
* `endDate` - Filter by date range (YYYY-MM-DD)

### Get Payment Details

Retrieve specific payment with full details:

```javascript Node.js theme={null}
const response = await fetch(`https://api.royalti.io/payment/${paymentId}`, {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

const payment = await response.json();

console.log('Recipient:', payment.TenantUser.fullName);
console.log('Amount:', payment.amountUSD);
console.log('Date:', payment.transactionDate);
console.log('Memo:', payment.memo);
console.log('Files:', payment.Files);  // Attached receipts
```

### Update Payment

Modify payment details:

```javascript Node.js theme={null}
const response = await fetch(`https://api.royalti.io/payment/${paymentId}`, {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    memo: 'Updated: Paid via bank transfer',
    title: 'Q1 2024 Royalty Distribution'
  })
});
```

### Delete Payment

Remove a payment record:

```javascript Node.js theme={null}
const response = await fetch(`https://api.royalti.io/payment/${paymentId}`, {
  method: 'DELETE',
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
```

<Warning>
  Deleting a payment does NOT automatically adjust user balances. You must manually recalculate accounting after deleting payments.
</Warning>

## Bulk Payment Processing

### Create Multiple Payments

Process batch payments efficiently:

```javascript Node.js theme={null}
const paymentData = [
  {
    user: 'user-id-1',
    title: 'March 2024 Royalties',
    transactionDate: '2024-04-01T10:00:00Z',
    currency: 'USD',
    amount: 500.00,
    amountUSD: 500.00
  },
  {
    user: 'user-id-2',
    title: 'March 2024 Royalties',
    transactionDate: '2024-04-01T10:00:00Z',
    currency: 'USD',
    amount: 750.00,
    amountUSD: 750.00
  }
];

const response = await fetch('https://api.royalti.io/payment/bulk', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ payments: paymentData })
});

const result = await response.json();
console.log('Created:', result.data.created);
console.log('Failed:', result.data.failed);
```

**Response Format:**

```json theme={null}
{
  "status": "success",
  "data": {
    "created": 2,
    "failed": 0,
    "payments": [
      { "id": "payment-1", "user": "user-id-1", "amount": 500.00 },
      { "id": "payment-2", "user": "user-id-2", "amount": 750.00 }
    ],
    "errors": []
  }
}
```

## Payment Settings

Payment Settings store bank account details, PayPal information, and other payment methods for each user. These settings are used when creating payment requests and processing payments.

### Supported Payment Types

| Type               | Description                     | Use Case                                 |
| ------------------ | ------------------------------- | ---------------------------------------- |
| `BankWireTransfer` | Bank account for wire transfers | Domestic and international bank payments |
| `PayPal`           | PayPal account                  | Quick online payments                    |
| `Card`             | Debit/credit card               | Card-based payments                      |
| `Crypto`           | Cryptocurrency wallet           | Digital currency payments                |

### Create Payment Settings

<Tabs>
  <Tab title="Bank Wire Transfer">
    Configure bank account details for wire transfers:

    ```javascript Node.js theme={null}
    const response = await fetch('https://api.royalti.io/payment-setting/', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: 'Primary Bank Account',
        type: 'BankWireTransfer',
        settings: {
          bankName: 'Chase Bank',
          accountNumber: '123456789',
          bankCode: '987654',
          routingNumber: '021000021',
          swiftCode: 'CHASUS33',  // For international
          IBAN: 'US64SVBKUS6S3300958879',  // Optional
          country: 'United States',
          countryCode: 'US',
          currency: 'USD'
        },
        isDefault: true,
        memo: 'Main account for USD payments'
      })
    });

    const setting = await response.json();
    console.log('Setting ID:', setting.paymentSetting.id);
    ```

    ```python Python theme={null}
    response = requests.post(
        'https://api.royalti.io/payment-setting/',
        headers={
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        },
        json={
            'name': 'Primary Bank Account',
            'type': 'BankWireTransfer',
            'settings': {
                'bankName': 'Chase Bank',
                'accountNumber': '123456789',
                'bankCode': '987654',
                'routingNumber': '021000021',
                'swiftCode': 'CHASUS33',
                'country': 'United States',
                'countryCode': 'US',
                'currency': 'USD'
            },
            'isDefault': True,
            'memo': 'Main account for USD payments'
        }
    )

    setting = response.json()
    print(f"Setting ID: {setting['paymentSetting']['id']}")
    ```

    **Required Fields:**

    * `bankName` - Name of the bank
    * `accountNumber` - Bank account number
    * `bankCode` - Bank code or sort code
    * `country` - Country name
    * `countryCode` - ISO country code
    * `currency` - Currency code
  </Tab>

  <Tab title="PayPal">
    Configure PayPal account for payments:

    ```javascript Node.js theme={null}
    const response = await fetch('https://api.royalti.io/payment-setting/', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: 'Primary PayPal',
        type: 'PayPal',
        settings: {
          paypalEmail: 'user@example.com',
          country: 'United States',
          countryCode: 'US',
          currency: 'USD'
        },
        isDefault: false,
        memo: 'PayPal account for quick payments'
      })
    });

    const setting = await response.json();
    ```

    ```python Python theme={null}
    response = requests.post(
        'https://api.royalti.io/payment-setting/',
        headers={
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        },
        json={
            'name': 'Primary PayPal',
            'type': 'PayPal',
            'settings': {
                'paypalEmail': 'user@example.com',
                'country': 'United States',
                'countryCode': 'US',
                'currency': 'USD'
            },
            'isDefault': False,
            'memo': 'PayPal account for quick payments'
        }
    )
    ```

    **Required Fields:**

    * `paypalEmail` - PayPal email address
    * `country` - Country name
    * `countryCode` - ISO country code
    * `currency` - Currency code
  </Tab>

  <Tab title="Admin: Create for Other Users">
    Admins can create payment settings for other users:

    ```javascript Node.js theme={null}
    const response = await fetch('https://api.royalti.io/payment-setting/', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${adminToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        user: 'target-user-uuid',  // TenantUserId
        name: 'User Bank Account',
        type: 'BankWireTransfer',
        settings: {
          // ... bank details
        },
        isDefault: true
      })
    });
    ```

    <Info>
      When the `user` parameter is provided, the setting is created for that user instead of the authenticated user.
    </Info>
  </Tab>
</Tabs>

### List Payment Settings

Get all payment settings for filtering and management:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/payment-setting/?user=${userId}&default=true',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const settings = await response.json();

settings.forEach(setting => {
  console.log(`${setting.name} (${setting.type}): ${setting.isDefault ? 'Default' : 'Alternative'}`);
});
```

**Query Parameters:**

* `user` - Filter by TenantUserId
* `default` - Filter by isDefault (true/false)
* `type` - Filter by payment type
* `q` - Search by name or memo
* `page` - Page number
* `size` - Items per page

### Get Payment Setting Details

Retrieve specific setting:

```javascript Node.js theme={null}
const response = await fetch(
  `https://api.royalti.io/payment-setting/${settingId}`,
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const setting = await response.json();

console.log('Name:', setting.name);
console.log('Type:', setting.type);
console.log('Settings:', setting.settings);
console.log('Is Default:', setting.isDefault);
```

### Update Payment Settings

Modify existing payment preferences:

```javascript Node.js theme={null}
const response = await fetch(`https://api.royalti.io/payment-setting/${settingId}`, {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    minimumPayment: 100.00,
    paymentMethod: 'paypal',
    paymentDetails: {
      email: 'john@example.com'
    }
  })
});
```

## Multi-Currency Handling

### Currency Conversion

When paying in non-USD currency, specify conversion rate:

```javascript Node.js theme={null}
const response = await fetch('https://api.royalti.io/payment/', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    user: userId,
    title: 'EUR Payment',
    transactionDate: new Date().toISOString(),
    currency: 'EUR',
    amount: 1000.00,        // 1000 EUR
    amountUSD: 1090.00,     // Converted amount
    conversionRate: 1.09,   // EUR to USD rate
    memo: 'Payment made in EUR'
  })
});
```

**Common Currencies:**

* `USD` - US Dollar
* `EUR` - Euro
* `GBP` - British Pound
* `CAD` - Canadian Dollar
* `AUD` - Australian Dollar
* `NGN` - Nigerian Naira

<Info>
  Always provide both the original amount and USD conversion. The system uses `amountUSD` for accounting calculations.
</Info>

## Payment History & Reports

### Payment Summary

Get aggregate payment statistics:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/payment/summary?startDate=2024-01-01&endDate=2024-12-31',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const summary = await response.json();

console.log('Total paid:', summary.totalAmount);
console.log('Number of payments:', summary.count);
console.log('Unique recipients:', summary.uniqueUsers);
```

### Monthly Payment Report

Get month-by-month payment breakdown:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/payment/monthly?year=2024',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const monthlyData = await response.json();

monthlyData.forEach(month => {
  console.log(`${month.month}: $${month.totalAmount} (${month.count} payments)`);
});
```

### Export Payment Data

Download payment records as CSV:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/payment/export/csv?startDate=2024-01-01&endDate=2024-12-31',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const csvData = await response.text();
// Save to file or process as needed
```

## Best Practices

### Payment Workflow

1. **Regular payment cycles** - Establish consistent monthly/quarterly payment schedules
2. **Minimum thresholds** - Set reasonable minimum amounts to reduce transaction fees
3. **Batch processing** - Group payments by payment method for efficiency
4. **Clear communication** - Notify users before and after payment processing

### Record Keeping

1. **Attach receipts** - Upload proof of payment for all transactions
2. **Detailed memos** - Include payment period, method, and any relevant notes
3. **Transaction IDs** - Store external payment processor transaction IDs
4. **Currency clarity** - Always specify both original and USD amounts

### Error Handling

Always verify payment creation and handle errors:

```javascript Node.js theme={null}
async function createPaymentSafely(paymentData) {
  try {
    const response = await fetch('https://api.royalti.io/payment/', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(paymentData)
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Payment failed: ${error.message}`);
    }

    const payment = await response.json();

    // Verify accounting updated
    const accountingResponse = await fetch(
      `https://api.royalti.io/accounting/${paymentData.user}/stats`,
      {
        headers: { 'Authorization': `Bearer ${token}` }
      }
    );

    const stats = await accountingResponse.json();
    console.log('Payment created. User balance updated:', stats.due);

    return payment;

  } catch (error) {
    console.error('Payment creation failed:', error.message);
    throw error;
  }
}
```

### Reconciliation

Regularly reconcile payments with accounting:

```javascript Node.js theme={null}
// Get all payments for a period
const paymentsResponse = await fetch(
  'https://api.royalti.io/payment/?startDate=2024-03-01&endDate=2024-03-31',
  { headers: { 'Authorization': `Bearer ${token}` } }
);

const { payments } = await paymentsResponse.json();

// Get transaction summary from accounting
const accountingResponse = await fetch(
  'https://api.royalti.io/accounting/transactions/summary',
  { headers: { 'Authorization': `Bearer ${token}` } }
);

const { summary } = await accountingResponse.json();

console.log('Payment records total:', payments.reduce((sum, p) => sum + p.amountUSD, 0));
console.log('Accounting total paid:', summary.totalPayments);
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="User balance not updated after payment">
    **Checklist:**

    * Payment was successfully created (check response)
    * Payment amount is positive
    * User ID is correct
    * Payment is linked to correct workspace

    **Solutions:**

    1. Verify payment was created: `GET /payment/{id}`
    2. Check user stats: `GET /accounting/{userId}/stats`
    3. Force refresh: `GET /accounting/{userId}/stats?forceRefresh=true`
    4. If still incorrect, delete and recreate payment
  </Accordion>

  <Accordion title="Payment request shows 'pending' but user can't create new request">
    **Cause:** Users can only have one pending payment request at a time.

    **Solution:**

    1. Check existing request: `GET /payment-request/?userId={userId}&status=pending`
    2. Complete or decline the existing request
    3. User can then create a new request
  </Accordion>

  <Accordion title="Currency conversion not working correctly">
    **Common issues:**

    * Wrong conversion rate direction (e.g., using USD/EUR instead of EUR/USD)
    * Missing `conversionRate` field
    * `amountUSD` doesn't match calculation

    **Solution:**

    ```javascript theme={null}
    // Correct conversion example
    const eurAmount = 1000;
    const eurToUsdRate = 1.09;  // 1 EUR = 1.09 USD
    const usdAmount = eurAmount * eurToUsdRate;  // 1090 USD

    {
      currency: 'EUR',
      amount: eurAmount,
      amountUSD: usdAmount,
      conversionRate: eurToUsdRate
    }
    ```
  </Accordion>

  <Accordion title="Bulk payment partially failed">
    **Expected behavior:** Bulk operations continue processing even if some fail.

    **Check response:**

    ```javascript theme={null}
    const result = await response.json();
    console.log('Successful:', result.data.created);
    console.log('Failed:', result.data.failed);
    console.log('Errors:', result.data.errors);
    ```

    **Solution:** Review errors array and retry failed payments individually.
  </Accordion>

  <Accordion title="Payment request created but approval fails - 'PaymentSettingId is required'">
    **Cause:** Payment request was created without a `PaymentSettingId`, but approval requires one to process the payment.

    **Prevention:**
    Always provide `PaymentSettingId` when creating payment requests (unless you plan to update it before approval):

    ```javascript theme={null}
    // Good: Payment setting provided
    const request = await fetch('https://api.royalti.io/payment-request/', {
      method: 'POST',
      body: JSON.stringify({
        PaymentSettingId: 'setting-uuid',  // Include this
        currency: 'USD',
        amountUSD: 500.00
      })
    });

    // Risky: No payment setting
    const request = await fetch('https://api.royalti.io/payment-request/', {
      method: 'POST',
      body: JSON.stringify({
        currency: 'USD',
        amountUSD: 500.00
        // Missing PaymentSettingId
      })
    });
    ```

    **If already created without PaymentSettingId:**

    1. User must configure payment settings first via Payment Settings API
    2. Create a new payment request with the `PaymentSettingId`
    3. Or contact support to manually update the request
  </Accordion>
</AccordionGroup>

## API Reference

For complete endpoint documentation, see:

**Payment Requests:**

* [POST /payment-request](/api-reference/payment-requests/post-payment-request) - Create request
* [GET /payment-request](/api-reference/payment-requests/get-payment-request) - List requests
* [PATCH /payment-request/{id}/approve](/api-reference/payment-requests/patch-payment-request-id-approve) - Approve
* [PATCH /payment-request/{id}/decline](/api-reference/payment-requests/patch-payment-request-id-decline) - Decline
* [POST /payment-request/bulk/delete](/api-reference/payment-requests/post-payment-request-bulk-delete) - Bulk Delete

**Payments:**

* [POST /payment](/api-reference/payments/post-payment) - Create payment
* [GET /payment](/api-reference/payments/get-payment) - List payments
* [GET /payment/{id}](/api-reference/payments/get-payment-id) - Get details
* [PUT /payment/{id}](/api-reference/payments/put-payment-id) - Update payment
* [DELETE /payment/{id}](/api-reference/payments/delete-payment-id) - Delete payment
* [POST /payment/bulk](/api-reference/payments/post-payment-bulk) - Bulk create

**Payment Settings:**

* [POST /payment-setting](/api-reference/payment-settings/post-payment-setting) - Create settings
* [GET /payment-setting](/api-reference/payment-settings/get-payment-setting) - Get settings
* [PUT /payment-setting/{id}](/api-reference/payment-settings/put-payment-setting-id) - Update settings

**Related Guides:**

* [Financial Data & Management](/guides/financial-data-management) - View earnings
* [User Management](/guides/user-management) - Manage collaborators
* [Integrated Workflows](/guides/integrated-workflows) - Complete payment workflows
