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

# Payments

> Reference for the PaymentsService class in the Tybrite SDK.

The `PaymentsService` class (accessed via `client.payments`) handles the core initialization and status verification for diverse payment providers including Stripe, PayPal, Paystack, and M-Pesa.

<Note>
  **Sandbox test mode:** When you use a `tybrite_sk_test_*` key, all payment providers are automatically switched to their test/sandbox credentials — regardless of what your store's payment settings show. Stripe uses test mode, PayPal uses its sandbox, Paystack uses test keys, and M-Pesa targets the Safaricom sandbox. No code changes are needed between sandbox and production; swap the key and the right environment follows. Every response includes a `Tybrite-Environment: sandbox | production` header confirming which environment resolved.
</Note>

## 🔐 Security & Signing (HMAC & Idempotency)

To prevent unauthorized payment initiation and duplicate charges, `initializePayment` requires both an HMAC-SHA256 signature and a mandatory **Idempotency Key**.

### Signing & Idempotency Process

1. **Generate ID**: Create a unique key for the transaction (e.g., `payment-{order_id}-{timestamp}`).
2. **Generate Timestamp**: Get the current Unix timestamp in seconds.
3. **Prepare Payload**: Concatenate timestamp and JSON body: `timestamp + "." + JSON_body`.
4. **Generate Signature**: Compute HMAC-SHA256 signature using your **HMAC Secret**.
5. **Base64 Encode**: The signature must be Base64 encoded.

### Code Implementation (TypeScript)

```typescript theme={null}
import crypto from 'crypto';

// 1. Define Signing Helper
function generateHmacSignature(payload: string, secret: string): string {
  return crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('base64');
}

// 2. Prepare Data & Sign
const idempotencyKey = `payment-${orderId}-${Date.now()}`;
const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify(paymentData);
const payload = `${timestamp}.${body}`;
const signature = generateHmacSignature(payload, hmacSecret);

// 3. Initialize Payment
const response = await client.payments.initializePayment({
  idempotencyKey: idempotencyKey,
  xTimestamp: timestamp,
  xSignature: signature,
  requestBody: paymentData
});

// If response.idempotent is true, this was a duplicate request
```

<Warning>
  **Financial Integrity:** Payment initialization triggers money movement. The `Idempotency-Key` prevents duplicate sessions and charges. Requests missing this header will return `400 missing_idempotency_key`.
</Warning>

***

## Methods

### `getPaymentMethods`

Retrieve all active payment methods configured in your Tybrite Admin Panel. This endpoint accepts **both publishable (`pk_*`) and secret (`sk_*`) API keys** — storefronts can safely call it client-side to discover available providers before checkout.

```typescript theme={null}
const { methods } = await client.payments.getPaymentMethods({
  fields: 'provider,display_name,type'
});
```

```json Response theme={null}
{
  "methods": [
    {
      "provider": "cash",
      "display_name": "Cash on Delivery",
      "type": "manual",
      "environment": "production",
      "is_configured": true
    },
    {
      "provider": "stripe",
      "display_name": "Stripe",
      "type": "redirect",
      "environment": "test",
      "is_configured": true
    }
  ]
}
```

Each method in the returned list includes:

| Field           | Description                                                                    |
| :-------------- | :----------------------------------------------------------------------------- |
| `provider`      | Provider identifier (e.g. `stripe`, `paypal`, `paystack`, `mpesa`).            |
| `display_name`  | Human-friendly label to show at checkout.                                      |
| `type`          | How the buyer completes payment: `redirect`, `popup`, `stk_push`, or `manual`. |
| `environment`   | `sandbox` or `production`, matching the key you used.                          |
| `is_configured` | `true` when the provider is fully set up and ready to accept payments.         |

***

### `initializePayment`

Create a payment session with your chosen provider. Requires a **Secret Key**, a valid HMAC signature, and a mandatory `Idempotency-Key`. On the first successful call this returns **`201 Created`**; subsequent calls with the same idempotency key and identical payload return **`200 OK`** with `idempotent: true` and the original session.

<Tip>
  The mandatory `Idempotency-Key` also makes this call eligible for the SDK's [automatic retries](/sdk/introduction#automatic-retries): a transient failure is retried safely and returns the original session rather than starting a second charge.
</Tip>

<Warning>
  **Secret Key required.** `initializePayment` will reject publishable keys with `403`. Never call this from untrusted client environments — keep your `sk_*` key and HMAC secret server-side.
</Warning>

<CodeGroup>
  ```typescript Stripe (International) theme={null}
  const paymentData = {
    provider: 'stripe',
    amount: 5000,
    currency: 'USD',
    email: 'john.doe@example.com',
    success_url: 'https://example.com/thanks',
    cancel_url: 'https://example.com/checkout'
  };

  const timestamp = Math.floor(Date.now() / 1000);
  const idempotencyKey = `pay-stripe-${Date.now()}`;
  const signature = generateHmacSignature(`${timestamp}.${JSON.stringify(paymentData)}`, hmacSecret);

  // HTTP 201 Created on first call; HTTP 200 OK with idempotent:true on replay
  const response = await client.payments.initializePayment({
    idempotencyKey: idempotencyKey,
    xTimestamp: timestamp,
    xSignature: signature,
    requestBody: paymentData
  });

  // Example 201 response body
  // {
  //   "success": true,
  //   "provider": "stripe",
  //   "type": "redirect",
  //   "reference": "PAY-61713784-F4TAOI",
  //   "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1wkhn...",
  //   "session_id": "cs_test_a1wkhnXDJL6XPIzS3rjHUeF0PxUb7yu6Nxw8WJsXpjIgzNG6lSe8x3uue0",
  //   "environment": "test",
  //   "expires_at": "2026-06-21T13:21:54.000Z"
  // }

  // Redirect to Stripe Checkout
  window.location.href = (response as any).checkout_url;
  ```

  ```typescript M-Pesa (Mobile Money) theme={null}
  const paymentData = {
    provider: 'mpesa',
    amount: 1500,
    phone: '254700111222'
  };

  const timestamp = Math.floor(Date.now() / 1000);
  const idempotencyKey = `pay-mpesa-${Date.now()}`;
  const signature = generateHmacSignature(`${timestamp}.${JSON.stringify(paymentData)}`, hmacSecret);

  // HTTP 201 Created on first call
  const response = await client.payments.initializePayment({
    idempotencyKey: idempotencyKey,
    xTimestamp: timestamp,
    xSignature: signature,
    requestBody: paymentData
  });

  // Example 201 response body — an STK push prompt is sent to the phone:
  // {
  //   "success": true,
  //   "provider": "mpesa",
  //   "type": "stk_push",
  //   "reference": "PAY-12345678-ABC123",
  //   "checkout_request_id": "ws_CO_123456789",
  //   "merchant_request_id": "12345-67890-1",
  //   "customer_message": "Success. Request accepted for processing"
  // }
  ```

  ```typescript PayPal (Wallet / Card) theme={null}
  const paymentData = {
    provider: 'paypal',
    amount: 49.99,
    currency: 'USD'
  };

  const timestamp = Math.floor(Date.now() / 1000);
  const idempotencyKey = `pay-paypal-${Date.now()}`;
  const signature = generateHmacSignature(`${timestamp}.${JSON.stringify(paymentData)}`, hmacSecret);

  // HTTP 201 Created on first call
  const response = await client.payments.initializePayment({
    idempotencyKey: idempotencyKey,
    xTimestamp: timestamp,
    xSignature: signature,
    requestBody: paymentData
  });

  // Example 201 response body
  // {
  //   "provider": "paypal",
  //   "type": "popup",
  //   "order_id": "ord_01HX...",
  //   "paypal_order_id": "5O190127TN364715T",
  //   "client_id": "AeA1QIZ...",
  //   "currency": "USD",
  //   "environment": "sandbox"
  // }

  // Use client_id + paypal_order_id to render the PayPal Buttons popup in your
  // storefront. After the buyer approves, call verifyPayment to capture.
  ```
</CodeGroup>

***

### `verifyPayment`

Retrieve the absolute source of truth for a transaction. This is a read operation and does not require HMAC signing.

<Warning>
  **Secret Key required.** `verifyPayment` will reject publishable keys with `403`. Verification responses include charge details (amounts, customer email, payment intents) and must not be exposed to untrusted clients.
</Warning>

```typescript theme={null}
const result = await client.payments.verifyPayment({
  requestBody: {
    provider: 'mpesa',
    reference: 'PAYMENT_REFERENCE_UUID'
  }
});
```

#### Response shape (per-provider `oneOf`)

The response shape depends on the `provider` field — one of four variants is returned:

<CodeGroup>
  ```json Stripe theme={null}
  {
    "provider": "stripe",
    "reference": "cs_test_a1b2c3...",
    "status": "success",
    "amount": 5000,
    "currency": "USD",
    "customer_email": "john.doe@example.com",
    "payment_intent": "pi_3Q...",
    "metadata": {
      "order_id": "ord_01HX..."
    }
  }
  ```

  ```json Paystack theme={null}
  {
    "provider": "paystack",
    "reference": "T123456789",
    "status": "success",
    "amount": 250000,
    "currency": "NGN",
    "channel": "card",
    "customer_email": "john.doe@example.com",
    "paid_at": "2026-05-20T10:24:11.000Z",
    "metadata": {
      "order_id": "ord_01HX..."
    }
  }
  ```

  ```json M-Pesa theme={null}
  {
    "provider": "mpesa",
    "reference": "ws_CO_20260520102411...",
    "status": "success",
    "result_code": "0",
    "result_description": "The service request is processed successfully.",
    "merchant_request_id": "29115-34620561-1"
  }
  ```

  ```json PayPal theme={null}
  {
    "provider": "paypal",
    "reference": "5O190127TN364715T",
    "status": "success",
    "amount": 49.99,
    "currency": "USD",
    "capture_id": "3C679366HH908993F"
  }
  ```
</CodeGroup>

**Status semantics by provider:**

* **Stripe** — `success` when the session/intent is paid, otherwise the raw Stripe `payment_status` (`unpaid`, `no_payment_required`, etc.).
* **Paystack** — Mirrors Paystack's `status` field (`success`, `failed`, `abandoned`).
* **M-Pesa** — Enum: `pending | success | cancelled | failed`. Result code `1032` (user cancelled on phone) maps to `cancelled`.
* **PayPal** — `verify` captures the buyer-approved order and returns `success` once the capture completes. The `reference` you pass is the PayPal order id returned at initialization.

## Provider Support Matrix

| Provider     | Best For             | Requirement | Verification Method |
| :----------- | :------------------- | :---------- | :------------------ |
| **Stripe**   | International Cards  | Email       | Session Query       |
| **PayPal**   | Global Wallet / Card | Order id    | Capture             |
| **Paystack** | African Cards        | Email       | Ref Verification    |
| **M-Pesa**   | Kenyan Mobile Money  | Phone (254) | STK Query           |

***

<Note>
  **Formatting Tip:** For M-Pesa, always provide the phone number in international format without the `+` prefix (e.g., `2547...`).
</Note>

***

### Response Codes

#### `initializePayment`

| Code  | Meaning                                                                               |
| :---- | :------------------------------------------------------------------------------------ |
| `201` | Payment session created (new on first call with idempotency key).                     |
| `200` | Idempotent replay — same key and identical payload, original initialization returned. |
| `400` | Invalid request (missing `provider`/`amount`, invalid phone format for mpesa).        |
| `401` | Invalid API key, or invalid/expired HMAC signature (5-minute replay window).          |
| `403` | Publishable key supplied — initialize requires a secret key.                          |
| `404` | `order_id` provided but no matching order exists.                                     |
| `429` | Rate limit exceeded (100 / hour per API key on initialize).                           |
| `500` | Upstream payment provider error or server configuration error.                        |

#### `verifyPayment`

| Code  | Meaning                                                                              |
| :---- | :----------------------------------------------------------------------------------- |
| `200` | Verification result returned. `status` field varies per provider — see shapes above. |
| `400` | Invalid request, or provider returned a validation error.                            |
| `401` | Invalid API key.                                                                     |
| `403` | Publishable key supplied — verify requires a secret key.                             |
| `429` | Rate limit exceeded.                                                                 |
| `500` | Server error.                                                                        |

#### `getPaymentMethods`

| Code  | Meaning                             |
| :---- | :---------------------------------- |
| `200` | List of configured payment methods. |
| `400` | Invalid `fields` parameter.         |
| `401` | Invalid API key.                    |
| `429` | Rate limit exceeded.                |
| `500` | Server error.                       |
