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

# Complete API Reference

> StableX GraphQL API reference

# StableX Complete API Reference

Use the **On this page** menu on the right to jump to any section.

***

## 1. API Overview

### 1.1 GraphQL Endpoint

```
POST https://your-domain.com/graphql
```

### 1.2 Headers

```
Content-Type: application/json
Authorization: Bearer <access_token>  (for authenticated requests)
```

### 1.3 Request Format

```json theme={null}
{
  "query": "mutation { ... }",
  "variables": { ... }
}
```

***

## 2. Authentication

### 2.1 Unified Login (Recommended)

Automatically detects if user is a Merchant or Staff member.

**Permissions**: None (public)

**Request**:

```graphql theme={null}
mutation Login($email: String!, $password: String!) {
  login(email: $email, password: $password) {
    access_token
    refresh_token
    expires_in
    user_type
  }
}
```

**Variables**:

```json theme={null}
{
  "email": "merchant@example.com",
  "password": "SecurePass123!"
}
```

**Response** (Merchant):

```json theme={null}
{
  "data": {
    "login": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": null,
      "expires_in": 3600,
      "user_type": "merchant"
    }
  }
}
```

**Response** (Staff):

```json theme={null}
{
  "data": {
    "login": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": "def502004a3f5e...",
      "expires_in": 3600,
      "user_type": "staff"
    }
  }
}
```

***

### 2.2 Merchant Login (Legacy)

**Permissions**: None (public)

**Request**:

```graphql theme={null}
mutation MerchantLogin($email: String!, $password: String!) {
  merchantLogin(email: $email, password: $password) {
    access_token
    token_type
  }
}
```

**Variables**:

```json theme={null}
{
  "email": "merchant@example.com",
  "password": "SecurePass123!"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "merchantLogin": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "token_type": "Bearer"
    }
  }
}
```

***

### 2.3 Staff Login (Legacy)

**Permissions**: None (public)

**Request**:

```graphql theme={null}
mutation StaffLogin($email: String!, $password: String!) {
  staffLogin(email: $email, password: $password) {
    access_token
    refresh_token
    token_type
    expires_in
  }
}
```

**Variables**:

```json theme={null}
{
  "email": "staff@example.com",
  "password": "SecurePass123!"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "staffLogin": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": "def502004a3f5e...",
      "token_type": "Bearer",
      "expires_in": 3600
    }
  }
}
```

***

### 2.4 Refresh Token

Get a new access token without re-entering credentials.

**Permissions**: None (public, but requires valid refresh token)

**Request**:

```graphql theme={null}
mutation RefreshToken($refreshToken: String!) {
  refreshToken(refreshToken: $refreshToken) {
    access_token
    refresh_token
    token_type
    expires_in
  }
}
```

**Variables**:

```json theme={null}
{
  "refreshToken": "def502004a3f5e..."
}
```

**Response**:

```json theme={null}
{
  "data": {
    "refreshToken": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": "abc123004b4g6f...",
      "token_type": "Bearer",
      "expires_in": 3600
    }
  }
}
```

***

### 2.5 Request Password Reset

**Permissions**: None (public)

**Request**:

```graphql theme={null}
mutation RequestPasswordReset($email: String!) {
  requestPasswordReset(email: $email)
}
```

**Variables**:

```json theme={null}
{
  "email": "user@example.com"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "requestPasswordReset": true
  }
}
```

***

### 2.6 Reset Password

**Permissions**: None (public, but requires valid token)

**Request**:

```graphql theme={null}
mutation ResetPassword($token: String!, $newPassword: String!) {
  resetPassword(token: $token, newPassword: $newPassword)
}
```

**Variables**:

```json theme={null}
{
  "token": "abc123def456...",
  "newPassword": "NewSecurePass123!"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "resetPassword": true
  }
}
```

***

### 2.7 Get Current User (Me)

**Permissions**: Requires authentication

**Request**:

```graphql theme={null}
query Me {
  me {
    ... on Merchant {
      id
      accountNumber
      email
      name
      merchant_category
      wallets {
        id
        address
        blockchain
        nickname
      }
    }
    ... on Staff {
      id
      email
      display_name
      roles
      is_active
      merchant {
        id
        name
      }
    }
  }
}
```

**Response** (Merchant):

```json theme={null}
{
  "data": {
    "me": {
      "id": "1",
      "accountNumber": "9891123456789012",
      "email": "merchant@example.com",
      "name": "Coffee Shop",
      "merchant_category": "Food & Beverage",
      "wallets": [
        {
          "id": "1",
          "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
          "blockchain": "solana-devnet",
          "nickname": "Main Wallet"
        }
      ]
    }
  }
}
```

**Response** (Staff):

```json theme={null}
{
  "data": {
    "me": {
      "id": "5",
      "email": "cashier@example.com",
      "display_name": "John Doe",
      "roles": ["Cashier"],
      "is_active": true,
      "merchant": {
        "id": "1",
        "name": "Coffee Shop"
      }
    }
  }
}
```

***

## 3. Merchant Operations

### 3.1 Create Merchant (Sign Up)

**Permissions**: None (public)

**Request**:

```graphql theme={null}
mutation CreateMerchant($name: String!, $email: String!, $password: String!) {
  createMerchant(name: $name, email: $email, password: $password) {
    id
    accountNumber
    name
    email
  }
}
```

**Variables**:

```json theme={null}
{
  "name": "My Coffee Shop",
  "email": "owner@coffeeshop.com",
  "password": "SecurePass123!"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "createMerchant": {
      "id": "1",
      "accountNumber": "9891123456789012",
      "name": "My Coffee Shop",
      "email": "owner@coffeeshop.com"
    }
  }
}
```

***

### 3.2 Update Merchant Profile

**Permissions**: `merchant.profile.manage` (Merchant only)

**Request**:

```graphql theme={null}
mutation UpdateMerchantProfile($profile: MerchantProfileInput!) {
  updateMerchantProfile(profile: $profile) {
    id
    accountNumber
    name
    email
    tax_percentage
    merchant_category
    address {
      address_line
      city
      state
      country
    }
  }
}
```

**Variables**:

```json theme={null}
{
  "profile": {
    "name": "Updated Coffee Shop",
    "tax_percentage": 8.5,
    "merchant_category": "Food & Beverage",
    "address": {
      "address_line": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "country": "USA"
    },
    "pricing_model_id": 2
  }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "updateMerchantProfile": {
      "id": "1",
      "accountNumber": "9891123456789012",
      "name": "Updated Coffee Shop",
      "email": "owner@coffeeshop.com",
      "tax_percentage": 8.5,
      "merchant_category": "Food & Beverage",
      "address": {
        "address_line": "123 Main St",
        "city": "San Francisco",
        "state": "CA",
        "country": "USA"
      }
    }
  }
}
```

***

### 3.3 Merchant Account Number

Every merchant is automatically assigned a unique 16-digit account number upon creation.

**Format**: `9891` + 12 random digits

**Example**: `9891123456789012`

**Features**:

* **Automatically Generated**: Created when a merchant signs up
* **Unique**: Enforced by database constraint
* **Immutable**: Cannot be changed after creation
* **Format Validation**: Checked via database constraint (`^9891[0-9]{12}$`)

**Use Cases**:

* Merchant identification
* Account reconciliation
* Integration with external systems
* Customer invoicing

**Querying Account Number**:

```graphql theme={null}
query Me {
  me {
    ... on Merchant {
      id
      accountNumber
      name
      email
    }
  }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "me": {
      "id": "1",
      "accountNumber": "9891123456789012",
      "name": "My Coffee Shop",
      "email": "owner@coffeeshop.com"
    }
  }
}
```

***

## 4. Staff Management

### 4.1 Create Staff

**Permissions**: `staff.manage` (Merchant or Manager with staff.manage permission)

**Request**:

```graphql theme={null}
mutation CreateStaff(
  $merchantId: ID
  $email: String!
  $password: String!
  $displayName: String
  $roles: [String!]!
) {
  createStaff(
    merchantId: $merchantId
    email: $email
    password: $password
    displayName: $displayName
    roles: $roles
  ) {
    id
    email
    display_name
    roles
    is_active
  }
}
```

**Variables**:

```json theme={null}
{
  "merchantId": "1",
  "email": "cashier@coffeeshop.com",
  "password": "SecurePass123!",
  "displayName": "Jane Smith",
  "roles": ["Cashier"]
}
```

**Response**:

```json theme={null}
{
  "data": {
    "createStaff": {
      "id": "5",
      "email": "cashier@coffeeshop.com",
      "display_name": "Jane Smith",
      "roles": ["Cashier"],
      "is_active": true
    }
  }
}
```

> **Note**: The `merchantId` parameter is **optional**:
>
> * If **omitted**, the merchant ID is automatically inferred from the authenticated user (works for both Merchant and Staff users)
> * If **provided**, it must match the authenticated user's merchant ID (staff cannot create users for other merchants)
> * Can be passed as either a **string** (`"1"`) or **number** (`1`) - both are accepted

***

### 4.2 List Staff

**Permissions**: `staff.list`

**Request**:

```graphql theme={null}
query StaffList($merchantId: ID!) {
  staffList(merchantId: $merchantId) {
    id
    email
    display_name
    roles
    is_active
  }
}
```

**Variables**:

```json theme={null}
{
  "merchantId": "1"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "staffList": [
      {
        "id": "5",
        "email": "cashier@coffeeshop.com",
        "display_name": "Jane Smith",
        "roles": ["Cashier"],
        "is_active": true
      },
      {
        "id": "6",
        "email": "manager@coffeeshop.com",
        "display_name": "Bob Johnson",
        "roles": ["Manager"],
        "is_active": true
      }
    ]
  }
}
```

***

### 4.3 Update Staff

**Permissions**: `staff.manage`

**Request**:

```graphql theme={null}
mutation UpdateStaff(
  $id: ID!
  $displayName: String
  $roles: [String!]
  $isActive: Boolean
) {
  updateStaff(
    id: $id
    displayName: $displayName
    roles: $roles
    isActive: $isActive
  ) {
    id
    email
    display_name
    roles
    is_active
  }
}
```

**Variables**:

```json theme={null}
{
  "id": "5",
  "displayName": "Jane Doe",
  "roles": ["Manager"],
  "isActive": true
}
```

**Response**:

```json theme={null}
{
  "data": {
    "updateStaff": {
      "id": "5",
      "email": "cashier@coffeeshop.com",
      "display_name": "Jane Doe",
      "roles": ["Manager"],
      "is_active": true
    }
  }
}
```

***

### 4.4 Deactivate Staff

**Permissions**: `staff.manage`

**Request**:

```graphql theme={null}
mutation DeactivateStaff($id: ID!) {
  deactivateStaff(id: $id)
}
```

**Variables**:

```json theme={null}
{
  "id": "5"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "deactivateStaff": true
  }
}
```

***

### 4.5 Assign Role to Staff

**Permissions**: `staff.manage`

**Request**:

```graphql theme={null}
mutation AssignRoleToStaff($staffId: ID!, $roleName: String!) {
  assignRoleToStaff(staffId: $staffId, roleName: $roleName) {
    id
    email
    roles
  }
}
```

**Variables**:

```json theme={null}
{
  "staffId": "5",
  "roleName": "Manager"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "assignRoleToStaff": {
      "id": "5",
      "email": "cashier@coffeeshop.com",
      "roles": ["Manager", "Cashier"]
    }
  }
}
```

***

### 4.6 Get Merchant Info for Staff

**Permissions**: Requires authentication

**Request**:

```graphql theme={null}
query GetMerchantInfoForStaff($staffEmail: String!) {
  getMerchantInfoForStaff(staffEmail: $staffEmail) {
    merchantId
    accountNumber
  }
}
```

**Variables**:

```json theme={null}
{
  "staffEmail": "cashier@coffeeshop.com"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "getMerchantInfoForStaff": {
      "merchantId": "1",
      "accountNumber": "9891123456789012"
    }
  }
}
```

***

## 5. Payment Operations

### 5.1 Create Payment

**Permissions**: `payment.create`

**Request**:

```graphql theme={null}
mutation CreatePayment(
  $amount: Decimal!
  $tipAmount: Decimal
  $blockchain: String!
  $tokenSymbol: String!
) {
  createPayment(
    amount: $amount
    tipAmount: $tipAmount
    blockchain: $blockchain
    tokenSymbol: $tokenSymbol
  ) {
    url
    qr_code
    transaction {
      id
      reference
      amount
      tip_amount
      status
      timestamp
      tokenSymbol
    }
  }
}
```

**Variables**:

```json theme={null}
{
  "amount": "25.50",
  "blockchain": "solana-devnet",
  "tokenSymbol": "USDC"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "createPayment": {
      "url": "solana:DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK?amount=25.50&spl-token=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU&reference=abc123...",
      "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
      "transaction": {
        "id": "42",
        "reference": "abc123def456...",
        "amount": "25.50",
        "status": "pending",
        "timestamp": "2024-01-15T10:30:00Z",
        "tokenSymbol": "USDC"
      }
    }
  }
}
```

***

### 5.2 Regenerate Payment

**Permissions**: `payment.create`

**Request**:

```graphql theme={null}
mutation RegeneratePayment($reference: String!) {
  regeneratePayment(reference: $reference) {
    url
    qr_code
    transaction {
      id
      reference
      amount
      status
    }
  }
}
```

**Variables**:

```json theme={null}
{
  "reference": "abc123def456..."
}
```

**Response**:

```json theme={null}
{
  "data": {
    "regeneratePayment": {
      "url": "solana:DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK?amount=25.50...",
      "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
      "transaction": {
        "id": "42",
        "reference": "abc123def456...",
        "amount": "25.50",
        "status": "pending"
      }
    }
  }
}
```

***

### 5.3 Create Payment Link

Creates a **shareable payment link**: a pending transaction, invoice, and a unique slug. The returned `url` is the public checkout URL (e.g. `https://your-domain.com/pay/<slug>`). No authentication is required for customers to open that URL and pay.

**Permissions**: `payment.create`

**Request**:

```graphql theme={null}
mutation CreatePaymentLink(
  $amount: Decimal!
  $tokenSymbol: String!
  $blockchain: String!
  $tipAmount: Decimal
  $description: String
  $expiresInHours: Int
) {
  createPaymentLink(
    amount: $amount
    tokenSymbol: $tokenSymbol
    blockchain: $blockchain
    tipAmount: $tipAmount
    description: $description
    expiresInHours: $expiresInHours
  ) {
    url
    slug
    transaction {
      id
      reference
      amount
      tip_amount
      status
      tokenSymbol
    }
  }
}
```

**Variables**:

```json theme={null}
{
  "amount": "25.50",
  "tokenSymbol": "USDC",
  "blockchain": "solana-devnet",
  "tipAmount": "2.00",
  "description": "Order #1234",
  "expiresInHours": 24
}
```

**Response**:

```json theme={null}
{
  "data": {
    "createPaymentLink": {
      "url": "https://your-domain.com/pay/a1b2c3d4e5f6",
      "slug": "a1b2c3d4e5f6",
      "transaction": {
        "id": "101",
        "reference": "8Kj3mNxPq7Rs...",
        "amount": "25.50",
        "tip_amount": "2.00",
        "status": "pending",
        "tokenSymbol": "USDC"
      }
    }
  }
}
```

* **url**: Public URL to share with the customer. Opening it returns checkout JSON (see 5.4).
* **slug**: Unique identifier for the link (also used in `url`).
* **expiresInHours**: Optional. If set, the link is considered expired after that many hours.

***

### 5.4 Get Payment Link Checkout (Public)

**Authentication**: None (public endpoint for customers).

Returns JSON for the checkout UI: merchant name, amount, token, network, payment URL (for QR / Solana Pay), and transaction reference. Used when a customer opens a payment link (e.g. `https://your-domain.com/pay/<slug>`).

**Endpoint**:

```
GET https://your-domain.com/pay/{slug}
```

**Example**:

```bash theme={null}
curl https://your-domain.com/pay/a1b2c3d4e5f6
```

**Response** (200 OK):

```json theme={null}
{
  "merchant_name": "Acme Coffee",
  "amount": "25.50",
  "tip_amount": "2.00",
  "token_symbol": "USDC",
  "network": "solana-devnet",
  "payment_url": "solana:DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK?amount=27.50&...",
  "transaction_reference": "8Kj3mNxPq7Rs...",
  "slug": "a1b2c3d4e5f6"
}
```

* **payment\_url**: Use this for QR code or "Pay with Solana" / wallet deep link.
* **transaction\_reference**: Use with `transactionStatus(reference: ...)` to poll for confirmation.

**Errors**:

* **404**: Payment link not found, expired, or already used (transaction confirmed).

***

## 6. Transaction & Reporting

### 6.1 List Transactions

**Permissions**: `transaction.view`

**Request**:

```graphql theme={null}
query Transactions($limit: Int, $offset: Int) {
  transactions(limit: $limit, offset: $offset) {
    transactions {
      id
      reference
      amount
      status
      timestamp
      tokenSymbol
      transaction_hash
    }
    totalCount
  }
}
```

**Variables**:

```json theme={null}
{
  "limit": 10,
  "offset": 0
}
```

**Response**:

```json theme={null}
{
  "data": {
    "transactions": {
      "transactions": [
        {
          "id": "42",
          "reference": "abc123def456...",
          "amount": "25.50",
          "status": "confirmed",
          "timestamp": "2024-01-15T10:30:00Z",
          "tokenSymbol": "USDC",
          "transaction_hash": "5g4h8j9k2l3m4n5o6p7q8r9s0t1u2v3w..."
        }
      ],
      "totalCount": 42
    }
  }
}
```

***

### 6.2 Transaction Status

**Permissions**: `transaction.view`

**Request**:

```graphql theme={null}
query TransactionStatus($reference: String!) {
  transactionStatus(reference: $reference) {
    id
    reference
    amount
    status
    timestamp
    transaction_hash
  }
}
```

**Variables**:

```json theme={null}
{
  "reference": "abc123def456..."
}
```

**Response**:

```json theme={null}
{
  "data": {
    "transactionStatus": {
      "id": "42",
      "reference": "abc123def456...",
      "amount": "25.50",
      "status": "confirmed",
      "timestamp": "2024-01-15T10:30:00Z",
      "transaction_hash": "5g4h8j9k2l3m4n5o6p7q8r9s0t1u2v3w..."
    }
  }
}
```

***

### 6.3 Daily Summary

**Permissions**: `report.view`

**Request**:

```graphql theme={null}
query DailySummary($timeframe: ReportTimeframe) {
  dailySummary(timeframe: $timeframe) {
    date
    totalSales
    transactionCount
    averageTransactionValue
  }
}
```

**Variables**:

```json theme={null}
{
  "timeframe": "DAILY"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "dailySummary": [
      {
        "date": "2024-01-15",
        "totalSales": "1250.00",
        "transactionCount": 42,
        "averageTransactionValue": "29.76"
      }
    ]
  }
}
```

***

### 6.4 Sales Summary Report

**Permissions**: `report.view`

**Request**:

```graphql theme={null}
query SalesSummaryReport(
  $timeframe: ReportTimeframe!
  $startDate: String
  $endDate: String
) {
  salesSummaryReport(
    timeframe: $timeframe
    startDate: $startDate
    endDate: $endDate
  ) {
    totalSales
    transactionCount
    averageTransactionValue
    reportPeriod
  }
}
```

**Variables**:

```json theme={null}
{
  "timeframe": "MONTHLY",
  "startDate": "2024-01-01",
  "endDate": "2024-01-31"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "salesSummaryReport": {
      "totalSales": "12500.00",
      "transactionCount": 420,
      "averageTransactionValue": "29.76",
      "reportPeriod": "2024-01 (Monthly)"
    }
  }
}
```

***

### 6.5 Sales Analytics

**Permissions**: `report.view`

**Request**:

```graphql theme={null}
query SalesAnalytics($timeframe: String!) {
  salesAnalytics(timeframe: $timeframe) {
    date
    totalSales
  }
}
```

**Variables**:

```json theme={null}
{
  "timeframe": "weekly"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "salesAnalytics": [
      {
        "date": "2024-01-15",
        "totalSales": "1250.00"
      },
      {
        "date": "2024-01-16",
        "totalSales": "1580.50"
      }
    ]
  }
}
```

***

### 6.6 Sales By Hour

**Permissions**: `report.view`

**Request**:

```graphql theme={null}
query SalesByHour {
  salesByHour {
    hour
    totalSales
  }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "salesByHour": [
      {
        "hour": 9,
        "totalSales": "150.00"
      },
      {
        "hour": 10,
        "totalSales": "320.50"
      }
    ]
  }
}
```

***

### 6.7 PDF Downloads (Invoices & Receipts)

StableX provides two types of PDF documents:

* **Invoices**: For pending/unpaid transactions (blue header, red "PAYMENT PENDING" stamp)
* **Receipts**: For confirmed/paid transactions (green header, green "PAID" stamp)

#### 6.7.1 Get Invoice Info (GraphQL)

Get the download URL and QR code for an invoice PDF.

**Permissions**: Authenticated (merchant or staff)

**Request**:

```graphql theme={null}
query GetInvoiceInfo($reference: String!) {
  getInvoiceInfo(reference: $reference) {
    url
    qr_code
  }
}
```

**Variables**:

```json theme={null}
{
  "reference": "abc123def456..."
}
```

**Response**:

```json theme={null}
{
  "data": {
    "getInvoiceInfo": {
      "url": "https://your-domain.com/invoices/abc123def456.../download",
      "qr_code": "iVBORw0KGgoAAAANSUhEUgAA..." 
    }
  }
}
```

#### 6.7.2 Get Receipt Info (GraphQL)

Get the download URL and QR code for a receipt PDF.

**Permissions**: Authenticated (merchant or staff)

**Request**:

```graphql theme={null}
query GetReceiptInfo($reference: String!) {
  getReceiptInfo(reference: $reference) {
    url
    qr_code
  }
}
```

**Variables**:

```json theme={null}
{
  "reference": "abc123def456..."
}
```

**Response**:

```json theme={null}
{
  "data": {
    "getReceiptInfo": {
      "url": "https://your-domain.com/receipts/abc123def456.../download",
      "qr_code": "iVBORw0KGgoAAAANSUhEUgAA..."
    }
  }
}
```

#### 6.7.3 Download Invoice PDF (REST API)

Download the invoice PDF directly.

**Endpoint**: `GET /invoices/{transaction_reference}/download`

**Authentication**: Bearer token required

**Request**:

```bash theme={null}
curl -X GET https://your-domain.com/invoices/abc123def456.../download \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -o invoice.pdf
```

**Response**: PDF file (application/pdf)

**Status Codes**:

* `200 OK`: PDF generated successfully
* `404 Not Found`: Transaction not found
* `401 Unauthorized`: Invalid or missing token
* `500 Internal Server Error`: PDF generation failed

#### 6.7.4 Download Receipt PDF (REST API)

Download the receipt PDF directly (only for confirmed transactions).

**Endpoint**: `GET /receipts/{transaction_reference}/download`

**Authentication**: Bearer token required

**Request**:

```bash theme={null}
curl -X GET https://your-domain.com/receipts/abc123def456.../download \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -o receipt.pdf
```

**Response**: PDF file (application/pdf)

**Status Codes**:

* `200 OK`: PDF generated successfully
* `404 Not Found`: Transaction not found or not confirmed
* `401 Unauthorized`: Invalid or missing token
* `500 Internal Server Error`: PDF generation failed

**Note**: Receipts can only be downloaded for transactions with status `confirmed`. For pending transactions, use the invoice endpoint instead.

#### 6.7.5 Invoice Query with Transaction Reference

When querying invoices, include the transaction relationship to get the reference needed for PDF downloads. Invoices can be in lifecycle states: `draft`, `sent`, `pending`, `paid`, `overdue`, or `void`. Draft and sent invoices may have `transaction_id: null` until the customer pays.

**Request**:

```graphql theme={null}
query GetInvoices($limit: Int, $offset: Int) {
  invoices(limit: $limit, offset: $offset) {
    invoices {
      id
      amount
      tax_amount
      status
      creation_date
      invoice_number
      due_date
      customer_email
      sent_at
      last_reminder_at
      transaction_id
      transaction {
        reference
      }
      line_items {
        id
        description
        quantity
        unit_amount
        amount
      }
    }
    totalCount
  }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "invoices": {
      "invoices": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440000",
          "amount": "100.00",
          "tax_amount": "10.00",
          "status": "draft",
          "creation_date": "2024-01-15T10:30:00Z",
          "invoice_number": "INV-0001",
          "due_date": "2024-02-01T00:00:00Z",
          "customer_email": "customer@example.com",
          "sent_at": null,
          "last_reminder_at": null,
          "transaction_id": null,
          "transaction": null,
          "line_items": [
            {
              "id": "1",
              "description": "Item A",
              "quantity": "2",
              "unit_amount": "25.00",
              "amount": "50.00"
            }
          ]
        }
      ],
      "totalCount": 1
    }
  }
}
```

**Usage**: For paid (or sent) invoices with a transaction, use `transaction.reference` to construct the download URL:

```
/invoices/{transaction.reference}/download
```

***

### 6.8 Invoice Lifecycle (Draft, Send, Remind)

Invoices support a full lifecycle: create as **draft**, **send** to the customer (email + payment link), then **paid** when the customer pays. Optional **overdue** status and **reminders** are supported.

**Invoice statuses**: `draft` | `sent` | `pending` | `paid` | `overdue` | `void`

* **draft**: Created without a transaction; can be updated.
* **sent**: Payment link created and (optionally) email sent; `transaction_id` and `sent_at` set.
* **pending**: Legacy: invoice tied to a transaction not yet confirmed.
* **paid**: Payment confirmed; `payment_date` set.
* **overdue**: `due_date` has passed and status was `sent`.
* **void**: Cancelled.

#### 6.8.1 Create Invoice (Draft)

Create an invoice in **draft** state with no transaction. Optionally include line items, due date, and customer email (required later for sending).

**Permissions**: Authenticated (merchant or staff)

**Request**:

```graphql theme={null}
mutation CreateInvoice(
  $amount: Decimal!
  $taxAmount: Decimal!
  $description: String
  $dueDate: String
  $customerEmail: String
  $lineItems: [InvoiceLineItemInput!]
) {
  createInvoice(
    amount: $amount
    taxAmount: $taxAmount
    description: $description
    dueDate: $dueDate
    customerEmail: $customerEmail
    lineItems: $lineItems
  ) {
    id
    status
    amount
    tax_amount
    invoice_number
    due_date
    customer_email
    transaction_id
    line_items {
      id
      description
      quantity
      unit_amount
      amount
    }
  }
}
```

**Variables**:

```json theme={null}
{
  "amount": "150.00",
  "taxAmount": "15.00",
  "description": "Consulting services",
  "dueDate": "2025-03-15T00:00:00Z",
  "customerEmail": "client@example.com",
  "lineItems": [
    {
      "description": "Hourly consulting",
      "quantity": "10",
      "unit_amount": "15.00",
      "amount": "150.00"
    }
  ]
}
```

**Response**:

```json theme={null}
{
  "data": {
    "createInvoice": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "draft",
      "amount": "150.00",
      "tax_amount": "15.00",
      "invoice_number": "INV-0001",
      "due_date": "2025-03-15T00:00:00Z",
      "customer_email": "client@example.com",
      "transaction_id": null,
      "line_items": [
        {
          "id": "1",
          "description": "Hourly consulting",
          "quantity": "10",
          "unit_amount": "15.00",
          "amount": "150.00"
        }
      ]
    }
  }
}
```

**Input type `InvoiceLineItemInput`**:

| Field        | Type     | Required | Description                  |
| ------------ | -------- | -------- | ---------------------------- |
| description  | String!  | Yes      | Line item description        |
| quantity     | Decimal! | Yes      | Quantity                     |
| unit\_amount | Decimal! | Yes      | Price per unit               |
| amount       | Decimal! | Yes      | Total (e.g. quantity × unit) |
| productId    | ID       | No       | Optional link to product     |

***

#### 6.8.2 Update Invoice (Draft Only)

Update a **draft** invoice: amount, tax, description, due date, customer email, and/or line items. Replacing `lineItems` overwrites all existing line items.

**Permissions**: Authenticated (merchant or staff)

**Request**:

```graphql theme={null}
mutation UpdateInvoice(
  $invoiceId: ID!
  $amount: Decimal
  $taxAmount: Decimal
  $description: String
  $dueDate: String
  $customerEmail: String
  $lineItems: [InvoiceLineItemInput!]
) {
  updateInvoice(
    invoiceId: $invoiceId
    amount: $amount
    taxAmount: $taxAmount
    description: $description
    dueDate: $dueDate
    customerEmail: $customerEmail
    lineItems: $lineItems
  ) {
    id
    status
    amount
    description
    due_date
    customer_email
    line_items { id description quantity unit_amount amount }
  }
}
```

**Variables** (example: update amount and due date only):

```json theme={null}
{
  "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
  "amount": "200.00",
  "dueDate": "2025-03-20T00:00:00Z"
}
```

**Response**: Returns the updated `Invoice` object. If the invoice is not in `draft` status, an error is returned.

***

#### 6.8.3 Send Invoice

Moves an invoice from **draft** to **sent**: creates a pending transaction and payment link, sets `sent_at`, and optionally sends an email to `customer_email` with the payment URL. The customer can open the link to pay; when payment is confirmed, the invoice is marked **paid**.

**Permissions**: Authenticated (merchant or staff)

**Requirements**: Invoice must be **draft** and have `customer_email` set. You must have a wallet for the given `blockchain`.

**Request**:

```graphql theme={null}
mutation SendInvoice(
  $invoiceId: ID!
  $blockchain: String!
  $tokenSymbol: String!
) {
  sendInvoice(
    invoiceId: $invoiceId
    blockchain: $blockchain
    tokenSymbol: $tokenSymbol
  ) {
    success
    message
    url
    invoice {
      id
      status
      sent_at
      transaction_id
    }
  }
}
```

**Variables**:

```json theme={null}
{
  "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
  "blockchain": "solana-devnet",
  "tokenSymbol": "USDC"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "sendInvoice": {
      "success": true,
      "message": "Invoice sent",
      "url": "https://your-domain.com/pay/a1b2c3d4e5f6",
      "invoice": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "status": "sent",
        "sent_at": "2025-02-11T14:30:00Z",
        "transaction_id": "42"
      }
    }
  }
}
```

**Notes**:

* `url` is the public payment link; share it or rely on the email sent to `customer_email`.
* If the email fails, the invoice is still marked **sent** and the mutation succeeds; the `url` can be shared manually.

***

#### 6.8.4 Send Invoice Reminder

Sends a reminder email for an invoice in **sent** or **overdue** status and sets `last_reminder_at`. The email includes the same payment link as when the invoice was sent.

**Permissions**: Authenticated (merchant or staff)

**Request**:

```graphql theme={null}
mutation SendInvoiceReminder($invoiceId: ID!) {
  sendInvoiceReminder(invoiceId: $invoiceId) {
    id
    status
    last_reminder_at
  }
}
```

**Variables**:

```json theme={null}
{
  "invoiceId": "550e8400-e29b-41d4-a716-446655440000"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "sendInvoiceReminder": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "overdue",
      "last_reminder_at": "2025-02-11T15:00:00Z"
    }
  }
}
```

**Errors**: Invoice must be in `sent` or `overdue` and have `customer_email`; the invoice must have an associated payment link (transaction).

***

#### 6.8.5 Sync Invoice Status

Manually sync an invoice’s status with its linked transaction (e.g. after payment confirmation). Useful if automatic status updates did not run. The invoice must already have a `transaction_id` (e.g. created via **sendInvoice** or a payment link).

**Permissions**: Authenticated (merchant or staff)

**Request**:

```graphql theme={null}
mutation SyncInvoiceStatus($invoiceId: ID!) {
  syncInvoiceStatus(invoiceId: $invoiceId) {
    id
    status
    payment_date
    transaction_id
  }
}
```

**Variables**:

```json theme={null}
{
  "invoiceId": "550e8400-e29b-41d4-a716-446655440000"
}
```

**Response**: Returns the updated `Invoice`. If the linked transaction is confirmed on-chain, the invoice is updated to `paid` and `payment_date` is set.

***

## 7. Wallet Management

### 7.1 Add or Update Wallet

**Permissions**: `wallet.manage`

**Request**:

```graphql theme={null}
mutation AddOrUpdateWallet(
  $address: String!
  $blockchain: String!
  $nickname: String
) {
  addOrUpdateWallet(
    address: $address
    blockchain: $blockchain
    nickname: $nickname
  ) {
    id
    address
    blockchain
    nickname
  }
}
```

**Variables**:

```json theme={null}
{
  "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
  "blockchain": "solana-devnet",
  "nickname": "Main Solana Wallet"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "addOrUpdateWallet": {
      "id": "1",
      "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
      "blockchain": "solana-devnet",
      "nickname": "Main Solana Wallet"
    }
  }
}
```

***

### 7.2 Remove Wallet

**Permissions**: `wallet.manage`

**Request**:

```graphql theme={null}
mutation RemoveWallet($walletId: ID!) {
  removeWallet(walletId: $walletId)
}
```

**Variables**:

```json theme={null}
{
  "walletId": "1"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "removeWallet": true
  }
}
```

***

### 7.3 Wallet Balances

**Permissions**: `wallet.view`

**Request**:

```graphql theme={null}
query WalletBalances {
  walletBalances {
    address
    blockchain
    nickname
    totalBalance
    breakdown {
      USDC
      USDT
    }
  }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "walletBalances": [
      {
        "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
        "blockchain": "solana-devnet",
        "nickname": "Main Wallet",
        "totalBalance": "1250.50",
        "breakdown": {
          "USDC": "850.00",
          "USDT": "400.50"
        }
      }
    ]
  }
}
```

***

### 7.4 Token Balances

**Permissions**: `wallet.view`

**Request**:

```graphql theme={null}
query TokenBalances(
  $blockchain: String!
  $walletAddress: String!
  $tokenSymbols: [String!]!
) {
  tokenBalances(
    blockchain: $blockchain
    walletAddress: $walletAddress
    tokenSymbols: $tokenSymbols
  ) {
    tokenSymbol
    balance
  }
}
```

**Variables**:

```json theme={null}
{
  "blockchain": "solana-devnet",
  "walletAddress": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
  "tokenSymbols": ["USDC", "USDT"]
}
```

**Response**:

```json theme={null}
{
  "data": {
    "tokenBalances": [
      {
        "tokenSymbol": "USDC",
        "balance": "850.00"
      },
      {
        "tokenSymbol": "USDT",
        "balance": "400.50"
      }
    ]
  }
}
```

***

## 8. Customer Management

### 8.1 List Customers

**Permissions**: `customer.view`

**Request**:

```graphql theme={null}
query Customers {
  customers {
    id
    wallet_address
    name
    email
    phone_number
  }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "customers": [
      {
        "id": "10",
        "wallet_address": "7xKs9mPqR8TuV3Wy0Zd5AcYb2MeFiHgF7Vj6Pu9Xq1Rs4",
        "name": "John Doe",
        "email": "john@example.com",
        "phone_number": "+1234567890"
      }
    ]
  }
}
```

***

### 8.2 Upsert Customer

**Permissions**: `customer.manage`

**Request**:

```graphql theme={null}
mutation UpsertCustomer(
  $wallet_address: String!
  $details: CustomerUpdate!
) {
  upsertCustomer(
    wallet_address: $wallet_address
    details: $details
  ) {
    id
    wallet_address
    name
    email
    phone_number
  }
}
```

**Variables**:

```json theme={null}
{
  "wallet_address": "7xKs9mPqR8TuV3Wy0Zd5AcYb2MeFiHgF7Vj6Pu9Xq1Rs4",
  "details": {
    "name": "Jane Doe",
    "email": "jane@example.com",
    "phone_number": "+1987654321"
  }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "upsertCustomer": {
      "id": "11",
      "wallet_address": "7xKs9mPqR8TuV3Wy0Zd5AcYb2MeFiHgF7Vj6Pu9Xq1Rs4",
      "name": "Jane Doe",
      "email": "jane@example.com",
      "phone_number": "+1987654321"
    }
  }
}
```

***

## 9. Error Handling

### 9.1 Error Response Format

```json theme={null}
{
  "errors": [
    {
      "message": "Access denied. Required permission: payment.create",
      "extensions": {
        "code": "PERMISSION_DENIED",
        "details": {
          "required_permission": "payment.create"
        }
      }
    }
  ]
}
```

### 9.2 Standard Error Codes

| Code                  | HTTP Status | Description                    | Action                     |
| --------------------- | ----------- | ------------------------------ | -------------------------- |
| `NOT_AUTHENTICATED`   | 401         | Missing or invalid token       | Redirect to login          |
| `PERMISSION_DENIED`   | 403         | User lacks required permission | Show access denied message |
| `INVALID_CREDENTIALS` | 401         | Wrong email/password           | Show login error           |
| `TOKEN_EXPIRED`       | 401         | Access token expired           | Call `refreshToken`        |
| `TOKEN_REVOKED`       | 401         | Refresh token revoked          | Redirect to login          |
| `RECORD_NOT_FOUND`    | 404         | Resource doesn't exist         | Show 404 state             |
| `VALIDATION_ERROR`    | 400         | Invalid input data             | Show validation errors     |
| `INTERNAL_ERROR`      | 500         | Server error                   | Show generic error         |

### 9.3 Common Error Scenarios

**1. Expired Token**

```json theme={null}
{
  "errors": [{
    "message": "Not authenticated",
    "extensions": { "code": "NOT_AUTHENTICATED" }
  }]
}
```

**Solution**: Call `refreshToken` mutation or redirect to login

**2. Permission Denied**

```json theme={null}
{
  "errors": [{
    "message": "Access denied. Required permission: staff.manage",
    "extensions": {
      "code": "PERMISSION_DENIED",
      "details": { "required_permission": "staff.manage" }
    }
  }]
}
```

**Solution**: Show user-friendly "You don't have permission" message

**3. Validation Error**

```json theme={null}
{
  "errors": [{
    "message": "Email already registered",
    "extensions": {
      "code": "VALIDATION_ERROR",
      "details": { "field": "email" }
    }
  }]
}
```

**Solution**: Highlight the email field and show error

***

## 10. Refund Operations

### 10.1 Create Refund Request

**Permissions**: `refund.create` (Staff Manager)

**Request**:

```graphql theme={null}
mutation CreateRefund($transactionReference: String!, $refundAmount: Decimal!, $reason: String!) {
    createRefundRequest(
        transactionReference: $transactionReference
        refundAmount: $refundAmount
        reason: $reason
    ) {
        id
        refundReference
        status
    }
}
```

**Variables**:

```json theme={null}
{
  "transactionReference": "TX_FULL_REFUND_UNIQUE_123",
  "refundAmount": "50.00",
  "reason": "Test reason"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "createRefundRequest": {
      "id": "1",
      "refundReference": "REF123",
      "status": "pending"
    }
  }
}
```

***

### 10.2 Approve Refund

**Permissions**: `refund.approve` (Staff Manager)

**Request**:

```graphql theme={null}
mutation ApproveRefund($refundReference: String!) {
    approveRefund(refundReference: $refundReference) {
        id
        status
    }
}
```

**Variables**:

```json theme={null}
{
  "refundReference": "REF_TEST_APPROVE"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "approveRefund": {
      "id": "1",
      "status": "approved"
    }
  }
}
```

***

### 10.3 Reject Refund

**Permissions**: `refund.reject` (Staff Manager)

**Request**:

```graphql theme={null}
mutation RejectRefund($refundReference: String!, $rejectionReason: String!) {
    rejectRefund(refundReference: $refundReference, rejectionReason: $rejectionReason) {
        id
        status
    }
}
```

**Variables**:

```json theme={null}
{
  "refundReference": "REF-123",
  "rejectionReason": "Invalid request"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "rejectRefund": {
      "id": "1",
      "status": "rejected"
    }
  }
}
```

***

### 10.4 Complete Refund

**Permissions**: `refund.complete` (Merchant)

**Request**:

```graphql theme={null}
mutation CompleteRefund($refundReference: String!, $transactionHash: String) {
    completeRefund(refundReference: $refundReference, transactionHash: $transactionHash) {
        id
        status
        refundTransactionHash
    }
}
```

**Variables**:

```json theme={null}
{
  "refundReference": "REF_COMPLETE_TEST",
  "transactionHash": "tx_hash_123"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "completeRefund": {
      "id": "1",
      "status": "completed",
      "refundTransactionHash": "tx_hash_123"
    }
  }
}
```

***

### 10.5 List Refunds

**Permissions**: `refund.view` (Staff Manager)

**Request**:

```graphql theme={null}
query Refunds($status: String) {
    refunds(status: $status) {
        refunds {
            id
            refundReference
            refundAmount
            status
        }
        totalCount
    }
}
```

**Variables** (optional):

```json theme={null}
{
  "status": "approved"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "refunds": {
      "refunds": [
        {
          "id": "6",
          "refundReference": "REF_FILTER_2",
          "refundAmount": "20.00",
          "status": "approved"
        }
      ],
      "totalCount": 1
    }
  }
}
```

***

### 10.6 Get Refund by Reference

**Permissions**: `refund.view` (Staff Manager)

**Request**:

```graphql theme={null}
query RefundByReference($reference: String!) {
    refundByReference(refundReference: $reference) {
        id
        refundReference
        status
    }
}
```

**Variables**:

```json theme={null}
{
  "reference": "REF_BY_REFERENCE"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "refundByReference": {
      "id": "100",
      "refundReference": "REF_BY_REFERENCE",
      "status": "pending"
    }
  }
}
```

***

### 10.7 Get Refund Summary

**Permissions**: `refund.view` (Staff Manager)

**Request**:

```graphql theme={null}
query RefundSummary {
    refundSummary {
        totalRefunded
        refundCount
        completedRefunds
    }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "refundSummary": {
      "totalRefunded": "50.00",
      "refundCount": 1,
      "completedRefunds": 1
    }
  }
}
```

***

## 11. Product & Price Management

### 11.1 List Products

**Permissions**: `product.view`

**Request**:

```graphql theme={null}
query GetProducts($active: Boolean, $category: String, $limit: Int, $offset: Int) {
    products(active: $active, category: $category, limit: $limit, offset: $offset) {
        products {
            id
            name
            description
            active
            sku
            productCode
            category
            tags
            archivedAt
            images
            createdAt
        }
        totalCount
    }
}
```

**Variables**:

```json theme={null}
{
  "active": true,
  "category": "Electronics",
  "limit": 10,
  "offset": 0
}
```

**Response**:

```json theme={null}
{
  "data": {
    "products": {
      "products": [
        {
          "id": "1",
          "name": "Premium Coffee Beans",
          "description": "High-quality arabica coffee beans",
          "active": true,
          "sku": "COFFEE-001",
          "productCode": "PC-001",
          "category": "Beverages",
          "tags": ["coffee", "premium", "arabica"],
          "archivedAt": null,
          "images": ["https://example.com/coffee.jpg"],
          "createdAt": "2024-01-15T10:30:00Z"
        }
      ],
      "totalCount": 25
    }
  }
}
```

> **Note**: The `active` filter can be used to show only active or inactive products. The `category` filter allows filtering by product category. Pagination is supported via `limit` and `offset` parameters.

***

### 11.2 Get Product by ID

**Permissions**: `product.view`

**Request**:

```graphql theme={null}
query GetProduct($id: ID!) {
    product(id: $id) {
        id
        merchantId
        name
        description
        active
        sku
        productCode
        category
        tags
        archivedAt
        images
        metadata
        prices {
            id
            amount
            currency
            type
            active
        }
        createdAt
        updatedAt
    }
}
```

**Variables**:

```json theme={null}
{
  "id": "1"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "product": {
      "id": "1",
      "merchantId": "1",
      "name": "Premium Coffee Beans",
      "description": "High-quality arabica coffee beans",
      "active": true,
      "sku": "COFFEE-001",
      "productCode": "PC-001",
      "category": "Beverages",
      "tags": ["coffee", "premium", "arabica"],
      "archivedAt": null,
      "images": ["https://example.com/coffee.jpg"],
      "metadata": {
        "origin": "Colombia",
        "roast": "Medium"
      },
      "prices": [
        {
          "id": "1",
          "amount": "29.99",
          "currency": "USD",
          "type": "ONE_TIME",
          "active": true
        }
      ],
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:30:00Z"
    }
  }
}
```

***

### 11.3 Get Product by SKU

**Permissions**: `product.view`

**Request**:

```graphql theme={null}
query GetProductBySku($sku: String!) {
    productBySku(sku: $sku) {
        id
        name
        description
        active
        sku
        productCode
        category
        tags
        archivedAt
    }
}
```

**Variables**:

```json theme={null}
{
  "sku": "COFFEE-001"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "productBySku": {
      "id": "1",
      "name": "Premium Coffee Beans",
      "description": "High-quality arabica coffee beans",
      "active": true,
      "sku": "COFFEE-001",
      "productCode": "PC-001",
      "category": "Beverages",
      "tags": ["coffee", "premium", "arabica"],
      "archivedAt": null
    }
  }
}
```

> **Note**: SKU must be unique per merchant. If a product with the given SKU is not found, an error will be returned.

***

### 11.4 Get Product Prices

**Permissions**: `product.view`

**Request**:

```graphql theme={null}
query GetProductPrices($productId: ID!) {
    productPrices(productId: $productId) {
        id
        amount
        currency
        type
        billingPeriod
        active
        nickname
        description
        pricingType
        minAmount
        maxAmount
        trialPeriodDays
        usageType
        unitLabel
        billingScheme
        taxBehavior
        taxCode
        createdAt
    }
}
```

**Variables**:

```json theme={null}
{
  "productId": "1"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "productPrices": [
      {
        "id": "1",
        "amount": "29.99",
        "currency": "USD",
        "type": "ONE_TIME",
        "billingPeriod": null,
        "active": true,
        "nickname": "Standard Price",
        "description": "One-time purchase price",
        "pricingType": "FIXED",
        "minAmount": null,
        "maxAmount": null,
        "trialPeriodDays": null,
        "usageType": null,
        "unitLabel": null,
        "billingScheme": null,
        "taxBehavior": "EXCLUSIVE",
        "taxCode": null,
        "createdAt": "2024-01-15T10:30:00Z"
      },
      {
        "id": "2",
        "amount": "9.99",
        "currency": "USD",
        "type": "RECURRING",
        "billingPeriod": "month",
        "active": true,
        "nickname": "Monthly Subscription",
        "description": "Monthly recurring subscription",
        "pricingType": "FIXED",
        "minAmount": null,
        "maxAmount": null,
        "trialPeriodDays": 7,
        "usageType": null,
        "unitLabel": null,
        "billingScheme": null,
        "taxBehavior": "EXCLUSIVE",
        "taxCode": null,
        "createdAt": "2024-01-15T10:30:00Z"
      }
    ]
  }
}
```

***

### 11.5 Create Product

**Permissions**: `product.manage`

**Request**:

```graphql theme={null}
mutation CreateProduct(
    $name: String!
    $description: String
    $active: Boolean
    $metadata: JSON
    $images: [String!]
    $sku: String
    $productCode: String
    $category: String
    $tags: [String!]
) {
    createProduct(
        name: $name
        description: $description
        active: $active
        metadata: $metadata
        images: $images
        sku: $sku
        productCode: $productCode
        category: $category
        tags: $tags
    ) {
        id
        name
        description
        active
        sku
        productCode
        category
        tags
        images
        createdAt
    }
}
```

**Variables**:

```json theme={null}
{
  "name": "Premium Coffee Beans",
  "description": "High-quality arabica coffee beans from Colombia",
  "active": true,
  "sku": "COFFEE-001",
  "productCode": "PC-001",
  "category": "Beverages",
  "tags": ["coffee", "premium", "arabica"],
  "images": ["https://example.com/coffee.jpg"],
  "metadata": {
    "origin": "Colombia",
    "roast": "Medium"
  }
}
```

**Response**:

```json theme={null}
{
  "data": {
    "createProduct": {
      "id": "1",
      "name": "Premium Coffee Beans",
      "description": "High-quality arabica coffee beans from Colombia",
      "active": true,
      "sku": "COFFEE-001",
      "productCode": "PC-001",
      "category": "Beverages",
      "tags": ["coffee", "premium", "arabica"],
      "images": ["https://example.com/coffee.jpg"],
      "createdAt": "2024-01-15T10:30:00Z"
    }
  }
}
```

> **Note**: The `name` field is required. All other fields are optional. The `sku` must be unique per merchant. If a product with the same SKU already exists, an error will be returned.

***

### 11.6 Update Product

**Permissions**: `product.manage`

**Request**:

```graphql theme={null}
mutation UpdateProduct(
    $id: ID!
    $name: String
    $description: String
    $active: Boolean
    $metadata: JSON
    $images: [String!]
    $sku: String
    $productCode: String
    $category: String
    $tags: [String!]
) {
    updateProduct(
        id: $id
        name: $name
        description: $description
        active: $active
        metadata: $metadata
        images: $images
        sku: $sku
        productCode: $productCode
        category: $category
        tags: $tags
    ) {
        id
        name
        description
        active
        sku
        productCode
        category
        tags
        updatedAt
    }
}
```

**Variables**:

```json theme={null}
{
  "id": "1",
  "name": "Updated Coffee Beans",
  "description": "Updated description",
  "category": "Premium Beverages",
  "tags": ["coffee", "premium", "arabica", "organic"]
}
```

**Response**:

```json theme={null}
{
  "data": {
    "updateProduct": {
      "id": "1",
      "name": "Updated Coffee Beans",
      "description": "Updated description",
      "active": true,
      "sku": "COFFEE-001",
      "productCode": "PC-001",
      "category": "Premium Beverages",
      "tags": ["coffee", "premium", "arabica", "organic"],
      "updatedAt": "2024-01-16T14:20:00Z"
    }
  }
}
```

> **Note**: Only the `id` field is required. All other fields are optional and will only be updated if provided. If updating the `sku`, it must remain unique per merchant.

***

### 11.7 Delete Product

**Permissions**: `product.manage`

**Request**:

```graphql theme={null}
mutation DeleteProduct($id: ID!) {
    deleteProduct(id: $id)
}
```

**Variables**:

```json theme={null}
{
  "id": "1"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "deleteProduct": true
  }
}
```

> **Note**: This performs a soft delete by setting `active=false`. The product record remains in the database but is marked as inactive.

***

### 11.8 Archive Product

**Permissions**: `product.manage`

**Request**:

```graphql theme={null}
mutation ArchiveProduct($id: ID!) {
    archiveProduct(id: $id) {
        id
        name
        archivedAt
    }
}
```

**Variables**:

```json theme={null}
{
  "id": "1"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "archiveProduct": {
      "id": "1",
      "name": "Premium Coffee Beans",
      "archivedAt": "2024-01-16T14:20:00Z"
    }
  }
}
```

> **Note**: Archiving a product sets the `archivedAt` timestamp. Archived products can be filtered out in queries and can be unarchived later.

***

### 11.9 Unarchive Product

**Permissions**: `product.manage`

**Request**:

```graphql theme={null}
mutation UnarchiveProduct($id: ID!) {
    unarchiveProduct(id: $id) {
        id
        name
        archivedAt
    }
}
```

**Variables**:

```json theme={null}
{
  "id": "1"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "unarchiveProduct": {
      "id": "1",
      "name": "Premium Coffee Beans",
      "archivedAt": null
    }
  }
}
```

> **Note**: Unarchiving a product clears the `archivedAt` timestamp, making the product available again.

***

### 11.10 Create Price

**Permissions**: `product.manage`

**Request**:

```graphql theme={null}
mutation CreatePrice(
    $productId: ID!
    $amount: Decimal!
    $currency: String!
    $type: PriceType!
    $billingPeriod: String
    $active: Boolean
    $metadata: JSON
    $nickname: String
    $description: String
    $pricingType: PricingType
    $minAmount: Decimal
    $maxAmount: Decimal
    $trialPeriodDays: Int
    $usageType: UsageType
    $unitLabel: String
    $billingScheme: BillingScheme
    $taxBehavior: TaxBehavior
    $taxCode: String
) {
    createPrice(
        productId: $productId
        amount: $amount
        currency: $currency
        type: $type
        billingPeriod: $billingPeriod
        active: $active
        metadata: $metadata
        nickname: $nickname
        description: $description
        pricingType: $pricingType
        minAmount: $minAmount
        maxAmount: $maxAmount
        trialPeriodDays: $trialPeriodDays
        usageType: $usageType
        unitLabel: $unitLabel
        billingScheme: $billingScheme
        taxBehavior: $taxBehavior
        taxCode: $taxCode
    ) {
        id
        amount
        currency
        type
        billingPeriod
        active
        nickname
        description
        pricingType
        minAmount
        maxAmount
        trialPeriodDays
        usageType
        unitLabel
        billingScheme
        taxBehavior
        taxCode
        createdAt
    }
}
```

**Variables** (One-time price):

```json theme={null}
{
  "productId": "1",
  "amount": "29.99",
  "currency": "USD",
  "type": "ONE_TIME",
  "active": true,
  "nickname": "Standard Price",
  "description": "One-time purchase price",
  "pricingType": "FIXED",
  "taxBehavior": "EXCLUSIVE"
}
```

**Variables** (Recurring price with trial):

```json theme={null}
{
  "productId": "1",
  "amount": "9.99",
  "currency": "USD",
  "type": "RECURRING",
  "billingPeriod": "month",
  "active": true,
  "nickname": "Monthly Subscription",
  "description": "Monthly recurring subscription",
  "pricingType": "FIXED",
  "trialPeriodDays": 7,
  "taxBehavior": "EXCLUSIVE"
}
```

**Variables** (Pay-What-You-Want price):

```json theme={null}
{
  "productId": "1",
  "amount": "20.00",
  "currency": "USD",
  "type": "ONE_TIME",
  "active": true,
  "nickname": "PWYW Price",
  "description": "Customer chooses their price",
  "pricingType": "PAY_WHAT_YOU_WANT",
  "minAmount": "10.00",
  "maxAmount": "50.00",
  "taxBehavior": "EXCLUSIVE"
}
```

**Variables** (Usage-based pricing):

```json theme={null}
{
  "productId": "1",
  "amount": "0.01",
  "currency": "USD",
  "type": "RECURRING",
  "billingPeriod": "month",
  "active": true,
  "nickname": "Per API Call",
  "description": "Charged per API call",
  "pricingType": "FIXED",
  "usageType": "METERED",
  "unitLabel": "API calls",
  "billingScheme": "PER_UNIT",
  "taxBehavior": "EXCLUSIVE"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "createPrice": {
      "id": "1",
      "amount": "29.99",
      "currency": "USD",
      "type": "ONE_TIME",
      "billingPeriod": null,
      "active": true,
      "nickname": "Standard Price",
      "description": "One-time purchase price",
      "pricingType": "FIXED",
      "minAmount": null,
      "maxAmount": null,
      "trialPeriodDays": null,
      "usageType": null,
      "unitLabel": null,
      "billingScheme": null,
      "taxBehavior": "EXCLUSIVE",
      "taxCode": null,
      "createdAt": "2024-01-15T10:30:00Z"
    }
  }
}
```

> **Note**:
>
> * Required fields: `productId`, `amount`, `currency`, `type`
> * For `RECURRING` prices, `billingPeriod` is required (e.g., "month", "year")
> * For `PAY_WHAT_YOU_WANT` pricing, `minAmount` and/or `maxAmount` must be provided
> * `trialPeriodDays` is only allowed for `RECURRING` prices
> * For usage-based pricing (`usageType: METERED`), `unitLabel` is required
> * `amount` must be greater than 0
> * `currency` must be a 3-character code (e.g., "USD", "EUR")

***

### 11.11 Update Price

**Permissions**: `product.manage`

**Request**:

```graphql theme={null}
mutation UpdatePrice(
    $id: ID!
    $amount: Decimal
    $currency: String
    $active: Boolean
    $metadata: JSON
    $nickname: String
    $description: String
    $pricingType: PricingType
    $minAmount: Decimal
    $maxAmount: Decimal
    $trialPeriodDays: Int
    $usageType: UsageType
    $unitLabel: String
    $billingScheme: BillingScheme
    $taxBehavior: TaxBehavior
    $taxCode: String
) {
    updatePrice(
        id: $id
        amount: $amount
        currency: $currency
        active: $active
        metadata: $metadata
        nickname: $nickname
        description: $description
        pricingType: $pricingType
        minAmount: $minAmount
        maxAmount: $maxAmount
        trialPeriodDays: $trialPeriodDays
        usageType: $usageType
        unitLabel: $unitLabel
        billingScheme: $billingScheme
        taxBehavior: $taxBehavior
        taxCode: $taxCode
    ) {
        id
        amount
        currency
        active
        nickname
        description
        pricingType
        minAmount
        maxAmount
        updatedAt
    }
}
```

**Variables**:

```json theme={null}
{
  "id": "1",
  "amount": "39.99",
  "nickname": "Updated Price",
  "active": false
}
```

**Response**:

```json theme={null}
{
  "data": {
    "updatePrice": {
      "id": "1",
      "amount": "39.99",
      "currency": "USD",
      "active": false,
      "nickname": "Updated Price",
      "description": "One-time purchase price",
      "pricingType": "FIXED",
      "minAmount": null,
      "maxAmount": null,
      "updatedAt": "2024-01-16T14:20:00Z"
    }
  }
}
```

> **Note**: Only the `id` field is required. All other fields are optional and will only be updated if provided. The same validation rules apply as for creating prices (e.g., trial period only for recurring prices).

***

### 11.12 Delete Price

**Permissions**: `product.manage`

**Request**:

```graphql theme={null}
mutation DeletePrice($id: ID!) {
    deletePrice(id: $id)
}
```

**Variables**:

```json theme={null}
{
  "id": "1"
}
```

**Response**:

```json theme={null}
{
  "data": {
    "deletePrice": true
  }
}
```

> **Note**: This performs a hard delete, permanently removing the price from the database.

***

## Permission Matrix

| Endpoint              | Merchant | Manager (Staff) | Cashier (Staff) |
| --------------------- | -------- | --------------- | --------------- |
| `login`               | ✅        | ✅               | ✅               |
| `createMerchant`      | ✅        | ❌               | ❌               |
| `createPayment`       | ✅        | ✅               | ✅               |
| `transactions`        | ✅        | ✅               | ✅               |
| `salesSummaryReport`  | ✅        | ✅               | ❌               |
| `addOrUpdateWallet`   | ✅        | ❌               | ❌               |
| `createStaff`         | ✅        | ❌               | ❌               |
| `customers`           | ✅        | ✅               | ❌               |
| `upsertCustomer`      | ✅        | ✅               | ❌               |
| `createRefundRequest` | ❌        | ✅               | ❌               |
| `approveRefund`       | ❌        | ✅               | ❌               |
| `rejectRefund`        | ❌        | ✅               | ❌               |
| `completeRefund`      | ✅        | ❌               | ❌               |
| `refunds`             | ❌        | ✅               | ❌               |
| `refundByReference`   | ❌        | ✅               | ❌               |
| `refundSummary`       | ❌        | ✅               | ❌               |
| `products`            | ✅        | ✅               | ✅               |
| `product`             | ✅        | ✅               | ✅               |
| `productBySku`        | ✅        | ✅               | ✅               |
| `productPrices`       | ✅        | ✅               | ✅               |
| `createProduct`       | ✅        | ❌               | ❌               |
| `updateProduct`       | ✅        | ❌               | ❌               |
| `deleteProduct`       | ✅        | ❌               | ❌               |
| `archiveProduct`      | ✅        | ❌               | ❌               |
| `unarchiveProduct`    | ✅        | ❌               | ❌               |
| `createPrice`         | ✅        | ❌               | ❌               |
| `updatePrice`         | ✅        | ❌               | ❌               |
| `deletePrice`         | ✅        | ❌               | ❌               |

***

## Quick Start Example (cURL)

```bash theme={null}
# 1. Login
curl -X POST https://your-domain.com/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation { login(email: \"merchant@example.com\", password: \"SecurePass123!\") { access_token user_type } }"
  }'

# 2. Create Payment (with token)
curl -X POST https://your-domain.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -d '{
    "query": "mutation { createPayment(amount: \"25.50\", blockchain: \"solana-devnet\", tokenSymbol: \"USDC\") { url qr_code } }"
  }'
```

***

This API reference covers all available endpoints in the StableX platform. For any questions or issues, please contact the development team.
