# Destination charges

Pay a connected seller with a destination charge: the transfer, the application fee, each side's balance transactions, refunds that reverse them and the seller's payouts.

A destination charge takes a payment on your platform and transfers a share to a connected seller, so you can test what each side's balance records. Onboard a seller, then pay it $100.00 with a $10.00 application fee:

```bash
curl -s "$RYSTIC_URL/v2/core/accounts" -u sk_test_123: -H 'Content-Type: application/json' -d '{"dashboard":"none","identity":{"country":"us","entity_type":"individual","individual":{"given_name":"Jenny","surname":"Rosen","date_of_birth":{"day":1,"month":1,"year":1901},"address":{"line1":"address_full_match","city":"San Francisco","state":"CA","postal_code":"94103","country":"us"},"id_numbers":[{"type":"us_ssn","value":"000000000"}]},"attestations":{"terms_of_service":{"account":{"date":"2026-01-01T00:00:00.000Z","ip":"203.0.113.7"}}}},"defaults":{"currency":"usd","responsibilities":{"fees_collector":"application","losses_collector":"application"},"profile":{"business_url":"https://example.com"}},"configuration":{"merchant":{"capabilities":{"card_payments":{"requested":true}}},"recipient":{"capabilities":{"stripe_balance":{"stripe_transfers":{"requested":true}}}}}}' | jq -c '{id}'
# -> {"id":"acct_0000000000000001"}
curl -s "$RYSTIC_URL/v1/payment_intents" -u sk_test_123: -d amount=10000 -d currency=usd -d 'payment_method_types[]=card' -d payment_method=pm_card_bypassPending -d confirm=true -d 'transfer_data[destination]=acct_0000000000000001' -d application_fee_amount=1000 | jq -c '{status, latest_charge}'
# -> {"status":"succeeded","latest_charge":"ch_000000000000000000000001"}
curl -s "$RYSTIC_URL/v1/charges/ch_000000000000000000000001" -u sk_test_123: | jq -c '{transfer, application_fee, transfer_group}'
# -> {"transfer":"tr_000000000000000000000001","application_fee":"fee_000000000000000000000001","transfer_group":"group_pi_000000000000000000000001"}
curl -s "$RYSTIC_URL/v1/balance_transactions" -u sk_test_123: -H 'Stripe-Account: acct_0000000000000001' | jq -c '[.data[] | {type, amount, fee, net}]'
# -> [{"type":"payment","amount":10000,"fee":1000,"net":9000}]
```

Amounts are in the currency's smallest unit, as on Stripe, so 10000 is $100.00. The bypassPending card puts what the seller gains, its share less the fee, straight into its `available` balance, ready for a payout. [Connect](https://www.rystic.ai/docs/simulators/stripe/features/connect.md) covers creating the seller and acting as it.

### Refund part of a destination charge

Refund half the payment, taking the seller's share of it back and returning that part of the fee:

```bash
curl -s "$RYSTIC_URL/v1/refunds" -u sk_test_123: -d charge=ch_000000000000000000000001 -d amount=5000 -d reverse_transfer=true -d refund_application_fee=true | jq -c '{status, transfer_reversal}'
# -> {"status":"succeeded","transfer_reversal":"trr_000000000000000000000001"}
curl -s "$RYSTIC_URL/v1/balance_transactions" -u sk_test_123: -H 'Stripe-Account: acct_0000000000000001' | jq -c '[.data[] | {type, amount}]'
# -> [{"type":"adjustment","amount":500},{"type":"payment_refund","amount":-5000},{"type":"payment","amount":10000}]
curl -s "$RYSTIC_URL/v1/balance" -u sk_test_123: -H 'Stripe-Account: acct_0000000000000001' | jq -c '{available: .available[0].amount}'
# -> {"available":4500}
```

Each route that creates a refund takes `reverse_transfer` and `refund_application_fee` on a destination charge.

`reverse_transfer=true` takes the refund's proportion of the share back from the account, and the refund's `transfer_reversal` names a `trr_` reversal. The platform lists a `transfer_refund` row. The account lists a `payment_refund` row and a `pyr_` refund of its `py_` payment, whose `amount_refunded` grows.

`refund_application_fee=true` also returns the refund's proportion of the fee, as an `application_fee_refund` row on the platform and an `adjustment` row on the account. Each refund's shares round half up on their own. The refund that leaves the transfer fully reversed returns the rest of the fee, as Stripe's do.

Without `refund_application_fee` the account keeps paying the fee, so its balance can go negative, as Stripe's does. Without `reverse_transfer` the platform alone bears the refund.

**Differs from Stripe:** the simulator writes none of the `reserve_transaction` rows Stripe posts on the platform when a refund leaves the connected account's balance negative.

**Differs from Stripe:** the simulator accepts a refund whose reversal rounds to 0 and a fee refund without `reverse_transfer`. Stripe refuses both, the first with "You cannot reverse a transfer for amount=0."

### Pay out to a seller

Give the seller a bank account, then pay out $40.00 of its balance:

```bash
curl -s "$RYSTIC_URL/v1/accounts/acct_0000000000000001/external_accounts" -u sk_test_123: -d external_account=btok_us_verified | jq -c '{id}'
# -> {"id":"ba_1Sim00000000000000000001"}
curl -s "$RYSTIC_URL/v1/payouts" -u sk_test_123: -H 'Stripe-Account: acct_0000000000000001' -d amount=4000 -d currency=usd | jq -c '{status, destination}'
# -> {"status":"pending","destination":"ba_1Sim00000000000000000001"}
curl -s "$RYSTIC_URL/v1/balance" -u sk_test_123: -H 'Stripe-Account: acct_0000000000000001' | jq -c '{available: .available[0].amount}'
# -> {"available":500}
```

A seller's payout draws on its own balance, and its `destination` is the bank account the external account route returned. [Balance and payouts](https://www.rystic.ai/docs/simulators/stripe/features/balance.md) covers payouts.

### The charge and the transfer

A PaymentIntent with `transfer_data[destination]` is a destination charge. It's paid when created with `confirm=true`, when confirmed or when captured, `POST /v1/charges/{charge}/capture` of its charge included. It credits the platform the charge's net as any card payment does. Then it transfers a share to the connected account: `transfer_data[amount]` when set, capped at the captured amount, or else the whole amount.

The account gains the share less any `application_fee_amount`. So with no application fee on a whole-amount transfer, the platform loses the card fee. `transfer_data[amount]` beside `application_fee_amount` returns 400 `parameters_exclusive`, as on Stripe. `POST /v1/charges` takes no Connect parameters.

The charge names these at once: `destination`, `transfer_group` (`group_` plus the PaymentIntent's id), its `transfer` and, with an application fee, its `application_fee`. No route reads the transfer or the fee.

Each side lists its own balance transactions:

| Account | Balance transactions |
|---|---|
| the platform | the `charge` row; a `transfer` row for minus the share, its source the transfer `transfer.created` announces; with a fee, an `application_fee` row for plus the fee |
| the connected account | a `payment` row: the share as its amount, the application fee as its fee (one `application_fee` line in `fee_details`) and the credit as its net |

The account's `payment` row has the `py_` payment as its source. The account reads the payment with `GET /v1/charges/{id}` and lists it with `GET /v1/charges`. The payment sends `payment.created` as the account's, with `request` `{id: null, idempotency_key: null}`. That event lists among the account's events and reaches the platform's Connect endpoints, never the platform's own list.

**Differs from Stripe:** the simulator names the transfer and the fee, moves both balances and lists the event at the charge. Stripe names the transfer and the fee about 6 seconds later, and does the rest seconds later.

---

Source: https://www.rystic.ai/docs/simulators/stripe/features/destination-charges · Markdown: https://www.rystic.ai/docs/simulators/stripe/features/destination-charges.md
