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

# Split one payment across two cards

> Build the object that splits one payment across two cards, start it, create the payment and read the result on Web, iOS, Android and React Native.

A split payment lets the shopper fund one order with two cards. Each card pays its own part of the total, you create one payment, and you receive one result.

The method is `start` on every platform. For a split payment it receives an array of payment methods instead of one. The SDK creates one composite token, hands it to [`createPayment`](https://docs.y.uno/docs/yuno-sdk/controllers/create-payment), runs the 3DS of each card and reports the outcome in [`onStatus`](https://docs.y.uno/docs/yuno-sdk/controllers/on-status).

To divide a payment among marketplace recipients, see [Split payments marketplace](https://docs.y.uno/docs/payment-features/split-payments-marketplace.md) instead.

## Availability

| Platform | Minimum version | Entry point |
| - | - | - |
| Web | `2.6.0` | `transaction.start([...])` |
| iOS | `3.1.0` | `transaction.start(paymentsSelected:)` |
| Android | `3.1.0` | `transaction.start(activity, paymentsSelected)` |
| React Native | `2.1.0` | `transaction.start([...])` |

## Before you start

* **Ask Yuno to enable it.** Split payment is enabled per account. Without it, the token request fails with `SPLIT_NOT_ENABLED`.
* **Create the checkout session with the full amount.** Use the total of the order and the default `SDK_CHECKOUT` workflow.
* **Override `createPayment`.** You create the payment from your backend. See [Create the payment](#create-the-payment).
* **Collect the card data in your own UI.** Pass it in the object. The flow does not mount an SDK form or a payment methods list for the cards.
* **Pass each amount with the decimals of its currency.** The SDK sends `value` as you give it, and the backend rejects extra decimals with `AMOUNT_PRECISION`.

## Build the object

You build the same payment method object a one-card payment uses, once per card, and pass the two of them to `start` as an array. Each one adds the `amount` that card pays.

> [tab] Web
>
> ```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> start([{ paymentMethod }])
> ```
>
> | Field | Type | Description |
> | - | - | - |
> | `paymentMethod.type` | String | `"CARD"`. |
> | `paymentMethod.data.amount.currency` | String | Required. Currency of this card's part. |
> | `paymentMethod.data.amount.value` | Number | Required. Amount this card pays, greater than 0, with the decimals of the currency. |
> | `paymentMethod.data.card` | Object | The card data. Send it or `vaultedToken`. |
> | `paymentMethod.vaultedToken` | String | The vaulted token of an enrolled card. Send it or `data.card`. |
>
> ```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> await transaction.start([
>   {
>     paymentMethod: {
>       type: 'CARD',
>       data: {
>         amount: { currency: 'BRL', value: 200 },
>         card: {
>           number: '4111111111111111',
>           securityCode: '123',
>           expirationMonth: 11,
>           expirationYear: 29,
>           holderName: 'John Doe',
>         },
>       },
>     },
>   },
>   {
>     paymentMethod: {
>       type: 'CARD',
>       vaultedToken: 'aad8578e-ac2f-40a0-8065-25b5957f6555',
>       data: {
>         amount: { currency: 'BRL', value: 100 },
>       },
>     },
>   },
> ])
> ```

> [tab] iOS
>
> ```swift theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> start(paymentsSelected: [SdkPayments.PaymentMethodSelected])
> ```
>
> | Field | Type | Description |
> | - | - | - |
> | `paymentMethodType` | `String` | `"CARD"`. |
> | `transactionData.amount` | `AmountTransactionData(currency: String, value: Double)` | Required. Currency and amount this card pays, with a value greater than 0 and the decimals of the currency. |
> | `transactionData.card` | `CardTransactionData` | The card data. Send it or `vaultedToken`. |
> | `vaultedToken` | `String?` | The vaulted token of an enrolled card. Send it or `transactionData.card`. |
>
> ```swift theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> transaction.start(paymentsSelected: [
>     SdkPayments.PaymentMethodSelected(
>         paymentMethodType: "CARD",
>         transactionData: SdkPayments.TransactionData(
>             card: SdkPayments.CardTransactionData(
>                 number: "4111111111111111",
>                 securityCode: "123",
>                 expirationMonth: 11,
>                 expirationYear: 29,
>                 holderName: "John Doe"
>             ),
>             amount: SdkPayments.AmountTransactionData(currency: "BRL", value: 200)
>         )
>     ),
>     SdkPayments.PaymentMethodSelected(
>         paymentMethodType: "CARD",
>         vaultedToken: "aad8578e-ac2f-40a0-8065-25b5957f6555",
>         transactionData: SdkPayments.TransactionData(
>             amount: SdkPayments.AmountTransactionData(currency: "BRL", value: 100)
>         )
>     )
> ])
> ```

> [tab] Android
>
> ```kotlin theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> start(activity, paymentsSelected: List<PaymentMethodSelected>)
> ```
>
> | Field | Type | Description |
> | - | - | - |
> | `type` | `String` | `"CARD"`. |
> | `transactionData.amount` | `PaymentAmount(currency: String, value: Double)` | Required. Currency and amount this card pays, with a value greater than 0 and the decimals of the currency. |
> | `transactionData.card` | `CardData` | The card data. Send it or `vaultedToken`. |
> | `vaultedToken` | `String?` | The vaulted token of an enrolled card. Send it or `transactionData.card`. |
>
> ```kotlin theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> transaction.start(
>     activity = this,
>     paymentsSelected = listOf(
>         PaymentMethodSelected(
>             type = "CARD",
>             transactionData = TransactionData(
>                 card = CardData(
>                     number = "4111111111111111",
>                     securityCode = "123",
>                     expirationMonth = 11,
>                     expirationYear = 29,
>                     holderName = "John Doe",
>                 ),
>                 amount = PaymentAmount(currency = "BRL", value = 200.0),
>             ),
>         ),
>         PaymentMethodSelected(
>             type = "CARD",
>             vaultedToken = "aad8578e-ac2f-40a0-8065-25b5957f6555",
>             transactionData = TransactionData(
>                 amount = PaymentAmount(currency = "BRL", value = 100.0),
>             ),
>         ),
>     ),
> )
> ```

> [tab] React Native
>
> ```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> start(selection: PaymentMethodSelected | PaymentMethodSelected[]): Promise<void>
> ```
>
> | Field | Type | Description |
> | - | - | - |
> | `type` | `string` | `'CARD'`. |
> | `transactionData.amount` | `PaymentAmount` (`{ currency: string, value: number }`) | Required. Currency and amount this card pays, with a value greater than 0 and the decimals of the currency. |
> | `transactionData.card` | `CardData` | The card data. Send it or `vaultedToken`. |
> | `vaultedToken` | `string` | The vaulted token of an enrolled card. Send it or `transactionData.card`. |
>
> ```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> await transaction.start([
>   {
>     type: 'CARD',
>     transactionData: {
>       card: {
>         number: '4111111111111111',
>         securityCode: '123',
>         expirationMonth: 11,
>         expirationYear: 29,
>         holderName: 'John Doe',
>       },
>       amount: { currency: 'BRL', value: 200 },
>     },
>   },
>   {
>     type: 'CARD',
>     vaultedToken: 'aad8578e-ac2f-40a0-8065-25b5957f6555',
>     transactionData: {
>       amount: { currency: 'BRL', value: 100 },
>     },
>   },
> ])
> ```

### Rules for the items

* **Number of items:** two, one per card.
* **Order:** the first item is charged and authenticated first, then the second.
* **`amount`:** required on every item, with `currency` and `value`.
* **Card data:** every item carries a card or a vaulted token. The backend rejects an item with neither or with both.
* **Same card twice:** not allowed. The SDK compares the card numbers and the vaulted tokens.
* **Currency:** the same on every item, and the currency of the checkout session. The SDK checks the items against each other and the backend checks them against the session.
* **Sum of the amounts:** must equal the amount of the checkout session. The backend validates it.

## Complete example

Build the object, start the payment, create it in `createPayment` and receive the result in `onStatus`. This example splits a checkout session of 300 BRL into 200 BRL on a new card and 100 BRL on a vaulted card.

> [tab] Web
>
> ```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> const sdkPayments = await SdkPayments.initialize('<PUBLIC_API_KEY>')
>
> const transaction = sdkPayments.transaction({
>   checkoutSession: '<CHECKOUT_SESSION>',
>   countryCode: 'BR',
>   createPayment: async (token) => {
>     // Your backend calls POST /v2/payments with the composite token
>     await myBackend.createSplitPayment({
>       token: token.token,
>       checkout_session: token.checkout_session,
>     })
>   },
>   onStatus: (result) => {
>     // One result for the whole payment
>     console.log(result.status, result.substatus)
>   },
> })
>
> try {
>   await transaction.start([
>     {
>       paymentMethod: {
>         type: 'CARD',
>         data: {
>           amount: { currency: 'BRL', value: 200 },
>           card: {
>             number: '4111111111111111',
>             securityCode: '123',
>             expirationMonth: 11,
>             expirationYear: 29,
>             holderName: 'John Doe',
>           },
>         },
>       },
>     },
>     {
>       paymentMethod: {
>         type: 'CARD',
>         vaultedToken: 'aad8578e-ac2f-40a0-8065-25b5957f6555',
>         data: {
>           amount: { currency: 'BRL', value: 100 },
>         },
>       },
>     },
>   ])
> } catch (error) {
>   // An invalid array rejects start() before any request
>   if (error.code === 'INVALID_SPLIT_PAYMENT') {
>     console.error(error.message)
>   }
> }
> ```

> [tab] iOS
>
> ```swift theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> import SdkPayments
>
> final class Checkout: SdkPayments.TransactionController {
>
>     private var transaction: SdkPayments.Transaction?
>
>     func createPayment(token: [String: Any]) async throws {
>         // Your backend calls POST /v2/payments with the composite token
>         try await createSplitPaymentOnBackend(token: token["token"] as? String ?? "")
>     }
>
>     func onStatus(status: SdkPayments.TransactionStatus) {
>         // One result for the whole payment
>         print(status.status, status.substatus ?? "", status.message?.code ?? "")
>     }
>
>     func pay() {
>         let config = SdkPayments.TransactionConfig(
>             checkoutSession: "<CHECKOUT_SESSION>",
>             countryCode: "BR",
>             controller: self
>         )
>         let transaction = SdkPayments.Transaction(config: config)
>         self.transaction = transaction
>
>         transaction.start(paymentsSelected: [
>             SdkPayments.PaymentMethodSelected(
>                 paymentMethodType: "CARD",
>                 transactionData: SdkPayments.TransactionData(
>                     card: SdkPayments.CardTransactionData(
>                         number: "4111111111111111",
>                         securityCode: "123",
>                         expirationMonth: 11,
>                         expirationYear: 29,
>                         holderName: "John Doe"
>                     ),
>                     amount: SdkPayments.AmountTransactionData(currency: "BRL", value: 200)
>                 )
>             ),
>             SdkPayments.PaymentMethodSelected(
>                 paymentMethodType: "CARD",
>                 vaultedToken: "aad8578e-ac2f-40a0-8065-25b5957f6555",
>                 transactionData: SdkPayments.TransactionData(
>                     amount: SdkPayments.AmountTransactionData(currency: "BRL", value: 100)
>                 )
>             )
>         ])
>     }
> }
> ```
>
> Call `SdkPayments.initialize(apiKey:)` once at app startup. The SDK does not retain the controller: keep it alive for the whole transaction.

> [tab] Android
>
> ```kotlin theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> import android.util.Log
> import androidx.activity.ComponentActivity
> import com.yuno.payments.sdk.api.Transaction
> import com.yuno.payments.sdk.api.TransactionConfig
> import com.yuno.payments.sdk.api.TransactionController
> import com.yuno.payments.sdk.api.models.CardData
> import com.yuno.payments.sdk.api.models.PaymentAmount
> import com.yuno.payments.sdk.api.models.PaymentMethodSelected
> import com.yuno.payments.sdk.api.models.TransactionData
> import com.yuno.payments.sdk.api.models.TransactionStatus
>
> class CheckoutActivity : ComponentActivity(), TransactionController {
>
>     override suspend fun createPayment(token: Map<String, Any>) {
>         // Your backend calls POST /v2/payments with the composite token
>         createSplitPaymentOnBackend(token["token"] as? String ?: "")
>     }
>
>     override fun onStatus(status: TransactionStatus) {
>         // One result for the whole payment
>         Log.d("Checkout", "${status.status} ${status.substatus} ${status.message?.code}")
>     }
>
>     fun pay() {
>         val config = TransactionConfig(
>             checkoutSession = "<CHECKOUT_SESSION>",
>             countryCode = "BR",
>             controller = this,
>         )
>
>         Transaction(config = config).start(
>             activity = this,
>             paymentsSelected = listOf(
>                 PaymentMethodSelected(
>                     type = "CARD",
>                     transactionData = TransactionData(
>                         card = CardData(
>                             number = "4111111111111111",
>                             securityCode = "123",
>                             expirationMonth = 11,
>                             expirationYear = 29,
>                             holderName = "John Doe",
>                         ),
>                         amount = PaymentAmount(currency = "BRL", value = 200.0),
>                     ),
>                 ),
>                 PaymentMethodSelected(
>                     type = "CARD",
>                     vaultedToken = "aad8578e-ac2f-40a0-8065-25b5957f6555",
>                     transactionData = TransactionData(
>                         amount = PaymentAmount(currency = "BRL", value = 100.0),
>                     ),
>                 ),
>             ),
>         )
>     }
> }
> ```
>
> Call `SdkPayments.initialize(applicationContext, apiKey = "<PUBLIC_API_KEY>")` once at app startup.

> [tab] React Native
>
> ```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
> import { SdkPayments, Transaction } from '@yuno-payments/yuno-sdk-react-native'
>
> await SdkPayments.initialize('<PUBLIC_API_KEY>')
>
> const transaction = new Transaction({
>   checkoutSession: '<CHECKOUT_SESSION>',
>   countryCode: 'BR',
>   controller: {
>     createPayment: async (token) => {
>       // Your backend calls POST /v2/payments with the composite token
>       await myBackend.createSplitPayment({
>         token: token.token,
>         checkout_session: token.checkout_session,
>       })
>     },
>     onStatus: (status) => {
>       // One result for the whole payment
>       console.log(status.status, status.substatus, status.message?.code)
>     },
>   },
> })
>
> await transaction.start([
>   {
>     type: 'CARD',
>     transactionData: {
>       card: {
>         number: '4111111111111111',
>         securityCode: '123',
>         expirationMonth: 11,
>         expirationYear: 29,
>         holderName: 'John Doe',
>       },
>       amount: { currency: 'BRL', value: 200 },
>     },
>   },
>   {
>     type: 'CARD',
>     vaultedToken: 'aad8578e-ac2f-40a0-8065-25b5957f6555',
>     transactionData: {
>       amount: { currency: 'BRL', value: 100 },
>     },
>   },
> ])
> ```
>
> Call `SdkPayments.initialize` once at app startup. React Native passes the array to the native SDK of each platform, so validations and results are the same as on iOS and Android.

## Create the payment

The token you receive in `createPayment` is one composite token for both cards. It carries `token`, `checkout_session` and `expires_at`. Read it from the `token` key. From your backend, send it to `POST /v2/payments` with `workflow: "SDK_CHECKOUT"`, as `payment_method.token`.

> [!WARNING]
>
> `POST /v1/payments` rejects a composite token. Use `POST /v2/payments`.

The request, its fields and the response are in [Pay with two cards](https://docs.y.uno/docs/payment-features/pay-with-two-cards.md#create-the-payment), under the `SDK_CHECKOUT` example, and in the [Create Payment (two cards)](https://docs.y.uno/reference/payments/create-payment-v2.md) reference.

## Receive the result

You receive the result in `onStatus`, at payment level. There is no callback per card.

The SDK runs card 1 and then card 2. If card 1 fails, card 2 does not start and `onStatus` reports the status of the payment.

If card 2 fails after card 1 was authorized, what happens to card 1 is handled on the backend. The payment response on your server shows the final state of each card: see [Pay with two cards](https://docs.y.uno/docs/payment-features/pay-with-two-cards.md).

On iOS, also from React Native, `onStatus` can report a non-final `PENDING` first, as for any payment. See [`onStatus`](https://docs.y.uno/docs/yuno-sdk/controllers/on-status), and confirm the outcome on your server before you fulfill the order.

## Errors

### SDK validations

The SDK checks the shape of the array before it requests the token, so an invalid array sends no request. The code is `INVALID_SPLIT_PAYMENT` and the message is in English. The message is for your developers: do not show it to the shopper.

| Platform | How you receive it |
| - | - |
| Web | `start()` rejects with an error that carries `code` and `message`. `onStatus` is not called. |
| iOS, Android, React Native | `onStatus` receives `status: "INTERNAL_ERROR"`, with `INVALID_SPLIT_PAYMENT` in `message.code` and the text in `message.reason`. |

The SDK checks, in this order, that:

* The array has two items.
* Every item has an `amount` with a `value` greater than 0 and a `currency`.
* Every item has the same currency as the first one.
* No card or vaulted token appears twice.

The message names the item that failed.

Everything else, such as the payment method type, an item with neither a card nor a vaulted token, the sum of the amounts and the currency of the session, is validated by the backend with the codes in [Backend errors](#backend-errors).

### Where the array is not available

The array is for payment transactions. On an enrollment transaction (`customerSession`):

| Platform | How you receive it |
| - | - |
| Web | `start()` rejects with ``An array of `paymentMethod` is not available for enrollment transactions.`` |
| iOS, Android, React Native | `onStatus` receives `INTERNAL_ERROR` with `message.code` `INVALID_SPLIT_PAYMENT` and `message.reason` `paymentsSelected is not available for enrollment`. |

### Backend errors

The backend validates the composite token request. When it rejects the request, `onStatus` receives `status: "INTERNAL_ERROR"` with `message.source` `backend` and no token reaches `createPayment`.

| Code | Meaning |
| - | - |
| `SPLIT_NOT_ENABLED` | Split payment is not enabled for the account. Ask Yuno to enable it. |
| `METHOD_COUNT_OUT_OF_RANGE` | The number of items is outside what the account allows. |
| `METHOD_TYPE_NOT_ALLOWED` | The payment method type is not allowed for split payment. |
| `AMOUNT_SUM_MISMATCH` | The sum of the amounts differs from the checkout session amount. |
| `CURRENCY_MISMATCH` | A currency differs from the checkout session currency. |
| `AMOUNT_NOT_POSITIVE` | An amount is not greater than 0. |
| `AMOUNT_PRECISION` | An amount has more decimals than the currency allows. |
| `DUPLICATE_INSTRUMENT` | The same card or vaulted token appears in two items. |
| `CARD_TOKEN_INVALID` | An item has neither a card nor a vaulted token, has both, or uses a vaulted token that does not belong to the customer. |
| `INSTALLMENT_PLAN_UNAVAILABLE` | The installment plan of an item is not available. |
| `INSTALLMENT_PLAN_LOOKUP_FAILED` | Yuno could not look up the installment plan. |
| `SEQUENCE_INVALID`, `TOTAL_MISMATCH`, `CAPTURE_MODE_MISMATCH`, `INVALID_REQUEST` | The request does not meet the token contract. Check the backend message in `message.reason` or `message.raw`. |

## Related

* [`createPayment`](https://docs.y.uno/docs/yuno-sdk/controllers/create-payment). The controller that receives the composite token.
* [`onStatus`](https://docs.y.uno/docs/yuno-sdk/controllers/on-status). The controller that receives the result.
* [`PaymentMethodSelected`](https://docs.y.uno/docs/yuno-sdk/reference/objects-you-pass#payment-method-selected) and [`TransactionData`](https://docs.y.uno/docs/yuno-sdk/reference/objects-you-pass#transaction-data). The objects each item is built from.
* [Pay with two cards](https://docs.y.uno/docs/payment-features/pay-with-two-cards.md). The payment response, statuses and operations on each card.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.