Mobile Billing — Payment
POST /payments/direct/payment with paymentMethod: "MOBILE_BILLING" adds the amount to the buyer's mobile phone bill (Faturana Yansıt). The buyer enters the code sent to their line and accepts the provider's agreement; you send both with confirm. This page shows every request field, the response, the confirmation and the errors for mobile billing; the action, confirm and error-code rules shared by all methods are on Payment.
Whether the method is open on your account, its provider, amount limits and the fields asked from the buyer are in the MOBILE_BILLING row of the GET /payments/methods response.
How it works
Start the payment
Send the line to be billed in mobileBilling.gsmNumber. If you let the buyer choose the provider, send its code in mobileBilling.providerCode; if you omit it, the platform chooses the provider. See Choosing the provider.
Show the code and the agreement
The response (202) carries an OTP action: the code goes to the action.otp.maskedTarget line by SMS. When action.consent.required is true, show the buyer the agreement at action.consent.url and get their acceptance.
Confirm
Send the code and the consent with confirm: { "otp": "…", "consentAccepted": true }. When the payment completes the response is 200 and payment.captured is sent.
If the line was already approved for this method, the provider may complete the payment without a code: in that case there is no action in the response and the result returns directly.
Choosing the provider
On this endpoint you do not have to choose the mobile billing provider: on the MOBILE_BILLING row providerSelectionMode is PLATFORM and providerSelection is OPTIONAL. mobileBilling.providerCode is optional:
- If you omit it, the platform picks, before any money moves, one of the providers connected to your account that can take this payment, and pins it to the payment. When one provider is connected, that one is used.
- If you send it, that provider is used. If the code is not connected to this method on your account, the request gets
422payment.provider.not_available. - After the choice the payment does not move to another provider, another method or card. The provider that runs the payment is in the
providerCodefield of the response and the webhook. - If none of the connected providers can take this payment, the request gets
422payment.provider.none_eligible; see No provider is eligible.
The checks the platform uses to choose: Listing Methods › How the platform chooses.
You can show the providers on your own payment page in the order of the GET /payments/methods response (displayName, logoUrl) and send the code the buyer chooses. In the Checkout Form the buyer always chooses the provider → Checkout Form — Payment › Provider choice.
No provider is eligible
When more than one provider is connected, you omit providerCode and none of them can take this payment, the payment is not started, nothing is added to the bill and no code is sent:
{
"status": "failure",
"code": "payment.provider.none_eligible",
"message": "None of the connected providers can take this payment; the payment was not started and nothing was charged. …",
"statusCode": 422,
"timestamp": "2026-10-12T09:00:00.000Z",
"service": "api-gateway",
"systemTime": 1791795600000,
"requestId": "…",
"docsUrl": "https://docs.lunixi.com/getting-started/errors#payment.provider.none_eligible",
"details": {
"paymentMethod": "MOBILE_BILLING",
"candidates": [
{ "providerCode": "<providerCode>", "outcome": "INELIGIBLE", "reason": "AMOUNT_OUT_OF_RANGE" },
{ "providerCode": "<providerCode-2>", "outcome": "UNAVAILABLE", "reason": "ELIGIBILITY_TIMEOUT" }
]
}
}details.candidates[] carries one row per provider. The rows contain no personal data.
| Field | Description |
|---|---|
providerCode | The provider's code |
outcome | INELIGIBLE: the provider cannot take this payment. UNAVAILABLE: no eligibility answer could be obtained from the provider. NOT_CHECKED: the provider was not tried because the selection time ran out |
reason | Reason code or null. Examples: CURRENCY_NOT_SUPPORTED, AMOUNT_OUT_OF_RANGE, PREAUTH_UNSUPPORTED, INPUT_INVALID, ELIGIBILITY_ERROR, ELIGIBILITY_TIMEOUT, SELECTION_BUDGET_EXHAUSTED; it can also be the provider's own decline code |
- If any result is
UNAVAILABLEorNOT_CHECKED, you can retry the same request later. - You can also choose the provider yourself with
mobileBilling.providerCode. - If every result is
INELIGIBLE, offer the buyer another method.
Request
POST https://api-gateway.lunixi.com/api/v1/payments/direct/payment
Content-Type: application/jsonThe request carries Step-Up authentication (Bearer token and Ed25519 signature headers; see Authentication) and a required Idempotency-Key header (at most 200 characters); the rules are the same as in Payment — Idempotency.
Method fields
| Field | Type | Required | Description |
|---|---|---|---|
paymentMethod | string | Yes | MOBILE_BILLING |
mobileBilling | object | Yes | Mobile billing block; its fields are in the rows below |
mobileBilling.gsmNumber | string | Yes* | Line to be billed; optional + and 10–15 digits. The code is sent to this line. *When omitted, buyer.gsmNumber is used; if neither is present the request gets 400 payment.method.block_invalid |
mobileBilling.providerCode | string | No | Provider code (GET /payments/methods). When omitted, the platform chooses the provider; see Choosing the provider |
mobileBilling.<field> | — | Per definition | Other fields in the provider's inputFields definition; see Method fields. Fields that are not in the definition cannot be sent |
preauth | boolean | No | true: pre-authorization; the provider must support it. See Pre-authorization |
callbackUrl | string | No | http or https, at most 2048 characters. If the provider returns a REDIRECT action, the buyer's browser returns here with the result |
The body contains only the mobileBilling block: another method's block, or card, force3D, installment, installmentQuoteToken, gets 400 VALIDATION_ERROR.
Order fields
| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | Yes | Amount, minor-unit integer; at least 1. It is the basket total: the price of the goods and services the buyer pays for, excluding the installment fee. If you send basketItems, it must equal the sum of the items' price. For card installments the system adds the fee. Example: 10050 |
currency | string (enum) | Yes | ISO 4217 code: TRY, USD, EUR, GBP, RUB, AED, SAR, QAR, KWD, JPY, CHF, CAD, AUD, CNY, AZN. A currency not enabled on your account is rejected |
orderId | string | Yes | Your order number, at most 128 characters. Example: ORD-84722 |
customerId | string | No | Your customer key. It identifies the buyer for wallet payments; when a card is saved with 3D Secure, it is stored under this key. Example: cust_7788 |
description | string | No | Payment description |
locale | string | No | Message language, default tr |
paymentChannel | string | No | Payment channel. Example: WEB |
paymentGroup | string | No | Product group. Example: PRODUCT |
buyer | object | No | Buyer details; see buyer |
shippingAddress | object | No | Shipping address; see Address |
billingAddress | object | No | Billing address; see Address |
basketItems | array | No | Basket items; see basketItems[] |
metadata | object | No | Free key-value object for your own records. Request a pre-authorization with the preauth field, not with metadata |
The amount is sent only in amount; the removed amount fields get 400 payment.amount.field_removed. Sending a field that is not defined here rejects the request with 400 VALIDATION_ERROR.
buyer.gsmNumber is used when mobileBilling.gsmNumber is omitted.
buyer
buyer is optional. If you send it, every field marked "Yes" below must be filled. On a Checkout Form intent (POST /payments/intents, POST /payments/marketplace/intents) this rule does not apply: every field is optional, and the fields you send are checked for type and length.
| Field | Type | Required | Example |
|---|---|---|---|
id | string | No | CUST-1 |
name | string | Yes | Ada |
surname | string | Yes | Yılmaz |
identityNumber | string | Yes | 11111111111 |
email | string (email) | Yes | [email protected] |
gsmNumber | string | Yes | +905000000000 |
city | string | Yes | Istanbul |
country | string | Yes | Turkey |
zipCode | string | Yes | 34732 |
ip | string | Yes | The buyer's IP address |
registrationDate | string | No | 2024-04-21 15:12:09 |
lastLoginDate | string | No | 2026-10-05 12:43:35 |
registrationAddress | string | No | Registered address |
Required-field rule, how it differs from customerId, and error field paths: Buyer.
Address
shippingAddress and billingAddress share the same shape. If you send the object, all five fields are required.
| Field | Type | Example |
|---|---|---|
contactName | string | Ada Yılmaz |
address | string | Altunizade Mah. … |
city | string | Istanbul |
country | string | Turkey |
zipCode | string | 34742 |
How the two addresses differ and error field paths: Addresses.
basketItems[]
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Item code |
name | string | Yes | Item name |
category1 | string | Yes | Main category |
category2 | string | No | Sub-category |
itemType | string (enum) | Yes | PHYSICAL or VIRTUAL |
price | integer | Yes | Item amount, minor-unit integer, at least 0 |
The sum of the items' price must equal amount; otherwise the request is rejected with 400 VALIDATION_ERROR. The installment fee is not added to the items.
Example request
# See the Authentication page for how the Step-Up signature headers are computed.
curl -X POST https://api-gateway.lunixi.com/api/v1/payments/direct/payment \
-H "Authorization: Bearer at_test_5f3e2b1a..." \
-H "Content-Type: application/json" \
-H "X-Key-Id: mk_test_sk_..." \
-H "X-Date: 2026-10-11T09:00:00.000Z" \
-H "X-Nonce: 8d4f2c1e-0a7b-4b3c-9e5d-6f1a2b3c4d5e" \
-H "Digest: SHA-256=..." \
-H "X-Signature: ..." \
-H "Idempotency-Key: ORDER-1002-1" \
-d '{
"paymentMethod": "MOBILE_BILLING",
"orderId": "ORDER-1002",
"amount": 5000,
"currency": "TRY",
"basketItems": [
{ "id": "DIGITAL-1", "name": "Dijital ürün", "category1": "Dijital", "itemType": "VIRTUAL", "price": 5000 }
],
"mobileBilling": { "gsmNumber": "+905321234567" }
}'Response and action
{
"status": "partial",
"code": "CUSTOMER_ACTION_REQUIRED",
"message": "…",
"data": {
"paymentId": "7b0e4c2a-1f3d-4e5b-8a6c-9d0e1f2a3b4c",
"orderId": "ORDER-1002",
"status": "PROCESSING",
"paymentMethod": "MOBILE_BILLING",
"providerCode": "<providerCode>",
"action": {
"id": "act_5c2e9f0a1b3d4e6f7a8b9c0d1e2f3a4b",
"type": "OTP",
"expiresAt": "2026-10-11T09:03:00.000Z",
"otp": { "maskedTarget": "53****67" },
"consent": { "required": true, "url": "https://…" }
}
},
"statusCode": 202,
"requestId": "…"
}<providerCode>is a placeholder.- The code's validity is in
action.expiresAt. consent.urlis the provider's agreement address; show it to the buyer as a link they can open.
Confirm
POST https://api-gateway.lunixi.com/api/v1/payments/{paymentId}/actions/{actionId}/confirm{ "otp": "123456", "consentAccepted": true }| Outcome | Response | What to do |
|---|---|---|
| Payment completed | 200, data.status CAPTURED | Ship the order on payment.captured |
| No agreement consent | 422 payment.action.consent_required | Get the buyer's acceptance and send again with consentAccepted: true. No attempt is used |
| Code missing or malformed | 400 payment.action.otp_required | Send the code as digits only. No attempt is used |
| Wrong code | 422 payment.action.input_rejected | The action is open; remaining attempts in details.attemptsRemaining. There are 3 attempts |
| No attempts left | 400 payment.action.attempts_exhausted | The payment is FAILED; start a new payment |
| Result being verified with the provider | 202 PAYMENT_PENDING_RECONCILIATION | Do not start a new payment; wait for the webhook |
If the code expires, the payment becomes FAILED and payment.failed is sent.
Resending the code
If the buyer did not receive the code, send a new one with POST /payments/{paymentId}/actions/{actionId}/resend. The response carries a new action; use the new action.id for confirm. A code can be resent at most 3 times per payment, with at least 30 seconds between sends; the rules are the same as in Payment — resend.
Line eligibility and limits
- If the line is closed to this method, not eligible, or its billing limit is not enough for the amount, the payment is declined: response
400,data.statusFAILED. Offer the buyer another method. - When more than one provider is connected and you omit
providerCode, eligibility is checked with the providers before the payment starts. If none is eligible the response is422payment.provider.none_eligible; no payment record is opened. - Amount limits and currencies are in the
minAmount,maxAmountandcurrenciesfields of theGET /payments/methodsresponse.
Pre-authorization
Pre-authorization support is shown by the preauth field of the GET /payments/methods response. When it is not supported, a request with preauth: true gets 422 payment.preauth.unsupported_for_method before the payment starts; nothing is added to the bill.
Errors
| Situation | Response |
|---|---|
| Line missing or malformed; field not in the definition | 400 payment.method.block_invalid (details.field: mobileBilling.gsmNumber) |
No providerCode and none of the connected providers can take the payment | 422 payment.provider.none_eligible |
| The selected provider is not connected | 422 payment.provider.not_available |
| Pre-authorization not supported | 422 payment.preauth.unsupported_for_method |
| Line not eligible or limit not enough | 400, payment FAILED |
Codes of the action endpoints: Payment — Error codes.
Webhooks
| Event | When |
|---|---|
payment.captured | The amount was added to the bill. Ship the order on this event |
payment.authorized | The amount was held for a pre-authorization (AUTHORIZED); no funds were captured yet. See Pre-authorization |
payment.failed | The payment failed (decline, expired code or no attempts left) |
The data of the events carries paymentMethod: "MOBILE_BILLING" and the provider's providerCode. See Webhooks.
Next steps
- Listing Methods — connected providers and provider selection
- Payment — common request, response and error codes
- Checkout Form — Payment — the mobile billing flow in the form