diff --git a/api-reference/general/get-token-rate-v1.mdx b/api-reference/general/get-token-rate-v1.mdx index bc89270..fdccd64 100644 --- a/api-reference/general/get-token-rate-v1.mdx +++ b/api-reference/general/get-token-rate-v1.mdx @@ -5,4 +5,4 @@ openapi: "openapi-v1.yaml GET /rates/{token}/{amount}/{fiat}" Retrieve an exchange rate for a token amount and fiat pair (legacy v1 API). Response `data` is a **single** rate value (string/number). -For **network in the path** and **buy/sell** quotes, use [Get Token Rate (v2)](/api-reference/general/get-token-rate) — `GET /v2/rates/{network}/{token}/{amount}/{fiat}`. +For **network in the path** and **buy/sell** quotes, use [Get Token Rate (v2)](/api-reference/general/get-token-rate) — `GET /v2/rates/{network}/{from}/{amount}/{to}`. diff --git a/api-reference/general/get-token-rate.mdx b/api-reference/general/get-token-rate.mdx index 79586cb..4a76b1d 100644 --- a/api-reference/general/get-token-rate.mdx +++ b/api-reference/general/get-token-rate.mdx @@ -1,9 +1,9 @@ --- title: 'Get Token Rate' -openapi: "openapi-v2.yaml GET /rates/{network}/{token}/{amount}/{fiat}" +openapi: "openapi-v2.yaml GET /rates/{network}/{from}/{amount}/{to}" --- -Get buy and/or sell quotes for a token amount and fiat pair on a **specific network**. This is the recommended public rates URL for v2 integrations. +Get buy and/or sell quotes on a **specific network**—either a **token + fiat** pair or a **fiat + fiat** corridor (via USDC). This is the recommended public rates URL for v2 integrations. The legacy endpoint **`GET /v1/rates/{token}/{amount}/{fiat}`** (optional `network` query) returns a **single numeric** `data` field. The v2 path below returns a structured **`buy` / `sell`** payload. @@ -14,23 +14,32 @@ Get buy and/or sell quotes for a token amount and fiat pair on a **specific netw | Parameter | Description | |-----------|-------------| | `network` | Network id: `ethereum`, `base`, `bnb-smart-chain`, `lisk`, `scroll`, `celo`, `arbitrum-one`, `polygon`, `asset-chain` | -| `token` | Token symbol (e.g. `USDT`, `USDC`) | -| `amount` | Crypto notional the quote applies to | -| `fiat` | Fiat currency code (e.g. `NGN`, `KES`) | +| `from` | Fiat currency code **or** token symbol on `network` (paired with `to`) | +| `amount` | For **token + fiat**: crypto (token) notional. For **fiat + fiat**: amount in **`from`** fiat. | +| `to` | Fiat currency code **or** token symbol on `network` (paired with `from`) | + +**Token + fiat:** exactly **one** fiat and **one** token (`.../USDT/100/NGN` ≡ `.../NGN/100/USDT`). **`from`/`to` cannot both be tokens.** **Path order does not imply trade direction**—use `side` or read **`data.buy`** / **`data.sell`** for onramp vs offramp display. + +**Fiat + fiat:** both are fiat codes; the server quotes through **USDC** on the same network. **`sell.rate`** = **`to` per 1 `from`**. **`buy.rate`** = **`from` per 1 `to`** (inverse corridor, **not** the reciprocal of `sell`—each side uses the appropriate leg bid/ask). ## Query parameters | Parameter | Required | Description | |-----------|----------|-------------| | `side` | No | `buy` or `sell` to return **only** that side. Omit to receive **both** when available. | +| `from_source` | No | If `from` matches **both** a fiat currency and a crypto asset on the network, set to **`fiat`** or **`crypto`**. Omit when unambiguous. | +| `to_source` | No | If `to` matches **both** a fiat currency and a crypto asset on the network, set to **`fiat`** or **`crypto`**. Omit when unambiguous. | | `provider_id` | No | Exactly **8 alphabetic** characters (`A–Z`, `a–z`) to pin the quote to one provider. | ## Example ```bash -# Both buy and sell quotes on Base +# Both buy and sell quotes on Base (token then fiat) GET https://api.paycrest.io/v2/rates/base/USDT/100/NGN +# Same pair with fiat then token +GET https://api.paycrest.io/v2/rates/base/NGN/100/USDT + # Sell side only (typical for offramp display) GET https://api.paycrest.io/v2/rates/base/USDT/100/NGN?side=sell @@ -44,7 +53,7 @@ Each populated side includes: | Field | Type | Description | |-------|------|-------------| -| `rate` | string | Quoted rate (fiat per crypto) for that side | +| `rate` | string | For token+fiat: fiat per crypto. For fiat+fiat: see path rules above (`sell` = `to` per `from`, `buy` = `from` per `to`). | | `providerIds` | string[] | Provider id(s) for the quote | | `orderType` | string | e.g. `regular` or `otc` | | `refundTimeoutMinutes` | integer | Minutes until automatic refund for that flow | @@ -76,13 +85,13 @@ When `side=buy` or `side=sell`, only that key is present under `data`. | HTTP | Typical cause | |------|----------------| -| **400** | Invalid `amount`, invalid `side` (must be `buy` or `sell`), invalid `provider_id` (must be exactly 8 letters A–Z or a–z), token not supported on network, unsupported fiat, or conversion rules (e.g. stable only to its base fiat) | +| **400** | Invalid `amount`, invalid `side`, invalid `from_source`/`to_source` (must be `fiat` or `crypto`), ambiguous segment without disambiguation, invalid `provider_id`, unsupported asset on network, or conversion rules (e.g. stable only to its base fiat) | | **404** | No provider available for the amount/currency/network | | **503** | Banking/mobile network issues affecting providers for the currency | | **500** | Internal error while resolving the rate | ## Rate resolution -Behavior matches the priority-queue–based resolution used when creating orders: the quote reflects provider selection and validation for the requested **side** (`buy` vs `sell`). Use **`sell`** for **offramp** (crypto → fiat) UX and **`buy`** for **onramp** (fiat → crypto) when displaying a rate before `POST /v2/sender/orders`. +Behavior matches the priority-queue–based resolution used when creating orders: the quote reflects provider selection and validation for the requested **side** (`buy` vs `sell`). **Path segment order (`from` / `to`) does not select direction**—use `?side=`, or read **`data.sell`** for **offramp** (crypto → fiat) display and **`data.buy`** for **onramp** (fiat → crypto) before `POST /v2/sender/orders`. See also [Get Token Rate (v1)](/api-reference/general/get-token-rate-v1) for the legacy scalar response. diff --git a/api-reference/introduction.mdx b/api-reference/introduction.mdx index ed4967b..aa965f0 100644 --- a/api-reference/introduction.mdx +++ b/api-reference/introduction.mdx @@ -69,7 +69,7 @@ For protocol information and utilities: - **`GET /currencies`** - List supported fiat currencies - **`GET /institutions/{currency_code}`** - List supported institutions - **`GET /tokens`** - List supported tokens and contract addresses -- **`GET /rates/{network}/{token}/{amount}/{fiat}`** - Get public buy/sell rate quotes for a token/fiat pair on a network (no API key) +- **`GET /rates/{network}/{from}/{amount}/{to}`** - Get public buy/sell rate quotes (token+fiat or fiat+fiat via USDC on a network; no API key) - **`GET /pubkey`** - Get aggregator public key - **`POST /verify-account`** - Verify bank account details before creating an order - **`GET /orders/{chain_id}/{id}`** - Get order status by onchain Gateway ID diff --git a/implementation-guides/sender-api-integration.mdx b/implementation-guides/sender-api-integration.mdx index d707d4e..1fc50ef 100644 --- a/implementation-guides/sender-api-integration.mdx +++ b/implementation-guides/sender-api-integration.mdx @@ -290,6 +290,59 @@ order = requests.post( --- +## KES mobile money (M-Pesa, Till, Paybill) + +Kenya offramp to mobile rails uses institution **`SAFAKEPC`** (Safaricom M-Pesa) or another KES **mobile_money** code from **[GET /institutions/KES](/api-reference/general/list-supported-institutions)**. **Till** and **Paybill** are not separate institution codes—you pass them in **`destination.recipient.metadata`**. + +| Channel | `metadata.channel` | `accountIdentifier` | Notes | +|---------|-------------------|---------------------|--------| +| Phone M-Pesa | `Mobile` or omit | Mobile number (e.g. `254712345678`) | Default when channel is omitted and the identifier looks like a phone number. Use **`Mobile`** in new integrations (not `MoMo`, which is provider-internal). | +| Till (buy goods) | `Till` | Till / buy-goods number | Do not rely on phone normalization; till numbers are not E.164 phones. | +| Paybill | `Paybill` | Paybill account or reference | Set **`metadata.businessNumber`** to the paybill business number when required. | + +**Example — Till** + +```json +"destination": { + "type": "fiat", + "currency": "KES", + "recipient": { + "institution": "SAFAKEPC", + "accountIdentifier": "123456", + "accountName": "Merchant Name", + "memo": "Order 42", + "metadata": { "channel": "Till" } + } +} +``` + +**Example — Paybill** + +```json +"destination": { + "type": "fiat", + "currency": "KES", + "recipient": { + "institution": "SAFAKEPC", + "accountIdentifier": "INV-001", + "accountName": "Customer Name", + "memo": "Invoice", + "metadata": { + "channel": "Paybill", + "businessNumber": "400200" + } + } +} +``` + +**KYC on offramp:** optional **`destination.kyc`** applies to the **recipient** only. Do not send sender or provider KYB on offramp create. + +**GET / webhooks (v2):** for offramp orders, **`destination.recipient.metadata`** returns the metadata you supplied on create (same shape as above). **`destination.kyc`** returns destination KYC when stored. There is no v1-style flat metadata fallback on v2 GET. + +**Verify-account:** Till and Paybill identifiers are not normalized as mobile phone numbers. Use the exact till or paybill values you will send on the order. + +--- + ## Handle the create response The create response includes a `providerAccount` whose shape depends on direction: **offramp** gives a **crypto receive address**; **onramp** gives **virtual account / fiat transfer** instructions. @@ -479,6 +532,8 @@ Use this when you want **clearer UX** before the user confirms—not because the - **Offramp** — verify the **fiat recipient** you will send in `destination.recipient` (same `institution`, `accountIdentifier`, and `currency` as the order). - **Onramp** — verify **`source.refundAccount`** (where fiat refunds go), not the crypto wallet in `destination.recipient`. +For **KES Till or Paybill**, pass the same **`accountIdentifier`** and **`metadata.channel`** you will use on create. Phone-style normalization does not apply to till or paybill numbers. + ```bash curl -X POST "https://api.paycrest.io/v2/verify-account" \ -H "API-Key: YOUR_API_KEY" \ @@ -494,7 +549,7 @@ A `200 OK` response returns the resolved `accountName` in `data`. If `data` is a ### Prefetch a quote for the UI -**`GET /v2/rates/{network}/{token}/{amount}/{fiat}`** is **public** (no API key). Use it when you want to **show** a rate before submit, or when you will pass **`rate`** on create (it must stay within market tolerance). If you omit **`rate`** on create, the API still picks an acceptable rate. +**`GET /v2/rates/{network}/{from}/{amount}/{to}`** is **public** (no API key). **`from` and `to`** are the fiat code and token symbol in either order. Use it when you want to **show** a rate before submit, or when you will pass **`rate`** on create (it must stay within market tolerance). If you omit **`rate`** on create, the API still picks an acceptable rate. The JSON `data` object includes **`buy`** and **`sell`** (unless you pass `?side=buy` or `?side=sell`). Use **`data.sell.rate`** for **offramp** and **`data.buy.rate`** for **onramp** when displaying a quote. **`provider_id`** is optional and must be exactly **8 letters** (`A–Z` or `a–z`) if you pin a provider. diff --git a/openapi-v2.yaml b/openapi-v2.yaml index 808c00b..c76902d 100644 --- a/openapi-v2.yaml +++ b/openapi-v2.yaml @@ -17,6 +17,20 @@ components: type: apiKey in: header name: API-Key + parameters: + OptionalOrderDirection: + name: direction + in: query + required: false + schema: + type: string + enum: [onramp, offramp] + description: | + Filter to **onramp** (fiat → crypto) or **offramp** (crypto → fiat). Omit to include **both** directions. + + On **stats** endpoints, invalid values return **400 Bad Request**. On **list** endpoints, values other than `onramp` or `offramp` are ignored (no direction filter). + + **HMAC (GET):** the signed payload includes all query parameters—include `direction` when you send it (alongside `timestamp`, `currency`, etc.). schemas: # Sender Schemas @@ -604,6 +618,9 @@ components: institution: type: string description: Bank or mobile provider code (SWIFT prefix or Paycrest institution code) + institutionName: + type: string + description: Human-readable institution name (e.g. OPay). Present on GET responses when the code resolves in Paycrest. accountIdentifier: type: string description: Account number or mobile number @@ -627,6 +644,9 @@ components: providerId: type: string description: Pin order to a specific provider (optional) + kyc: + type: object + description: Optional destination (recipient) KYC for offramp. Do not send sender or provider KYB on offramp create. recipient: $ref: '#/components/schemas/V2FiatRecipient' @@ -636,13 +656,29 @@ components: properties: institution: type: string + description: Institution code (e.g. SAFAKEPC for Safaricom M-Pesa). Till and Paybill use the same mobile institution with channel metadata. + institutionName: + type: string + description: Human-readable institution name (e.g. OPay). Present on GET responses when the code resolves in Paycrest. accountIdentifier: type: string + description: Bank account, mobile phone (E.164 or local), till number, or paybill account reference depending on channel. accountName: type: string memo: type: string description: Payment narration / reference + metadata: + type: object + description: Corridor-specific hints. For KES mobile offramp, use channel and optional businessNumber (see Sender API Integration guide). + properties: + channel: + type: string + enum: [Mobile, Till, Paybill] + description: KES payout channel. Mobile = phone M-Pesa; Till = buy goods; Paybill = paybill. Omit for default phone M-Pesa when accountIdentifier is a mobile number. + businessNumber: + type: string + description: Paybill business number (required for Paybill when not inferrable from accountIdentifier alone). V2CryptoDestination: type: object @@ -690,7 +726,7 @@ components: properties: institution: type: string - description: Bank or mobile provider code + description: Institution label for the virtual account (human-readable; from provider or stored metadata). accountIdentifier: type: string description: Account number or mobile number to send fiat to @@ -899,6 +935,8 @@ paths: - ApiKeyAuth: [] servers: - url: https://api.paycrest.io/v2 + parameters: + - $ref: '#/components/parameters/OptionalOrderDirection' responses: '200': description: Sender stats @@ -911,6 +949,12 @@ paths: properties: data: $ref: '#/components/schemas/SenderStatsResponse' + '400': + description: Bad request (e.g. invalid `direction`) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' '401': description: Unauthorized content: @@ -1190,8 +1234,11 @@ paths: parameters: - in: query name: currency + required: true schema: type: string + description: Fiat currency code (e.g. NGN, KES). + - $ref: '#/components/parameters/OptionalOrderDirection' responses: '200': description: Provider stats @@ -1205,7 +1252,7 @@ paths: data: $ref: '#/components/schemas/ProviderStatsResponse' '400': - description: Bad request + description: Bad request (e.g. missing `currency`, invalid `direction`) '401': description: Unauthorized @@ -1326,13 +1373,21 @@ paths: items: $ref: '#/components/schemas/SupportedTokenResponse' - /rates/{network}/{token}/{amount}/{fiat}: + /rates/{network}/{from}/{amount}/{to}: get: tags: - General summary: Get token rate (v2) description: | - Public quote for a crypto notional on a specific network. Returns **buy** and/or **sell** quotes (fiat per crypto), provider id(s), order type, and refund timeout. + Public quote on a specific network. Returns **buy** and/or **sell** quotes, order type, and refund timeout. + + **Token + fiat:** each of **`from`** / **`to`** is either an enabled **fiat currency code** or an enabled **crypto (token) symbol** on **`network`**. Exactly one must be fiat and one crypto (`.../USDT/100/NGN` and `.../NGN/100/USDT` are equivalent). **`amount`** is the **crypto notional**. Rates are **fiat per 1 token** for each side. **`from` and `to` cannot both be crypto.** + + **Ambiguous segment:** if a path value matches **both** an enabled fiat code **and** an enabled crypto symbol on that network (e.g. same string), the request returns **400** unless **`from_source`** and/or **`to_source`** is set to **`fiat`** or **`crypto`** to pick the leg. + + **Fiat + fiat:** both segments are fiat codes. The quote **bridges through USDC** on the same **`network`**. **`amount`** is denominated in **`from`** fiat. **`sell`** is **destination fiat per 1 unit of `from` fiat** (buy USDC with `from`, sell USDC for `to`). **`buy`** is **`from` fiat per 1 unit of `to` fiat** (inverse corridor; buy USDC with `to`, sell USDC for `from`)—asymmetric from **`sell`**, not a simple reciprocal. + + Path order does **not** imply trade direction for token/fiat pairs; omitting **`side`** returns **both** sides when available. For the legacy single-number response, use **`GET /v1/rates/{token}/{amount}/{fiat}`** with optional `network` query. servers: @@ -1346,29 +1401,41 @@ paths: enum: [ethereum, base, bnb-smart-chain, lisk, scroll, celo, arbitrum-one, polygon, asset-chain] description: Blockchain network identifier (lowercase in URL). - in: path - name: token + name: from required: true schema: type: string - description: Token symbol (e.g. USDT, USDC). + description: Fiat code or token symbol on `network` (paired with `to`). - in: path name: amount required: true schema: type: string - description: Token amount the quote applies to. + description: For token+fiat pairs, the **token** notional. For fiat+fiat pairs, the amount in **`from`** fiat. - in: path - name: fiat + name: to required: true schema: type: string - description: Fiat currency code (e.g. NGN, KES). + description: Fiat code or token symbol on `network` (paired with `from`). - in: query name: side schema: type: string enum: [buy, sell] description: Return only the buy or sell quote. Omit for both sides. + - in: query + name: from_source + schema: + type: string + enum: [fiat, crypto] + description: When `from` matches both a fiat and a crypto asset on `network`, set to `fiat` or `crypto` to disambiguate. Ignored when `from` is unambiguous. + - in: query + name: to_source + schema: + type: string + enum: [fiat, crypto] + description: When `to` matches both a fiat and a crypto asset on `network`, set to `fiat` or `crypto` to disambiguate. Ignored when `to` is unambiguous. - in: query name: provider_id schema: @@ -1388,7 +1455,7 @@ paths: data: $ref: '#/components/schemas/V2RateQuoteResponse' '400': - description: Bad request (invalid amount, side, provider_id, unsupported token/fiat/network, etc.) + description: Bad request (invalid amount, side, provider_id, ambiguous from/to without from_source/to_source, unsupported asset/network, etc.) '404': description: No provider available for the requested swap '500': @@ -1608,6 +1675,7 @@ paths: schema: type: string enum: [initiated, deposited, pending, fulfilling, fulfilled, validated, settling, settled, cancelled, refunding, refunded, expired] + - $ref: '#/components/parameters/OptionalOrderDirection' responses: '200': description: List of orders @@ -1663,7 +1731,7 @@ paths: tags: - Provider summary: List v2 payment orders (provider) - description: Returns a paginated list of payment orders assigned to the authenticated provider, in the v2 response schema. Requires a `currency` query parameter. Supports `search`, `export=csv`, `from`/`to` date filters, and `ordering` (asc/desc). + description: Returns a paginated list of payment orders assigned to the authenticated provider, in the v2 response schema. Requires a `currency` query parameter. Supports optional `direction` (`onramp` / `offramp`), `search`, `export=csv`, `from`/`to` date filters, and `ordering` (asc/desc). security: - ApiKeyAuth: [] servers: @@ -1720,6 +1788,7 @@ paths: type: string format: date description: End date for CSV export (YYYY-MM-DD). Required when `export=csv`. + - $ref: '#/components/parameters/OptionalOrderDirection' responses: '200': description: Paginated list of orders diff --git a/resources/changelog.mdx b/resources/changelog.mdx index 86138b0..7198d57 100644 --- a/resources/changelog.mdx +++ b/resources/changelog.mdx @@ -7,11 +7,35 @@ This page tracks significant changes to the Paycrest API and protocol. For full --- -## Q1 2026 +## Q2 2026 + +### KES Till and Paybill offramp (May 2026) + +**Offramp** to Kenya mobile rails now supports **Till** (buy goods) and **Paybill** in addition to phone M-Pesa. + +- On create, set **`destination.recipient.metadata.channel`** to **`Mobile`**, **`Till`**, or **`Paybill`** (use **`Mobile`** for phone M-Pesa; omit channel only when the identifier is a standard mobile number). +- **Paybill** may require **`metadata.businessNumber`**. +- Optional **`destination.kyc`** on offramp is **recipient-only** (no sender/provider KYB on create). +- **v2 GET / webhooks:** **`destination.recipient.metadata`** echoes create-time recipient metadata; **`destination.kyc`** when stored. +- Till/Paybill identifiers are not normalized as phone numbers on create or verify-account. + +See **[Sender API Integration — KES mobile money](/implementation-guides/sender-api-integration#kes-mobile-money-m-pesa-till-paybill)** and **[Supported Currencies](/resources/supported-currencies)**. + +--- + +### Optional `direction` on v2 order lists and stats (April 2026) + +**`GET /v2/sender/orders`** and **`GET /v2/provider/orders`** accept an optional query parameter **`direction`**, either **`onramp`** or **`offramp`**, to return only orders for that flow. Omit it to list both directions (default). On list endpoints, values other than `onramp` or `offramp` are ignored (no direction filter is applied). + +**`GET /v2/sender/stats`** and **`GET /v2/provider/stats`** accept the same optional **`direction`** parameter; omitted means totals and aggregates include both directions. Invalid **`direction`** values return **400 Bad Request**. + +**HMAC (ApiKeyAuth) on GET:** request signatures are built from all query parameters—include **`direction`** in the signed payload when you send it (e.g. alongside `timestamp` and `currency` on provider stats). + +--- ### Public v2 rates path and buy/sell quotes (April 2026) -**`GET /v2/rates/{network}/{token}/{amount}/{fiat}`** is the supported public rates URL. The network is a **path** segment (not the `network` query used on v1). Responses use structured **`buy`** and **`sell`** objects (`rate`, `providerIds`, `orderType`, `refundTimeoutMinutes`) instead of a single scalar `data` value. +**`GET /v2/rates/{network}/{from}/{amount}/{to}`** is the supported public rates URL. The network is a **path** segment (not the `network` query used on v1). **`from` and `to`** may be a **token + fiat** pair (either order; **not** two tokens) or **two fiat codes**. Fiat–fiat quotes **bridge through USDC** on the same network; **`amount`** is in **`from`** fiat; **`sell`** / **`buy`** use asymmetric inverse-corridor semantics (not simple reciprocals). Path order does **not** imply trade direction for token/fiat—omit **`side`** to get **both** sides when available. Responses use structured **`buy`** and **`sell`** objects (`rate`, `providerIds`, `orderType`, `refundTimeoutMinutes`) instead of a single scalar `data` value. Optional query parameters: @@ -34,6 +58,8 @@ Onchain principal amounts may differ slightly from older builds that used a loos --- +## Q1 2026 + ### v2 API — Onramp & Offramp (March 2026) The v2 API introduces a unified endpoint for both offramp and onramp flows, replacing the v1 offramp-only schema. diff --git a/resources/code-standards.mdx b/resources/code-standards.mdx index 975750a..74c7cec 100644 --- a/resources/code-standards.mdx +++ b/resources/code-standards.mdx @@ -48,6 +48,8 @@ For mobile payment providers, local banks without SWIFT codes, and other financi - **SAFAKEPC** - Safaricom M-Pesa (Kenya) - **AIRTKEPC** - Airtel Money (Kenya) +For KES **Till** and **Paybill** offramps, keep **`SAFAKEPC`** (or the same mobile institution you use for M-Pesa) and set **`destination.recipient.metadata.channel`** to **`Till`** or **`Paybill`** (and **`businessNumber`** for Paybill). See **[Sender API Integration — KES mobile money](/implementation-guides/sender-api-integration#kes-mobile-money-m-pesa-till-paybill)**. + ## Code Format Rules ### Currency Codes diff --git a/resources/supported-currencies.mdx b/resources/supported-currencies.mdx index 6cbc717..7386707 100644 --- a/resources/supported-currencies.mdx +++ b/resources/supported-currencies.mdx @@ -53,6 +53,11 @@ const order = { ``` ### Mobile Money (Kenya) + +Phone M-Pesa, **Till** (buy goods), and **Paybill** all use a KES mobile institution (typically **`SAFAKEPC`**). Set **`destination.recipient.metadata.channel`** to **`Mobile`**, **`Till`**, or **`Paybill`**. Full rules and KYC notes are in **[Sender API Integration — KES mobile money](/implementation-guides/sender-api-integration#kes-mobile-money-m-pesa-till-paybill)**. + +**Phone M-Pesa** + ```javascript const order = { amount: "50", @@ -69,12 +74,37 @@ const order = { institution: "SAFAKEPC", accountIdentifier: "254700000000", accountName: "Jane Smith", - memo: "Mobile payment" + memo: "Mobile payment", + metadata: { channel: "Mobile" } } } }; ``` +**Till** + +```javascript +recipient: { + institution: "SAFAKEPC", + accountIdentifier: "123456", + accountName: "Shop Name", + memo: "Payment", + metadata: { channel: "Till" } +} +``` + +**Paybill** + +```javascript +recipient: { + institution: "SAFAKEPC", + accountIdentifier: "INV-001", + accountName: "Customer", + memo: "Bill pay", + metadata: { channel: "Paybill", businessNumber: "400200" } +} +``` + ### PIX (Brazil) ```javascript const order = {