# Payments & refunds

Choose native payment instructions or record money the studio actually collected elsewhere. Both models preserve Podyx's admission, pricing, approval and fulfillment rules.

## Native payment capabilities

Accept a complete [native booking](/api/docs#/Studio%20native%20checkout/v2CheckoutBooking), then [read the checkout](/api/docs#/Studio%20native%20checkout/v2GetCheckout). Native instructions can be `no_payment_required`, `cash`, `stripe_elements`, `square_card` or `redirect`. Use the returned public SDK options, connected-account/location, client secret, amount and browser capability for the selected instruction kind.

Keep studio keys and private provider credentials on your backend. Deliver only the public instruction to the authorized customer's browser. Cash and zero/credit-funded paths still obey their native admission/approval policy. Do not create fake provider or payment IDs to make the paths look alike.

For Stripe Elements, ask Podyx's operator for the **Podyx Stripe platform's publishable key**, available from that platform's [Stripe Dashboard API keys](https://docs.stripe.com/keys). Configure this public value in your frontend as `stripePublishableKey`, using the same test/live environment as the checkout. Booking checkout instructions do not return it; a key from an unrelated Stripe account is not a substitute. Initialize Stripe with this configured key and the returned `provider_options.connected_account_id`, mount Elements with `client_secret` and use the provider's [browser confirmation API](https://docs.stripe.com/js/payment_intents/confirm_payment).

For Square, load its [Web Payments SDK](https://developer.squareup.com/docs/web-payments/overview) for the returned environment/application/location, tokenize in the browser and submit `square_token` through your backend to [checkout payment tokens](/api/docs#/Studio%20native%20checkout/v2SubmitCheckoutToken). Never send the studio key to either browser SDK.

[Confirmation](/api/docs#/Studio%20native%20checkout/v2ConfirmCheckout), [retry](/api/docs#/Studio%20native%20checkout/v2RetryCheckout) and [unpaid cancellation](/api/docs#/Studio%20native%20checkout/v2CancelCheckout) are separate commands under the checkout's `/payment/` subresource. They accept the published body (empty `{}` where specified) and each gets its own persisted request UUID. Retry uncertain delivery of one command with that command's original UUID. Read current state after provider work is queued; a browser callback does not prove settlement.

## Studio-collected money

[Create an external checkout](/api/docs#/Studio%20external%20payments/v2CreateExternalCheckout) with the same complete ordinary booking selection. This chooses `provider=external`, `settlement_mode=off_platform`; it does not change the configured native provider or relabel a Stripe/Square/cash payable.

Read the [external checkout](/api/docs#/Studio%20external%20payments/v2GetExternalCheckout) for the real payment ID and authoritative amount/currency. Collect money through the studio's own process, then [report the actual collection](/api/docs#/Studio%20external%20payments/v2ReportExternalPayment). The `collection_id` identifies the real collection independently of the HTTP request UUID. Persist it through retries, key rotation and refunds; a merchant reference cannot replace it.

For an existing payment ID, omitting both `amount` and `currency` reports the entire positive outstanding balance, frozen at acceptance. A covered payable refuses that default. Use explicit actual amounts/currency for partial collection or overcollection. Successful refunds reopen the balance; pending refunds do not. Same collection identity and immutable facts is a no-op; changed facts conflict.

An authoritative zero ordinary booking total needs no collection report. It retains its zero external payable identity and obeys approval/fulfillment policy. A zero-total Tour instead has its separately documented no-Payment policy.

## Read money and admission separately

[External checkout collections](/api/docs#/Studio%20external%20recovery/v2ListExternalRefundCollections) page actual sources; do not count payable face value and its child allocations as separate income. Partial collections accumulate against the canonical payable. Late money does not revive a terminal booking.

An inline report can contain a complete ordinary `booking` selection. If a booking race prevents admission after collection facts are accepted, the durable report can exist without a confirmed booking. [Read the report](/api/docs#/Studio%20external%20payments/v2GetExternalPaymentReport): `underfunded`, `applied`, `action_required` and `requires_reconciliation` have different next actions. Amount/payment can be null where no authoritative amount remains. Use the explicit [resolution operation](/api/docs#/Studio%20external%20payments/v2ResolveExternalPaymentReport) over real facts; do not repeat the original booking attempt or invent a refund.

External payable [failed/void outcomes](/api/docs#/Studio%20external%20recovery/v2ReportExternalPayableOutcome) report actual outcomes with a retained outcome identity. They are not refunds.

## Refund an actual collection

First inspect the [cancellation plan](/api/docs/guides/v2/bookings) and original collection sources. [Request an external refund](/api/docs#/Studio%20external%20recovery/v2RequestExternalRefund) with payment, collection, retained refund ID, amount/currency and the frozen `cancellation_refund_item_id` when resolving a plan item. A pending request reserves capacity; it does not mean money was returned.

The studio executes the refund outside Podyx, then [reports the actual outcome](/api/docs#/Studio%20external%20recovery/v2ReportExternalRefund) as `succeeded`, `failed` or `reversed`. [Read the current refund](/api/docs#/Studio%20external%20recovery/v2GetExternalRefund), payable and plan. Successful/reversed outcomes affect actual claims once. A replacement attempt names `replaces_refund_id` and retains the root intention; a new HTTP UUID is not a new refund intention.

## Permissions, effects and uncertainty

Native collection and external reporting have different permissions and readiness prerequisites, in addition to the relevant client/session authority. Submission can create durable checkout/report/refund identities and asynchronous provider work. Keep payment, staff approval, fulfillment, notification processing and delivery as separate facts.

Follow [same-intent recovery](/api/docs/guides/v2/recovery). An undetermined provider result must retain its frozen provider request identity; another tender is unsafe. Do not infer rollback from an HTTP error or transport failure.

## Example

Report an actual partial collection against the returned external payable:

```sh
curl "https://app.podyx.com/api/v2/external-payment-reports" \
  -H "Authorization: Bearer $PODYX_API_KEY" \
  -H "Idempotency-Key: $REPORT_REQUEST_UUID" \
  -H 'Content-Type: application/json' \
  --data '{"payment_id":"<real-payment-uuid>","collection_id":"<persisted-collection-uuid>","amount":"75.00","currency":"USD"}'
```

The amount is illustrative: report the real collected amount and currency, not this example's price. Keep both identities when recovering uncertain delivery.

A minimal Stripe browser setup uses a public instruction supplied by your backend and the separately configured `stripePublishableKey` described above. Load the official Stripe SDK first; your app supplies the mounted payment form and its return URL:

```js
const options = instruction.provider_options;
const stripe = Stripe(stripePublishableKey, {
  stripeAccount: options.connected_account_id
});
const elements = stripe.elements({ clientSecret: instruction.client_secret });
elements.create('payment').mount('#payment-element');
// On the customer's explicit payment submission:
const result = await stripe.confirmPayment({
  elements,
  confirmParams: { return_url: yourReturnUrl },
  redirect: 'if_required'
});
```

Handle provider action/errors in your UI. Then use your backend's confirmation/current-resource capabilities as required by the returned instruction; do not mark the booking paid from this snippet's result alone. Provider sandbox execution remains a separate integration check.
