# ZintGO POS API Contract

## Common Rules

| Rule | Contract |
| --- | --- |
| Authentication | All APIs except `POST /auth/getAuthToken` require `Authorization: Bearer <access_token>`. |
| Store scope | Store id is taken from the bearer token. Do not send `storeId` to customer or points APIs. |
| Success | `200` is the only success status. Use HTTP status as the success indicator. |
| Invalid input | `501` with an error body. |
| Duplicate idempotency | `420` with `error: duplicate_idempotency`. |
| Idempotency | Authenticated POST writes require `Idempotency-Key` header. Do not send it in the body. |
| Correlation | Authenticated POST writes require `Correlation-ID` header. Do not send it in the body. |
| Reads | Reads are `GET`; send inputs as query parameters. Body must be empty. |
| Customer lookup | Customer APIs accept any one of `customerUid`, `customerEmail`, or `customerPhone`. All three are optional fields, but one is mandatory per request. |

Common error response:

```json
{
  "ok": false,
  "error": "invalid_argument",
  "message": "Request failed"
}
```

## Endpoint Matrix

| Type | API | Schema Request | Response | Response Codes | Sample |
| --- | --- | --- | --- | --- | --- |
| Auth | `POST /auth/getAuthToken` | Body: `storeId`, `clientSecret`. | `access_token`, `token_type`, `expires_in`. | `200`, `401`, `403`, `501`, `500` | `{"storeId":"STORE_ID","clientSecret":"SECRET"}` |
| Customer | `POST /customers/add` | Headers: `Idempotency-Key`, `Correlation-ID`. Body: one or both of `customerEmail`, `customerPhone`; optional `displayName`. | Customer, wallet, `created`, `matchedBy`. | `200`, `420`, `401`, `403`, `404`, `501`, `500` | `{"customerEmail":"CUSTOMER_EMAIL","customerPhone":"CUSTOMER_PHONE"}` |
| Customer | `GET /customers/read` | Query: one of `customerUid`, `customerEmail`, `customerPhone`. | Customer and wallet only if wallet exists for authenticated store. | `200`, `401`, `403`, `404`, `501`, `500` | `/customers/read?customerPhone=CUSTOMER_PHONE` |
| Store | `GET /store/config` | No body. No query params. | Current earn and reward config. | `200`, `401`, `403`, `404`, `500` | `/store/config` |
| Store | `POST /store/config` | Headers: `Idempotency-Key`, `Correlation-ID`. Body: config fields to update. | Updated config. | `200`, `420`, `401`, `403`, `404`, `501`, `500` | `{"qrType":"dynamic","earnMode":"bill"}` |
| Points | `POST /points/add` | Headers: `Idempotency-Key`, `Correlation-ID`. Body: one customer identifier, `points`, optional `note`. | Points added and new balance. | `200`, `420`, `401`, `403`, `404`, `501`, `500` | `{"customerUid":"CUSTOMER_UID","points":5}` |
| Points | `POST /points/addExtended` | Headers: `Idempotency-Key`, `Correlation-ID`. Body: one customer identifier; optional `value`, `quantity`, `billAmount`, or `amount`. | Points added, new balance, and reward availability after the add. | `200`, `420`, `401`, `403`, `404`, `501`, `500` | `{"customerPhone":"CUSTOMER_PHONE","value":48.5}` |
| Points | `GET /points/read` | Query: one of `customerUid`, `customerEmail`, `customerPhone`. | Point balance and `walletExists`. | `200`, `401`, `403`, `404`, `501`, `500` | `/points/read?customerEmail=CUSTOMER_EMAIL` |
| Points | `GET /points/readExtended` | Query: one of `customerUid`, `customerEmail`, `customerPhone`. | Point balance, wallet status, and reward summary. | `200`, `401`, `403`, `404`, `501`, `500` | `/points/readExtended?customerUid=CUSTOMER_UID` |
| Points | `POST /points/redeem` | Headers: `Idempotency-Key`, `Correlation-ID`. Body: one customer identifier; optional `billAmount`. | Redeem transaction, new balance, and bill discount calculation. | `200`, `420`, `401`, `403`, `404`, `412`, `501`, `500` | `{"customerUid":"CUSTOMER_UID","billAmount":25}` |

## Customer Identifier

Use any one of these three fields. They are individually optional, but one must be present.

```json
{
  "customerUid": "CUSTOMER_UID",
  "customerEmail": "CUSTOMER_EMAIL",
  "customerPhone": "CUSTOMER_PHONE"
}
```

For `GET` APIs, send the same names as query parameters:

```text
/points/read?customerPhone=CUSTOMER_PHONE
```

## Headers For POST Writes

```http
Authorization: Bearer YOUR_POS_TOKEN
Idempotency-Key: IDEMPOTENCY_KEY
Correlation-ID: CORRELATION_ID
```

Aliases accepted:

| Preferred | Alias |
| --- | --- |
| `Idempotency-Key` | `X-Idempotency-Key` |
| `Correlation-ID` | `X-Correlation-ID` |

## Response Schemas

### Customer Response

```json
{
  "storeId": "STORE_ID",
  "customerUid": "CUSTOMER_UID",
  "customerEmail": "CUSTOMER_EMAIL",
  "customerPhone": "CUSTOMER_PHONE",
  "matchedBy": "phone",
  "customer": {
    "uid": "CUSTOMER_UID",
    "exists": true,
    "displayName": "CUSTOMER_NAME",
    "email": "CUSTOMER_EMAIL",
    "emailLower": "CUSTOMER_EMAIL",
    "phone": "CUSTOMER_PHONE",
    "phoneE164": "CUSTOMER_PHONE"
  },
  "wallet": {
    "exists": true,
    "points": 42
  }
}
```

### Store Config Response

```json
{
  "storeId": "STORE_ID",
  "config": {
    "qrType": "dynamic",
    "earnMode": "bill",
    "pointsPerVisit": 1,
    "pointsPerQty": 1,
    "pointsPer10": 1,
    "rewardType": "percent",
    "rewardThresholdPoints": 100,
    "rewardLabel": "10% off next bill",
    "rewardConfig": {
      "percentOff": 10,
      "percentTarget": "bill",
      "percentItem": ""
    }
  }
}
```

### Reward Summary

```json
{
  "rewardThresholdPoints": 100,
  "rewardType": "percent",
  "rewardLabel": "10% off next bill",
  "rewardConfig": {
    "percentOff": 10,
    "percentTarget": "bill",
    "percentItem": ""
  },
  "availableRewards": 0
}
```

### Points Add Response

```json
{
  "storeId": "STORE_ID",
  "customerUid": "CUSTOMER_UID",
  "customerEmail": "CUSTOMER_EMAIL",
  "customerPhone": "CUSTOMER_PHONE",
  "pointsAdded": 5,
  "newPoints": 47
}
```

### Points Add Extended Response

```json
{
  "storeId": "STORE_ID",
  "customerUid": "CUSTOMER_UID",
  "customerEmail": "CUSTOMER_EMAIL",
  "customerPhone": "CUSTOMER_PHONE",
  "pointsAdded": 4,
  "newPoints": 51,
  "rewardEligible": false,
  "reward": {
    "rewardThresholdPoints": 100,
    "rewardType": "percent",
    "rewardLabel": "10% off next bill",
    "rewardConfig": {
      "percentOff": 10,
      "percentTarget": "bill",
      "percentItem": ""
    },
    "availableRewards": 0
  }
}
```

### Points Read Response

```json
{
  "storeId": "STORE_ID",
  "customerUid": "CUSTOMER_UID",
  "customerEmail": "CUSTOMER_EMAIL",
  "customerPhone": "CUSTOMER_PHONE",
  "points": 51,
  "walletExists": true
}
```

### Points Read Extended Response

```json
{
  "storeId": "STORE_ID",
  "customerUid": "CUSTOMER_UID",
  "customerEmail": "CUSTOMER_EMAIL",
  "customerPhone": "CUSTOMER_PHONE",
  "points": 51,
  "walletExists": true,
  "reward": {
    "rewardThresholdPoints": 100,
    "rewardType": "percent",
    "rewardLabel": "10% off next bill",
    "rewardConfig": {
      "percentOff": 10,
      "percentTarget": "bill",
      "percentItem": ""
    },
    "availableRewards": 0
  }
}
```

### Redeem Response

```json
{
  "storeId": "STORE_ID",
  "customerUid": "CUSTOMER_UID",
  "customerEmail": "CUSTOMER_EMAIL",
  "customerPhone": "CUSTOMER_PHONE",
  "txId": "TRANSACTION_ID",
  "pointsRedeemed": 100,
  "newPoints": 25,
  "rewardLabel": "10% off next bill",
  "billAmount": 25,
  "discountAmount": 2.5,
  "finalBillAmount": 22.5,
  "reward": {
    "rewardThresholdPoints": 100,
    "rewardType": "percent",
    "rewardLabel": "10% off next bill",
    "rewardConfig": {
      "percentOff": 10,
      "percentTarget": "bill",
      "percentItem": ""
    },
    "billAmount": 25,
    "discountAmount": 2.5,
    "finalBillAmount": 22.5,
    "availableRewards": 0
  }
}
```

## Curl Samples

### Add Extended Points

```bash
curl -X POST "/points/addExtended" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_POS_TOKEN" \
  -H "Idempotency-Key: IDEMPOTENCY_KEY" \
  -H "Correlation-ID: CORRELATION_ID" \
  -d '{
    "customerPhone": "CUSTOMER_PHONE",
    "value": 48.5
  }'
```

### Read Extended Points

```bash
curl -X GET "/points/readExtended?customerUid=CUSTOMER_UID" \
  -H "Authorization: Bearer YOUR_POS_TOKEN"
```

### Redeem

```bash
curl -X POST "/points/redeem" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_POS_TOKEN" \
  -H "Idempotency-Key: IDEMPOTENCY_KEY" \
  -H "Correlation-ID: CORRELATION_ID" \
  -d '{
    "customerUid": "CUSTOMER_UID",
    "billAmount": 25
  }'
```
