# Extension Gift Card Flows

This document provides a detailed explanation of the extension gift card gateway flows, including the purpose of the flow, the services involved, and the description of each field in the requests and responses.

{% hint style="warning" %}
All requests are sent with `x-akinon-api-version: v1`. The microservice must validate this header and respond accordingly.
{% endhint %}

**Example configuration:**

```json
{
  "resource_url": "https://gift-card-service.example.com",
  "config": {
    "auth": {
      "username": "<basic-auth-username>",
      "password": "<basic-auth-password>"
    }
  }
}
```

## Purpose of the Flow

The Extension Gift Card Gateway Flow is designed to integrate with third-party gift card providers. Instead of communicating directly with each provider, Commerce communicates through a standardised interface — the `GiftCardExtensionClient` — which the provider must implement. This flow is particularly useful for:

* **Provider Abstraction:** Allowing each provider to handle its own gift card logic without changes to Commerce.
* **OTP Support:** Supporting providers that require OTP verification before a purchase can be completed.
* **Idempotent Refunds:** Ensuring safe retry behaviour for refund operations.

The following configuration parameters are shared during integration:

| Field         | Description                       |
| ------------- | --------------------------------- |
| resource\_url | Base URL of the provider API      |
| username      | Username for basic authentication |
| password      | Password for basic authentication |

### Common Request Headers

All requests include the following headers:

| Header               | Description                                 | Requirement |
| -------------------- | ------------------------------------------- | ----------- |
| Authorization        | Basic \[BASE64\_encoded(username:password)] | Mandatory   |
| x-akinon-api-version | Always `v1`                                 | Mandatory   |
| x-akinon-request-id  | Unique UUID4 identifier for each request    | Mandatory   |
| Content-Type         | Always `application/json`                   | Mandatory   |

### Timeouts

| Type    | Duration |
| ------- | -------- |
| Connect | 5s       |
| Read    | 60s      |

### Success Criteria

All endpoints treat any HTTP response other than `200` as a failure. The `purchase` endpoint has additional success criteria described in its section.

### Common Request Fields

Every request body includes the following base fields:

| Field   | Type   | Required | Description               |
| ------- | ------ | -------- | ------------------------- |
| version | String | Yes      | Always `"v1"`             |
| guid    | String | Yes      | Unique request identifier |

***

## <mark style="color:red;">check-balance (POST)</mark>

Queries card balance and card details by card number. For providers that require OTP, this step initiates the OTP flow.

**URL:**

```
^^/check-balance
```

**Request Body Structure**

| Field      | Type   | Required | Description               |
| ---------- | ------ | -------- | ------------------------- |
| version    | String | Yes      | Always `"v1"`             |
| guid       | String | Yes      | Unique request identifier |
| cardNumber | String | Yes      | Raw card number           |

**Request Body Example**

```json
{
  "version": "v1",
  "guid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "cardNumber": "4111111111111111"
}
```

**Request Body (typescript interface)**

```typescript
interface CheckBalanceRequest {
  version: 'v1';
  guid: string;
  cardNumber: string;
}
```

**Response Body Structure**

| Field            | Type           | Required    | Description                                                                                                                        |
| ---------------- | -------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| cardNumberMasked | String         | Yes         | Masked card number (e.g. `****1234`)                                                                                               |
| purchaseToken    | String \| null | Conditional | Must be returned here for non-OTP flows. For OTP flows, may be returned in the `verify-otp` response instead                       |
| balance          | String \| null | Conditional | Card balance with 2 decimal places (e.g. `"250.00"`). May be null in OTP flows; in that case balance is expected in `verify-otp`   |
| currency         | String \| null | Conditional | ISO 4217 currency code (e.g. `"TRY"`). If returned, must match the order currency; mismatches cause the transaction to be rejected |
| expirationDate   | String \| null | No          | Card expiration date                                                                                                               |
| otpRequired      | Boolean        | Yes         | Whether OTP verification is required. If `true`, `otpRef` is mandatory; missing `otpRef` causes an error                           |
| otpRef           | String \| null | Conditional | Required when `otpRequired=true`. Used in `verify-otp` and `send-otp` requests                                                     |
| maskedPhone      | String \| null | Conditional | Masked phone number to which the OTP was sent. Expected when `otpRequired=true`                                                    |
| expiresIn        | Number \| null | No          | OTP validity duration in seconds                                                                                                   |

**Response Body Example**

```json
{
  "cardNumberMasked": "****1234",
  "purchaseToken": "purchase-token-abc123",
  "balance": "250.00",
  "currency": "TRY",
  "expirationDate": "2026-12-31",
  "otpRequired": false,
  "otpRef": null,
  "maskedPhone": null,
  "expiresIn": null
}
```

**Response Body (typescript interface)**

```ts
interface CheckBalanceResponse {
  cardNumberMasked: string;
  purchaseToken: string | null;
  balance: string | null;
  currency: string | null;
  expirationDate: string | null;
  otpRequired: boolean;
  otpRef: string | null;
  maskedPhone: string | null;
  expiresIn: number | null;
}
```

***

## <mark style="color:red;">send-otp (POST)</mark>

Resends the OTP code for an existing OTP reference. Commerce applies a cooldown between consecutive requests; the provider may also enforce its own throttling.

**URL:**

```
^^/send-otp
```

**Request Body Structure**

| Field   | Type   | Required | Description                                                   |
| ------- | ------ | -------- | ------------------------------------------------------------- |
| version | String | Yes      | Always `"v1"`                                                 |
| guid    | String | Yes      | Unique request identifier                                     |
| otpRef  | String | Yes      | OTP reference code obtained from the `check-balance` response |

**Request Body Example**

```json
{
  "version": "v1",
  "guid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "otpRef": "otp-ref-xyz789"
}
```

**Request Body (typescript interface)**

```ts
interface SendOtpRequest {
  version: 'v1';
  guid: string;
  otpRef: string;
}
```

**Response Body Structure**

| Field       | Type   | Required | Description                                                      |
| ----------- | ------ | -------- | ---------------------------------------------------------------- |
| otpRef      | String | Yes      | OTP reference code. May be the same value or a renewed reference |
| maskedPhone | String | Yes      | Masked phone number to which the OTP was sent                    |
| expiresIn   | Number | No       | New OTP validity duration in seconds                             |

**Response Body Example**

```json
{
  "otpRef": "otp-ref-xyz789",
  "maskedPhone": "+90***5678",
  "expiresIn": 120
}
```

**Response Body (typescript interface)**

```ts
interface SendOtpResponse {
  otpRef: string;
  maskedPhone: string;
  expiresIn?: number;
}
```

***

## <mark style="color:red;">verify-otp (POST)</mark>

Verifies the OTP code entered by the user. Returns `purchaseToken` on success. Even when HTTP `200` is returned, the operation is considered failed if `verified=false`.

**URL:**

```
^^/verify-otp
```

**Request Body Structure**

| Field   | Type   | Required | Description                                                    |
| ------- | ------ | -------- | -------------------------------------------------------------- |
| version | String | Yes      | Always `"v1"`                                                  |
| guid    | String | Yes      | Unique request identifier                                      |
| otpRef  | String | Yes      | OTP reference code obtained from `check-balance` or `send-otp` |
| otpCode | String | Yes      | OTP code entered by the user                                   |

**Request Body Example**

```json
{
  "version": "v1",
  "guid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "otpRef": "otp-ref-xyz789",
  "otpCode": "123456"
}
```

**Request Body (typescript interface)**

```ts
interface VerifyOtpRequest {
  version: 'v1';
  guid: string;
  otpRef: string;
  otpCode: string;
}
```

**Response Body Structure**

| Field         | Type           | Required    | Description                                                                                           |
| ------------- | -------------- | ----------- | ----------------------------------------------------------------------------------------------------- |
| verified      | Boolean        | Yes         | Verification result. If `false` even on HTTP `200`, the operation is treated as failed                |
| purchaseToken | String         | Yes         | Token to be used in the subsequent `purchase` request                                                 |
| balance       | String \| null | Conditional | Current card balance (2 decimal places). Required here if balance was not returned in `check-balance` |
| currency      | String \| null | Conditional | ISO 4217. Required if `balance` is present                                                            |

**Response Body Example**

```json
{
  "verified": true,
  "purchaseToken": "purchase-token-abc123",
  "balance": "250.00",
  "currency": "TRY"
}
```

**Response Body (typescript interface)**

```ts
interface VerifyOtpResponse {
  verified: boolean;
  purchaseToken: string;
  balance?: string;
  currency?: string;
}
```

***

## <mark style="color:red;">purchase (POST)</mark>

Charges payment from the card. If multiple gift cards are applied, a separate request is sent for each card. If any card fails, previously successful charges are automatically voided.

{% hint style="info" %}
Reservations with `amount=0` are excluded from purchase requests.
{% endhint %}

**Success criteria:** HTTP `200` **AND** `status="RESOLVED"` **AND** `subStatus="RESOLVED"` must all be satisfied.

**URL:**

```
^^/purchase
```

**Request Body Structure**

| Field         | Type   | Required | Description                                           |
| ------------- | ------ | -------- | ----------------------------------------------------- |
| version       | String | Yes      | Always `"v1"`                                         |
| guid          | String | Yes      | Unique request identifier                             |
| purchaseToken | String | Yes      | Token obtained from `check-balance` or `verify-otp`   |
| amount        | String | Yes      | Charge amount with 2 decimal places (e.g. `"100.00"`) |
| currency      | String | Yes      | ISO 4217, uppercase (e.g. `"TRY"`)                    |
| orderNumber   | String | Yes      | Commerce order number                                 |

**Request Body Example**

```json
{
  "version": "v1",
  "guid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "purchaseToken": "purchase-token-abc123",
  "amount": "100.00",
  "currency": "TRY",
  "orderNumber": "ORD-12345"
}
```

**Request Body (typescript interface)**

```ts
interface PurchaseRequest {
  version: 'v1';
  guid: string;
  purchaseToken: string;
  amount: string;
  currency: string;
  orderNumber: string;
}
```

**Response Body Structure**

| Field         | Type   | Required | Description                                                                                                                     |
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| status        | String | Yes      | Transaction status. See values table below                                                                                      |
| subStatus     | String | Yes      | Transaction sub-status. See values table below                                                                                  |
| transactionId | String | Yes      | Unique transaction ID assigned by the provider. If empty or missing, Commerce uses a UUID4 fallback, which may affect void flow |

**`status` Values**

| Value    | Description            |
| -------- | ---------------------- |
| RESOLVED | Transaction successful |
| REJECTED | Transaction rejected   |

**`subStatus` Values**

| Value             | Description                                |
| ----------------- | ------------------------------------------ |
| RESOLVED          | Transaction successful                     |
| PROCESSING\_ERROR | Provider-side error during the transaction |
| RISKY             | Transaction flagged as risky               |

**Response Body Example**

```json
{
  "status": "RESOLVED",
  "subStatus": "RESOLVED",
  "transactionId": "txn-9201e160-989c-44d8-8a7e-0f2a06549343"
}
```

**Response Body (typescript interface)**

```ts
interface PurchaseResponse {
  status: 'RESOLVED' | 'REJECTED';
  subStatus: 'RESOLVED' | 'PROCESSING_ERROR' | 'RISKY';
  transactionId: string;
}
```

***

## <mark style="color:red;">void (POST)</mark>

Reverses a purchase transaction. If the order has not yet been created at the time of the void, `orderNumber` may be sent as `null`. Commerce suppresses void errors and does not halt the flow; however, the provider is still expected to return a successful response.

**URL:**

```
^^/void
```

**Request Body Structure**

| Field         | Type           | Required | Description                                                                |
| ------------- | -------------- | -------- | -------------------------------------------------------------------------- |
| version       | String         | Yes      | Always `"v1"`                                                              |
| guid          | String         | Yes      | Unique request identifier                                                  |
| orderNumber   | String \| null | No       | Commerce order number. May be `null` if the order has not yet been created |
| transactionId | String         | Yes      | ID of the purchase transaction to cancel                                   |

**Request Body Example**

```json
{
  "version": "v1",
  "guid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "orderNumber": "ORD-12345",
  "transactionId": "txn-9201e160-989c-44d8-8a7e-0f2a06549343"
}
```

**Request Body (typescript interface)**

```ts
interface VoidRequest {
  version: 'v1';
  guid: string;
  orderNumber: string | null;
  transactionId: string;
}
```

**Response Body Structure**

| Field         | Type   | Required | Description                                                                             |
| ------------- | ------ | -------- | --------------------------------------------------------------------------------------- |
| transactionId | String | Yes      | Unique ID for the void transaction. If empty or missing, Commerce uses a UUID4 fallback |

**Response Body Example**

```json
{
  "transactionId": "void-txn-001"
}
```

**Response Body (typescript interface)**

```ts
interface VoidResponse {
  transactionId: string;
}
```

***

## <mark style="color:red;">refund (POST)</mark>

Issues a partial or full refund.

**URL:**

```
^^/refund
```

**Request Body Structure**

| Field         | Type   | Required | Description                                          |
| ------------- | ------ | -------- | ---------------------------------------------------- |
| version       | String | Yes      | Always `"v1"`                                        |
| guid          | String | Yes      | Unique request identifier                            |
| orderNumber   | String | Yes      | Commerce order number                                |
| transactionId | String | Yes      | ID of the purchase transaction to refund             |
| amount        | String | Yes      | Refund amount with 2 decimal places (e.g. `"50.00"`) |
| currency      | String | Yes      | ISO 4217, uppercase (e.g. `"TRY"`)                   |

**Request Body Example**

```json
{
  "version": "v1",
  "guid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "orderNumber": "ORD-12345",
  "transactionId": "txn-9201e160-989c-44d8-8a7e-0f2a06549343",
  "amount": "50.00",
  "currency": "TRY"
}
```

**Request Body (typescript interface)**

```ts
interface RefundRequest {
  version: 'v1';
  guid: string;
  orderNumber: string;
  transactionId: string;
  amount: string;
  currency: string;
}
```

**Response Body (raw 200)**

```
No Body
```

***

## <mark style="color:red;">history (POST)</mark>

Returns the full transaction history for a given order.

**URL:**

```
^^/history
```

**Request Body Structure**

| Field       | Type   | Required | Description               |
| ----------- | ------ | -------- | ------------------------- |
| version     | String | Yes      | Always `"v1"`             |
| guid        | String | Yes      | Unique request identifier |
| orderNumber | String | Yes      | Commerce order number     |

**Request Body Example**

```json
{
  "version": "v1",
  "guid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "orderNumber": "ORD-12345"
}
```

**Request Body (typescript interface)**

```ts
interface HistoryRequest {
  version: 'v1';
  guid: string;
  orderNumber: string;
}
```

**Response Body Structure**

| Field                     | Type   | Required | Description                                                                       |
| ------------------------- | ------ | -------- | --------------------------------------------------------------------------------- |
| orderNumber               | String | Yes      | Commerce order number                                                             |
| status                    | String | Yes      | Overall order status                                                              |
| subStatus                 | String | Yes      | Overall order sub-status                                                          |
| paymentTransactionHistory | Array  | Yes      | Transaction list. The key must be `paymentTransactionHistory`, not `transactions` |

**`paymentTransactionHistory` Item Fields**

| Field         | Type              | Required | Description                                    |
| ------------- | ----------------- | -------- | ---------------------------------------------- |
| transactionId | String            | Yes      | Transaction ID                                 |
| type          | String            | Yes      | Transaction type: `PURCHASE`, `VOID`, `REFUND` |
| amount        | String            | Yes      | Transaction amount with 2 decimal places       |
| currency      | String            | Yes      | ISO 4217                                       |
| statusCode    | String            | Yes      | Transaction status code                        |
| subStatusCode | String            | Yes      | Transaction sub-status code                    |
| timestamp     | String (ISO 8601) | Yes      | Transaction timestamp                          |

**Response Body Example**

```json
{
  "orderNumber": "ORD-12345",
  "status": "RESOLVED",
  "subStatus": "RESOLVED",
  "paymentTransactionHistory": [
    {
      "transactionId": "txn-9201e160-989c-44d8-8a7e-0f2a06549343",
      "type": "PURCHASE",
      "amount": "100.00",
      "currency": "TRY",
      "statusCode": "RESOLVED",
      "subStatusCode": "RESOLVED",
      "timestamp": "2026-04-28T10:00:00Z"
    }
  ]
}
```

**Response Body (typescript interface)**

```ts
interface HistoryResponse {
  orderNumber: string;
  status: string;
  subStatus: string;
  paymentTransactionHistory: PaymentTransaction[];
}

interface PaymentTransaction {
  transactionId: string;
  type: 'PURCHASE' | 'VOID' | 'REFUND';
  amount: string;
  currency: string;
  statusCode: string;
  subStatusCode: string;
  timestamp: string;
}
```

***

## Typical Flows

### Non-OTP Provider

```
check-balance → purchase
```

### OTP Provider

```
check-balance → [send-otp] → verify-otp → purchase
```

> `send-otp` is optional; it is used when the user needs the OTP resent.

### Payment Reversal

```
void
```

### Refund

```
refund
```

***

## Response Body For Errors (ALL)

All endpoints return errors in the following format. The first element of the `errors` array is used as the user-facing message.

**Response Body (raw 4xx/5xx json)**

```json
{
  "errors": ["Error message"]
}
```

**Response Body (raw 4xx/5xx typescript interface)**

```ts
interface ErrorResponse {
  errors: string[];
}
```

***

## <mark style="color:red;">Changelog</mark>

### Version 1.0.0

#### Added

* Initial implementation of the Extension Gift Card Gateway
* `check-balance` endpoint for card balance queries and OTP flow initiation
* `send-otp` endpoint for resending OTP codes
* `verify-otp` endpoint for OTP verification
* `purchase` endpoint for charging gift cards
* `void` endpoint for cancelling purchase transactions
* `refund` endpoint for partial and full refunds
* `history` endpoint for order transaction history
* HTTP Basic Auth support
* `x-akinon-request-id` and `x-akinon-api-version` headers on all requests


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://apidocs.akinon.com/flows/extension-gift-card-flows.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
