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

# Payment API

# Payment API Service

The Payment API Service is the main gateway for processing payments. It orchestrates card tokenization, fraud detection, and transaction processing while providing a simple REST API for merchants.

<Info>
  **Base URL:** `https://paymentgateway.redahaloubi.com/api/v1`

  **Public API:** `https://paymentgateway.redahaloubi.com/api/public`
</Info>

***

## Authentication

The Payment API uses two different authentication methods depending on the endpoint:

<Tabs>
  <Tab title="API Key (Server-to-Server)">
    **Use for:** Payment processing from your backend server

    ```http theme={null}
    X-API-Key: pk_live_abc123def456...
    ```

    **Endpoints requiring API Key:**

    * `/api/v1/payments/*` - All payment operations
    * `/api/v1/transactions/*` - Transaction queries
    * `/api/v1/payment-intents` - Create payment intent
    * `/api/v1/payment-intents/:id/cancel` - Cancel payment intent

    **How to get an API key:** See [Merchant API](/api/merchant-api) → Create API Key
  </Tab>

  <Tab title="Client Secret (Browser)">
    **Use for:** Hosted checkout page (browser-based)

    ```http theme={null}
    X-Client-Secret: pi_abc123_secret_xyz789...
    ```

    Or as query parameter:

    ```
    ?client_secret=pi_abc123_secret_xyz789...
    ```

    **Endpoints requiring Client Secret:**

    * `/api/public/payment-intents/:id/confirm` - Confirm payment

    **Security:**

    * Client secrets are safe to use in browsers
    * They expire after 1 hour
    * They're tied to a specific payment intent
    * Cannot be used to access other merchant data
  </Tab>
</Tabs>

***

## Rate Limits

<CardGroup cols={2}>
  <Card title="Payment Operations" icon="credit-card">
    **20 requests per second** per merchant

    **10,000 requests per hour** per merchant
  </Card>

  <Card title="Transaction Queries" icon="magnifying-glass">
    **100 requests per second** per merchant
  </Card>

  <Card title="Public Endpoints" icon="globe">
    **50 requests per second** per IP

    Used for hosted checkout page
  </Card>

  <Card title="Idempotency Cache" icon="clock">
    **24 hour** cache duration

    Prevents duplicate payments
  </Card>
</CardGroup>

***

## Payment Flow Overview

```mermaid theme={null}
sequenceDiagram
    participant Merchant as "Merchant Server"
    participant API as "Payment API"
    participant Token as "Tokenization Service"
    participant Txn as "Transaction Service"

    Note over Merchant,Txn: Two-Step Payment (Authorize → Capture)

    Merchant->>API: POST /payments/authorize
    API->>Token: Tokenize Card (gRPC)
    Token-->>API: {token}
    API->>Txn: Authorize (gRPC)
    Txn-->>API: {auth_code, status}
    API-->>Merchant: {payment_id, status: "authorized"}

    Note over Merchant,Txn: Later: Capture Payment

    Merchant->>API: POST /payments/{id}/capture
    API->>Txn: Capture (gRPC)
    Txn-->>API: {status: "captured"}
    API-->>Merchant: {status: "captured"}
```

***

## Payment Operations

### Authorize Payment

<ParamField path="POST" type="endpoint">
  `/api/v1/payments/authorize`
</ParamField>

Authorize a payment by holding funds on the customer's card without charging. Authorization is valid for 7 days.

**Authentication:** API Key required

**Request Body**

<ParamField body="amount" type="integer" required>
  Amount in **cents** (e.g., 9999 = \$99.99)

  **Min:** 1 (0.01 in currency)

  **Max:** No limit (dependent on card)
</ParamField>

<ParamField body="currency" type="string" required>
  Three-letter currency code

  **Supported:** USD, EUR, MAD

  **Length:** Exactly 3 characters
</ParamField>

<ParamField body="card" type="object" required>
  Card details

  <Expandable title="Card Object">
    <ParamField body="number" type="string" required>
      Card number (13-19 digits, no spaces)
    </ParamField>

    <ParamField body="cardholder_name" type="string" required>
      Name on card
    </ParamField>

    <ParamField body="exp_month" type="integer" required>
      Expiration month (1-12)
    </ParamField>

    <ParamField body="exp_year" type="integer" required>
      Expiration year (4 digits, e.g., 2027)
    </ParamField>

    <ParamField body="cvv" type="string" required>
      Card verification value (3-4 digits)
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="customer" type="object">
  Customer information (optional)

  <Expandable title="Customer Object">
    <ParamField body="email" type="string">
      Customer email (for receipts)
    </ParamField>

    <ParamField body="name" type="string">
      Customer full name
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="description" type="string">
  Payment description (e.g., "Order #12345")
</ParamField>

<ParamField body="metadata" type="object">
  Custom key-value pairs for your reference

  Example: `{"order_id": "12345", "customer_id": "cus_abc"}`
</ParamField>

**Example Request**

```bash theme={null}
curl -X POST https://paymentgateway.redahaloubi.com/api/v1/payments/authorize \
  -H "X-API-Key: pk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-12345-auth" \
  -d '{
    "amount": 9999,
    "currency": "USD",
    "card": {
      "number": "4242424242424242",
      "cardholder_name": "John Doe",
      "exp_month": 12,
      "exp_year": 2027,
      "cvv": "123"
    },
    "customer": {
      "email": "john@customer.com",
      "name": "John Doe"
    },
    "description": "Order #12345 - Premium Plan",
    "metadata": {
      "order_id": "12345",
      "plan": "premium"
    }
  }'
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pay_abc123def456ghi789",
    "status": "authorized",
    "amount": 9999,
    "currency": "USD",
    "card_brand": "visa",
    "card_last4": "4242",
    "auth_code": "123456",
    "fraud_score": 15,
    "fraud_decision": "approve",
    "response_code": "00",
    "response_message": "Approved",
    "transaction_id": "txn_xyz789uvw012rst345",
    "created_at": "2026-01-24T10:00:00Z",
    "expires_at": "2026-01-31T10:00:00Z"
  }
}
```

<Accordion title="Error Responses">
  **400 Bad Request - Invalid Amount**

  ```json theme={null}
  {
    "success": false,
    "error": "invalid request: amount must be at least 1"
  }
  ```

  **400 Bad Request - Unsupported Currency**

  ```json theme={null}
  {
    "success": false,
    "error": "unsupported currency (only USD, EUR, and MAD supported)"
  }
  ```

  **402 Payment Required - Card Declined**

  ```json theme={null}
  {
    "success": false,
    "error": "payment declined: insufficient funds (code: 51)"
  }
  ```

  **402 Payment Required - Fraud Declined**

  ```json theme={null}
  {
    "success": false,
    "error": "payment declined: high fraud risk (score: 85)"
  }
  ```

  **409 Conflict - Idempotency Key Mismatch**

  ```json theme={null}
  {
    "success": false,
    "error": "idempotency key already used with different request data"
  }
  ```

  **429 Too Many Requests**

  ```json theme={null}
  {
    "success": false,
    "error": "rate limit exceeded: 20 requests per second"
  }
  ```
</Accordion>

<Info>
  **Idempotency:** Include an `Idempotency-Key` header to safely retry requests. If the same key is used within 24 hours, the original response is returned without creating a new payment.
</Info>

***

### Sale Payment

<ParamField path="POST" type="endpoint">
  `/api/v1/payments/sale`
</ParamField>

Process a sale (authorize + capture in one step). Funds are immediately charged to the customer's card.

**Authentication:** API Key required

**Request Body**

Same as [Authorize Payment](#authorize-payment)

**Example Request**

```bash theme={null}
curl -X POST https://paymentgateway.redahaloubi.com/api/v1/payments/sale \
  -H "X-API-Key: pk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-12345-sale" \
  -d '{
    "amount": 4999,
    "currency": "USD",
    "card": {
      "number": "5555555555554444",
      "cardholder_name": "Jane Smith",
      "exp_month": 6,
      "exp_year": 2028,
      "cvv": "456"
    },
    "description": "Subscription - Monthly Plan"
  }'
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pay_def456ghi789jkl012",
    "status": "captured",
    "amount": 4999,
    "currency": "USD",
    "card_brand": "mastercard",
    "card_last4": "4444",
    "auth_code": "789012",
    "response_code": "00",
    "response_message": "Approved",
    "transaction_id": "txn_mno345pqr678stu901",
    "created_at": "2026-01-24T10:05:00Z",
    "captured_at": "2026-01-24T10:05:01Z"
  }
}
```

<Info>
  **When to use Sale vs Authorize:**

  **Use Sale when:**

  * Immediate charge is required (subscriptions, digital goods)
  * You ship goods immediately
  * Payment and fulfillment happen together

  **Use Authorize when:**

  * You need to verify funds availability first
  * You ship physical goods later
  * You need to adjust the amount before capture
  * You want to manually review orders
</Info>

***

### Capture Payment

<ParamField path="POST" type="endpoint">
  `/api/v1/payments/:id/capture`
</ParamField>

Capture a previously authorized payment. You can capture the full amount or a partial amount.

**Authentication:** API Key required

**Path Parameters**

<ParamField path="id" type="string" required>
  Payment ID from authorization response
</ParamField>

**Request Body**

<ParamField body="amount" type="integer" required>
  Amount to capture in cents

  **Must be:** ≤ authorized amount

  **Partial captures:** Allowed (e.g., capture $50 of $100 authorization)
</ParamField>

**Example Request**

```bash theme={null}
curl -X POST https://paymentgateway.redahaloubi.com/api/v1/payments/pay_abc123def456/capture \
  -H "X-API-Key: pk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 9999
  }'
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pay_abc123def456ghi789",
    "status": "captured",
    "amount": 9999,
    "currency": "USD",
    "captured_amount": 9999,
    "card_brand": "visa",
    "card_last4": "4242",
    "captured_at": "2026-01-24T15:30:00Z"
  }
}
```

<Accordion title="Error Responses">
  **400 Bad Request - Already Captured**

  ```json theme={null}
  {
    "success": false,
    "error": "payment already captured"
  }
  ```

  **400 Bad Request - Amount Too High**

  ```json theme={null}
  {
    "success": false,
    "error": "capture amount exceeds authorized amount"
  }
  ```

  **400 Bad Request - Authorization Expired**

  ```json theme={null}
  {
    "success": false,
    "error": "authorization expired (valid for 7 days)"
  }
  ```

  **404 Not Found**

  ```json theme={null}
  {
    "success": false,
    "error": "payment not found"
  }
  ```
</Accordion>

<Warning>
  **Capture Deadline:** Authorizations expire after **7 days**. Capture before the expiration date or the authorization will be automatically voided.
</Warning>

***

### Void Payment

<ParamField path="POST" type="endpoint">
  `/api/v1/payments/:id/void`
</ParamField>

Cancel an authorized payment before it's captured. This releases the hold on the customer's card.

**Authentication:** API Key required

**Path Parameters**

<ParamField path="id" type="string" required>
  Payment ID to void
</ParamField>

**Request Body**

<ParamField body="reason" type="string" required>
  Reason for voiding (for audit logs)

  Example: "Customer requested cancellation", "Order canceled"
</ParamField>

**Example Request**

```bash theme={null}
curl -X POST https://paymentgateway.redahaloubi.com/api/v1/payments/pay_abc123def456/void \
  -H "X-API-Key: pk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Customer requested order cancellation"
  }'
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pay_abc123def456ghi789",
    "status": "voided",
    "amount": 9999,
    "currency": "USD",
    "voided_at": "2026-01-24T12:00:00Z",
    "void_reason": "Customer requested order cancellation"
  }
}
```

<Accordion title="Error Responses">
  **400 Bad Request - Already Captured**

  ```json theme={null}
  {
    "success": false,
    "error": "cannot void captured payment (use refund instead)"
  }
  ```

  **400 Bad Request - Already Voided**

  ```json theme={null}
  {
    "success": false,
    "error": "payment already voided"
  }
  ```
</Accordion>

<Info>
  **Void vs Refund:**

  **Void:** Cancel before capture (no money has been charged)\
  **Refund:** Return money after capture (money has been charged)
</Info>

***

### Refund Payment

<ParamField path="POST" type="endpoint">
  `/api/v1/payments/:id/refund`
</ParamField>

Refund a captured payment. Supports full and partial refunds.

**Authentication:** API Key required

**Path Parameters**

<ParamField path="id" type="string" required>
  Payment ID to refund
</ParamField>

**Request Body**

<ParamField body="amount" type="integer" required>
  Amount to refund in cents

  **Full refund:** Original payment amount

  **Partial refund:** Less than original amount
</ParamField>

<ParamField body="reason" type="string" required>
  Refund reason (for audit logs)

  Example: "Product returned", "Service not delivered"
</ParamField>

**Example Request**

```bash theme={null}
curl -X POST https://paymentgateway.redahaloubi.com/api/v1/payments/pay_abc123def456/refund \
  -H "X-API-Key: pk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2500,
    "reason": "Product damaged - partial refund"
  }'
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pay_abc123def456ghi789",
    "status": "partially_refunded",
    "amount": 9999,
    "currency": "USD",
    "refunded_amount": 2500,
    "remaining_amount": 7499,
    "refunded_at": "2026-01-24T16:00:00Z",
    "refund_reason": "Product damaged - partial refund"
  }
}
```

<Accordion title="Error Responses">
  **400 Bad Request - Not Captured**

  ```json theme={null}
  {
    "success": false,
    "error": "can only refund captured payments"
  }
  ```

  **400 Bad Request - Amount Too High**

  ```json theme={null}
  {
    "success": false,
    "error": "refund amount exceeds available amount"
  }
  ```

  **400 Bad Request - Already Refunded**

  ```json theme={null}
  {
    "success": false,
    "error": "payment already fully refunded"
  }
  ```
</Accordion>

<Info>
  **Refund Processing:**

  * Refunds are processed immediately
  * Funds typically appear in customer's account within 5-10 business days
  * Multiple partial refunds are supported until full amount is refunded
</Info>

***

### Get Payment

<ParamField path="GET" type="endpoint">
  `/api/v1/payments/:id`
</ParamField>

Retrieve details of a specific payment.

**Authentication:** API Key required

**Path Parameters**

<ParamField path="id" type="string" required>
  Payment ID
</ParamField>

**Example Request**

```bash theme={null}
curl -X GET https://paymentgateway.redahaloubi.com/api/v1/payments/pay_abc123def456 \
  -H "X-API-Key: pk_live_your_api_key"
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pay_abc123def456ghi789",
    "status": "authorized",
    "amount": 9999,
    "currency": "USD",
    "card_brand": "visa",
    "card_last4": "4242",
    "auth_code": "123456",
    "description": "Order #12345 - Premium Plan",
    "customer": {
      "email": "john@customer.com",
      "name": "John Doe"
    },
    "metadata": {
      "order_id": "12345",
      "plan": "premium"
    },
    "created_at": "2026-01-24T10:00:00Z",
    "expires_at": "2026-01-31T10:00:00Z"
  }
}
```

<Accordion title="Error Responses">
  **404 Not Found**

  ```json theme={null}
  {
    "success": false,
    "error": "payment not found"
  }
  ```

  **401 Unauthorized - Wrong Merchant**

  ```json theme={null}
  {
    "success": false,
    "error": "payment not found"
  }
  ```
</Accordion>

***

## Payment Intents

Payment Intents provide a hosted checkout solution where customers complete payment in a browser.

### Create Payment Intent

<ParamField path="POST" type="endpoint">
  `/api/v1/payment-intents`
</ParamField>

Create a payment intent for hosted checkout. Returns a `checkout_url` to redirect your customer.

**Authentication:** API Key required

**Request Body**

<ParamField body="amount" type="integer" required>
  Amount in cents
</ParamField>

<ParamField body="currency" type="string" required>
  Currency code (USD, EUR, MAD)
</ParamField>

<ParamField body="success_url" type="string" required>
  URL to redirect after successful payment

  **Must be HTTPS** (except localhost for testing)

  **Supports placeholders:** `{CHECKOUT_SESSION_ID}` will be replaced with payment intent ID
</ParamField>

<ParamField body="cancel_url" type="string">
  URL to redirect if customer cancels

  **Default:** Same as success\_url
</ParamField>

<ParamField body="order_id" type="string">
  Your internal order ID
</ParamField>

<ParamField body="description" type="string">
  Payment description shown to customer
</ParamField>

<ParamField body="capture_method" type="string">
  When to capture funds

  **Values:** `automatic` (default), `manual`

  * `automatic`: Funds captured immediately after authorization
  * `manual`: You must manually capture later
</ParamField>

<ParamField body="customer_email" type="string">
  Customer email (pre-filled on checkout page)
</ParamField>

<ParamField body="metadata" type="object">
  Custom metadata
</ParamField>

**Example Request**

```bash theme={null}
curl -X POST https://paymentgateway.redahaloubi.com/api/v1/payment-intents \
  -H "X-API-Key: pk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 9999,
    "currency": "USD",
    "success_url": "https://yourstore.com/order/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yourstore.com/order/cancel",
    "description": "Order #12345 - Premium Plan",
    "customer_email": "john@customer.com",
    "metadata": {
      "order_id": "12345"
    }
  }'
```

**Response (201 Created)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pi_abc123def456ghi789",
    "client_secret": "pi_abc123def456ghi789_secret_xyz789uvw456rst123",
    "checkout_url": "https://checkout-page-amber.vercel.app/checkout/pi_abc123def456ghi789?client_secret=pi_abc123def456ghi789_secret_xyz789uvw456rst123",
    "amount": 9999,
    "currency": "USD",
    "status": "created",
    "success_url": "https://yourstore.com/order/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yourstore.com/order/cancel",
    "description": "Order #12345 - Premium Plan",
    "expires_at": "2026-01-24T11:00:00Z",
    "created_at": "2026-01-24T10:00:00Z"
  }
}
```

<Info>
  **Next Steps:**

  1. Redirect your customer to `checkout_url`
  2. Customer completes payment on hosted page
  3. Customer is redirected to your `success_url` with `?payment_intent=pi_abc123...`
  4. Verify payment status on your server
</Info>

***

### Get Payment Intent (Public)

<ParamField path="GET" type="endpoint">
  `/api/public/payment-intents/:id`
</ParamField>

Get payment intent details (browser-safe, no sensitive data returned).

**Authentication:** None required (public endpoint)

**Path Parameters**

<ParamField path="id" type="string" required>
  Payment intent ID
</ParamField>

**Example Request**

```bash theme={null}
curl -X GET https://paymentgateway.redahaloubi.com/api/public/payment-intents/pi_abc123def456
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pi_abc123def456ghi789",
    "status": "created",
    "amount": 9999,
    "currency": "USD",
    "success_url": "https://yourstore.com/order/success",
    "cancel_url": "https://yourstore.com/order/cancel",
    "expires_at": "2026-01-24T11:00:00Z"
  }
}
```

<Warning>
  **Security Note:** This endpoint does NOT return `client_secret`. It only provides public information needed to display the checkout page.
</Warning>

***

### Confirm Payment Intent

<ParamField path="POST" type="endpoint">
  `/api/public/payment-intents/:id/confirm`
</ParamField>

Confirm a payment intent by submitting card details. This processes the actual payment.

**Authentication:** Client Secret required (in header or query)

**Path Parameters**

<ParamField path="id" type="string" required>
  Payment intent ID
</ParamField>

**Headers or Query**

<ParamField header="X-Client-Secret" type="string" required>
  Client secret from payment intent creation

  Alternative: `?client_secret=pi_abc123_secret_xyz789`
</ParamField>

**Request Body**

<ParamField body="card" type="object" required>
  Card details

  <Expandable title="Card Object">
    <ParamField body="number" type="string" required>
      Card number (13-19 digits)
    </ParamField>

    <ParamField body="cardholder_name" type="string" required>
      Name on card
    </ParamField>

    <ParamField body="exp_month" type="integer" required>
      Expiration month (1-12)
    </ParamField>

    <ParamField body="exp_year" type="integer" required>
      Expiration year (e.g., 2027)
    </ParamField>

    <ParamField body="cvv" type="string" required>
      Card verification value (3-4 digits)
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="customer_email" type="string">
  Customer email (optional if provided during intent creation)
</ParamField>

**Example Request**

```bash theme={null}
curl -X POST https://paymentgateway.redahaloubi.com/api/public/payment-intents/pi_abc123def456/confirm \
  -H "Content-Type: application/json" \
  -H "X-Client-Secret: pi_abc123def456ghi789_secret_xyz789uvw456rst123" \
  -d '{
    "card": {
      "number": "4242424242424242",
      "cardholder_name": "John Doe",
      "exp_month": 12,
      "exp_year": 2027,
      "cvv": "123"
    },
    "customer_email": "john@customer.com"
  }'
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "pi_abc123def456ghi789",
    "status": "authorized",
    "payment_id": "pay_xyz789uvw012rst345",
    "redirect_url": "https://yourstore.com/order/success?payment_intent=pi_abc123def456ghi789"
  }
}
```

<Accordion title="Error Responses">
  **401 Unauthorized - Invalid Client Secret**

  ```json theme={null}
  {
    "success": false,
    "error": {
      "code": "INVALID_CLIENT_SECRET",
      "message": "client secret is invalid or expired"
    }
  }
  ```

  **410 Gone - Intent Expired**

  ```json theme={null}
  {
    "success": false,
    "error": {
      "code": "INTENT_EXPIRED",
      "message": "payment intent expired (valid for 1 hour)"
    }
  }
  ```

  **402 Payment Required - Card Declined**

  ```json theme={null}
  {
    "success": false,
    "error": {
      "code": "PAYMENT_DECLINED",
      "message": "card declined: insufficient funds",
      "remaining_attempts": 3
    }
  }
  ```

  **410 Gone - Max Attempts**

  ```json theme={null}
  {
    "success": false,
    "error": {
      "code": "MAX_ATTEMPTS_REACHED",
      "message": "maximum payment attempts reached (5)"
    }
  }
  ```
</Accordion>

<Warning>
  **Payment Attempt Limits:**

  * Maximum **5 attempts** per payment intent
  * Each failed attempt is tracked
  * After 5 failures, the intent is locked
  * Create a new payment intent for additional attempts
</Warning>

***

### Cancel Payment Intent

<ParamField path="POST" type="endpoint">
  `/api/v1/payment-intents/:id/cancel`
</ParamField>

Cancel a payment intent before it's completed.

**Authentication:** API Key required

**Path Parameters**

<ParamField path="id" type="string" required>
  Payment intent ID to cancel
</ParamField>

**Example Request**

```bash theme={null}
curl -X POST https://paymentgateway.redahaloubi.com/api/v1/payment-intents/pi_abc123def456/cancel \
  -H "X-API-Key: pk_live_your_api_key"
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "message": "payment intent canceled"
}
```

<Accordion title="Error Responses">
  **400 Bad Request - Already Completed**

  ```json theme={null}
  {
    "success": false,
    "error": "cannot cancel completed payment intent"
  }
  ```
</Accordion>

***

## Transaction Endpoints

### Get Transaction

<ParamField path="GET" type="endpoint">
  `/api/v1/transactions/:id`
</ParamField>

Get details of a specific transaction.

**Authentication:** API Key required

**Path Parameters**

<ParamField path="id" type="string" required>
  Transaction ID
</ParamField>

**Example Request**

```bash theme={null}
curl -X GET https://paymentgateway.redahaloubi.com/api/v1/transactions/txn_abc123def456 \
  -H "X-API-Key: pk_live_your_api_key"
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "txn_abc123def456ghi789",
    "payment_id": "pay_xyz789uvw012rst345",
    "status": "authorized",
    "amount": 9999,
    "currency": "USD",
    "card_brand": "visa",
    "card_last4": "4242",
    "auth_code": "123456",
    "response_code": "00",
    "response_message": "Approved",
    "created_at": "2026-01-24T10:00:00Z"
  }
}
```

***

### List Transactions

<ParamField path="GET" type="endpoint">
  `/api/v1/transactions`
</ParamField>

List all transactions for your merchant account.

**Authentication:** API Key required

**Query Parameters**

<ParamField query="status" type="string">
  Filter by status

  **Values:** `authorized`, `captured`, `voided`, `refunded`, `failed`
</ParamField>

<ParamField query="limit" type="integer">
  Number of results per page

  **Default:** 10

  **Max:** 100
</ParamField>

<ParamField query="offset" type="integer">
  Number of results to skip

  **Default:** 0
</ParamField>

**Example Request**

```bash theme={null}
curl -X GET "https://paymentgateway.redahaloubi.com/api/v1/transactions?status=authorized&limit=20&offset=0" \
  -H "X-API-Key: pk_live_your_api_key"
```

**Response (200 OK)**

```json theme={null}
{
  "success": true,
  "data": {
    "transactions": [
      {
        "id": "txn_abc123def456",
        "payment_id": "pay_xyz789uvw012",
        "status": "authorized",
        "amount": 9999,
        "currency": "USD",
        "created_at": "2026-01-24T10:00:00Z"
      },
      {
        "id": "txn_def456ghi789",
        "payment_id": "pay_rst345mno678",
        "status": "authorized",
        "amount": 4999,
        "currency": "EUR",
        "created_at": "2026-01-24T09:30:00Z"
      }
    ],
    "total": 47,
    "limit": 20,
    "offset": 0
  }
}
```

***

## Test Cards

Use these test card numbers for development and testing:

<CardGroup cols={2}>
  <Card title="✅ Visa - Approved" icon="circle-check">
    **Card Number:** 4242 4242 4242 4242

    **Expiry:** Any future date (e.g., 12/2027)

    **CVV:** Any 3 digits (e.g., 123)

    **Result:** Authorization approved
  </Card>

  <Card title="✅ Mastercard - Approved" icon="circle-check">
    **Card Number:** 5555 5555 5555 4444

    **Expiry:** Any future date

    **CVV:** Any 3 digits

    **Result:** Authorization approved
  </Card>

  <Card title="❌ Generic Decline" icon="circle-xmark">
    **Card Number:** 4000 0000 0000 0002

    **Expiry:** Any future date

    **CVV:** Any 3 digits

    **Result:** Declined (code 05 - Do not honor)
  </Card>

  <Card title="❌ Insufficient Funds" icon="wallet">
    **Card Number:** 4000 0000 0000 9995

    **Expiry:** Any future date

    **CVV:** Any 3 digits

    **Result:** Declined (code 51)
  </Card>

  <Card title="❌ Expired Card" icon="clock">
    **Card Number:** 4000 0000 0000 0069

    **Expiry:** Any future date

    **CVV:** Any 3 digits

    **Result:** Declined (code 54)
  </Card>

  <Card title="❌ CVV Mismatch" icon="hashtag">
    **Card Number:** 4000 0000 0000 0127

    **Expiry:** Any future date

    **CVV:** Any 3 digits

    **Result:** Declined (code N7)
  </Card>

  <Card title="❌ Processing Error" icon="triangle-exclamation">
    **Card Number:** 4000 0000 0000 0119

    **Expiry:** Any future date

    **CVV:** Any 3 digits

    **Result:** Declined (code 96)
  </Card>

  <Card title="⚠️ High Fraud Risk" icon="shield-exclamation">
    **Card Number:** 4000 0000 0000 0259

    **Expiry:** Any future date

    **CVV:** Any 3 digits

    **Result:** Declined (fraud score > 70)
  </Card>
</CardGroup>

<Info>
  **Test Card Rules:**

  * All test cards use the same validation rules as real cards
  * Use any valid future expiry date (e.g., 12/2027)
  * Use any 3-digit CVV (except for CVV mismatch test)
  * Cardholder name can be anything
  * No real money is charged
</Info>

***

## Payment Status Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> created: Payment Intent Created
    created --> authorized: Card Authorized
    created --> failed: Authorization Failed
    
    authorized --> captured: Capture Payment
    authorized --> voided: Void Authorization
    authorized --> expired: 7 Days Passed
    
    captured --> refunded: Full Refund
    captured --> partially_refunded: Partial Refund
    
    partially_refunded --> refunded: Remaining Amount Refunded
    
    failed --> [*]
    voided --> [*]
    expired --> [*]
    refunded --> [*]
```

### Status Descriptions

<AccordionGroup>
  <Accordion title="created" icon="plus">
    **Payment Intent Status**

    Payment intent created but no payment attempt yet.

    **Next actions:** Confirm payment intent, Cancel
  </Accordion>

  <Accordion title="authorized" icon="lock">
    **Funds held on customer's card**

    Authorization is valid for 7 days. No money has been charged yet.

    **Next actions:** Capture, Void
  </Accordion>

  <Accordion title="captured" icon="check">
    **Payment completed**

    Funds have been charged to the customer's card.

    **Next actions:** Refund
  </Accordion>

  <Accordion title="voided" icon="ban">
    **Authorization canceled**

    Hold released on customer's card. No money was charged.

    **Final state** - No further actions possible
  </Accordion>

  <Accordion title="refunded" icon="rotate-left">
    **Fully refunded**

    All captured funds returned to customer.

    **Final state** - No further actions possible
  </Accordion>

  <Accordion title="partially_refunded" icon="circle-half-stroke">
    **Partially refunded**

    Some funds returned to customer, remaining amount still captured.

    **Next actions:** Additional refunds (up to remaining amount)
  </Accordion>

  <Accordion title="failed" icon="circle-xmark">
    **Payment failed**

    Authorization attempt declined by issuer or fraud system.

    **Final state** - Create new payment for retry
  </Accordion>

  <Accordion title="expired" icon="clock">
    **Authorization expired**

    Authorization not captured within 7 days. Automatically voided.

    **Final state** - Create new payment for retry
  </Accordion>
</AccordionGroup>

***

## Error Codes

<ResponseField name="400" type="Bad Request">
  Invalid request format or validation error

  **Common causes:**

  * Missing required fields
  * Invalid amount (must be positive integer)
  * Unsupported currency
  * Invalid card format
</ResponseField>

<ResponseField name="401" type="Unauthorized">
  Authentication failed

  **Common causes:**

  * Missing API key or client secret
  * Invalid API key
  * Expired client secret
  * API key from different merchant
</ResponseField>

<ResponseField name="402" type="Payment Required">
  Payment declined

  **Common causes:**

  * Card declined by issuer (insufficient funds, expired, etc.)
  * Fraud risk too high
  * CVV verification failed
  * Card reported lost/stolen
</ResponseField>

<ResponseField name="404" type="Not Found">
  Resource not found

  **Common causes:**

  * Invalid payment ID
  * Invalid transaction ID
  * Payment belongs to different merchant
</ResponseField>

<ResponseField name="409" type="Conflict">
  Request conflicts with current state

  **Common causes:**

  * Idempotency key already used with different data
  * Payment already captured/voided/refunded
  * Cannot perform action in current status
</ResponseField>

<ResponseField name="410" type="Gone">
  Resource expired or no longer available

  **Common causes:**

  * Payment intent expired (1 hour)
  * Authorization expired (7 days)
  * Maximum payment attempts reached
</ResponseField>

<ResponseField name="429" type="Too Many Requests">
  Rate limit exceeded

  **Limits:**

  * Payments: 20/sec, 10,000/hour
  * Transactions: 100/sec
  * Public endpoints: 50/sec per IP
</ResponseField>

***

## Idempotency

Idempotency prevents duplicate payments caused by network retries or accidental double-clicks.

### How It Works

<Steps>
  <Step title="Include Idempotency Key">
    Send a unique key in the `Idempotency-Key` header

    ```bash theme={null}
    -H "Idempotency-Key: order-12345-payment-attempt-1"
    ```
  </Step>

  <Step title="First Request Processed">
    The payment is processed normally and response is cached for 24 hours
  </Step>

  <Step title="Duplicate Requests Return Cached Response">
    If the same key is used again within 24 hours with the **same request body**, the original response is returned immediately without creating a new payment
  </Step>

  <Step title="Different Request Body = Error">
    If the same key is used with a **different request body**, you'll get a 409 Conflict error
  </Step>
</Steps>

### Best Practices

<CardGroup cols={2}>
  <Card title="Use Unique Keys" icon="key">
    Include order ID, user ID, timestamp, or attempt number

    Example: `order-{order_id}-{attempt}`
  </Card>

  <Card title="Consistent Keys" icon="arrows-repeat">
    Use the same key for all retry attempts of the same payment
  </Card>

  <Card title="24-Hour Expiry" icon="clock">
    Keys expire after 24 hours. After expiry, a new payment will be created
  </Card>

  <Card title="Test Retries" icon="flask">
    Test your retry logic in development to ensure proper idempotency handling
  </Card>
</CardGroup>

***

## Webhooks

Payment API sends webhooks for payment events to keep your server updated in real-time.

### Webhook Events

<CardGroup cols={2}>
  <Card title="payment.authorized" icon="lock">
    Payment authorized successfully
  </Card>

  <Card title="payment.captured" icon="check">
    Payment captured (charged)
  </Card>

  <Card title="payment.voided" icon="ban">
    Payment voided (canceled)
  </Card>

  <Card title="payment.refunded" icon="rotate-left">
    Payment refunded (full or partial)
  </Card>

  <Card title="payment.failed" icon="circle-xmark">
    Payment authorization failed
  </Card>

  <Card title="payment_intent.created" icon="plus">
    Payment intent created
  </Card>

  <Card title="payment_intent.succeeded" icon="circle-check">
    Payment intent completed successfully
  </Card>

  <Card title="payment_intent.canceled" icon="xmark">
    Payment intent canceled
  </Card>
</CardGroup>

### Webhook Payload

```json theme={null}
{
  "event": "payment.authorized",
  "timestamp": "2026-01-24T10:00:00Z",
  "id": "evt_abc123def456",
  "data": {
    "payment_id": "pay_xyz789uvw012",
    "merchant_id": "merchant_abc123",
    "status": "authorized",
    "amount": 9999,
    "currency": "USD",
    "card_brand": "visa",
    "card_last4": "4242",
    "created_at": "2026-01-24T10:00:00Z"
  }
}
```

### Verifying Webhook Signatures

<Warning>
  Always verify webhook signatures to ensure requests are from Payment Gateway.
</Warning>

```javascript theme={null}
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler
app.post('/webhooks/payment', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = JSON.stringify(req.body);
  
  if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  const event = req.body;
  
  switch(event.event) {
    case 'payment.authorized':
      // Handle authorization
      break;
    case 'payment.captured':
      // Handle capture
      break;
  }
  
  res.status(200).send('OK');
});
```

### Webhook Retry Logic

<Steps>
  <Step title="Initial Delivery">
    Webhook sent immediately after event
  </Step>

  <Step title="Retry Schedule">
    If delivery fails (non-200 response):

    * 1st retry: After 5 minutes
    * 2nd retry: After 15 minutes
    * 3rd retry: After 1 hour
    * 4th retry: After 6 hours
  </Step>

  <Step title="Maximum Attempts">
    After 5 failed attempts, webhook is marked as failed
  </Step>

  <Step title="Expiration">
    Webhooks expire after 24 hours
  </Step>
</Steps>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Quick Start" icon="rocket" href="/get-started/quick-start">
    Create your first payment intent in 5 minutes
  </Card>

  <Card title="Merchant API" icon="store" href="/api/merchant-api">
    Manage merchant accounts and team members
  </Card>

  <Card title="Checkout Integration" icon="browser" href="/integrations/checkout">
    Build a custom checkout flow
  </Card>

  <Card title="CLI Tool" icon="terminal" href="/tools/cli">
    Test payments from the command line
  </Card>
</CardGroup>

***

**Questions?** Contact support at [redahaloubi8@gmail.com](mailto:redahaloubi8@gmail.com)
