{"openapi":"3.1.0","info":{"title":"NxtPay Payments API","version":"1.0.0","summary":"Accept payments from your software via NxtPay — create a hosted checkout, poll order status, and issue refunds.","description":"The NxtPay Payments API lets your software (WordPress/WooCommerce, a custom storefront, a SaaS billing flow, etc.) take payments through NxtPay. You create a payment link, redirect the customer to the returned hosted checkout URL, then confirm the result by polling the order or by receiving a signed webhook. Authentication is a single API key (`npk_…`). Amounts are always integer minor units (cents). See /llms.txt and /llms-full.txt for an AI-readable version.","contact":{"name":"NxtPay","url":"https://payments.nxtpay.cc/docs"}},"servers":[{"url":"https://payments.nxtpay.cc","description":"Production base URL"}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"tags":[{"name":"Payments","description":"Create checkouts and look up orders."},{"name":"Subscriptions","description":"Create and cancel recurring subscriptions."},{"name":"Refunds","description":"Refund a captured payment."},{"name":"Status","description":"Routing availability."},{"name":"Integration","description":"Webhook endpoint self-registration and plugin heartbeat."}],"paths":{"/v1/payment-links":{"post":{"tags":["Payments"],"summary":"Create a payment link","description":"Creates an order and a hosted checkout session. Redirect the customer to `paymentUrl`. Store `orderId`, `whitesiteId`, and `statusToken` so you can poll the result. `environment: \"sandbox\"` routes to a test processor connection (use Stripe test cards). Send `Idempotency-Key` to make retries safe: a 502 `payment_unconfirmed` means the charge MAY exist — retry with the SAME key (never a fresh one) and the original link is replayed instead of charging again.","operationId":"createPaymentLink","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Retries with the same key replay the original payment link instead of creating a second charge."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkRequest"}}}},"responses":{"200":{"description":"Payment link created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkResponse"}}}},"400":{"description":"Validation error, or the checkout was cleanly rejected (`payment_link_rejected`, with `upstreamStatus`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing/invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Terminal (`checkout_unrecoverable_use_new_key`) — this Idempotency-Key is permanently bound to a removed payment route; start over with a NEW key. Retrying the same key can never succeed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited — see Retry-After header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Unconfirmed (`payment_unconfirmed` — retry with the SAME Idempotency-Key) or all routing attempts failed (`all_whitesites_failed`); both include `correlationId`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No payment processor available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/subscriptions":{"post":{"tags":["Subscriptions"],"summary":"Create a subscription","description":"Opens a recurring subscription checkout. Same flow as `POST /v1/payment-links`: redirect the customer to `paymentUrl`. On payment, NxtPay vaults their card and bills `amountCents` every `intervalCount` × `interval` (\"day\" | \"week\" | \"month\" | \"year\", default monthly; the period is capped at one year). Optional `signupFeeCents` adds a one-time fee to the first invoice and `trialDays` delays the first recurring charge. A subscription is pinned to the processor that opened it, so store the returned `subscriptionHandle` — it is the only thing `POST /v1/subscriptions/cancel` and `GET /v1/subscriptions/{subscriptionHandle}` need. Renewals arrive as `subscription.renewed` webhooks. Send `Idempotency-Key` to make retries safe. 503 `no_whitesite_available` means no recurring-capable route is currently available.","operationId":"createSubscription","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Retries with the same key replay the original subscription instead of creating a second one."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionRequest"}}}},"responses":{"200":{"description":"Subscription checkout created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"400":{"description":"Validation error (`invalid_interval`, `invalid_interval_count`, `invalid_signup_fee`, `invalid_trial_days`, `immediate_charge_exceeds_max`, …) or cleanly rejected (`subscription_rejected`, with `upstreamStatus`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing/invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Terminal (`checkout_unrecoverable_use_new_key`) — this Idempotency-Key is permanently bound to a removed payment route; start over with a NEW key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited — see Retry-After header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Unconfirmed (`subscription_unconfirmed` — retry with the SAME Idempotency-Key) or all routing attempts failed (`all_whitesites_failed`); both include `correlationId`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No recurring-capable processor available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/subscriptions/cancel":{"post":{"tags":["Subscriptions"],"summary":"Cancel a subscription","description":"Cancels a subscription so no further cycles bill. Cancellation is IMMEDIATE — billing stops the moment the cancel succeeds; there is no cancel-at-period-end option. Already-settled charges are not refunded. Pass the `subscriptionHandle` from the create response. Idempotent — canceling an already-canceled subscription returns `ok: true` with `alreadyCanceled: true` and the ORIGINAL `canceledAt`.","operationId":"cancelSubscription","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelSubscriptionRequest"}}}},"responses":{"200":{"description":"Canceled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelSubscriptionResponse"}}}},"400":{"description":"Invalid handle / not a subscription","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found / not owned by this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Subscription not active yet (customer has not paid)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (20/min per IP) — see Retry-After header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Transient upstream failure (`subscription_cancel_failed`) — safe to retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/subscriptions/{subscriptionHandle}":{"get":{"tags":["Subscriptions"],"summary":"Get a subscription","description":"Returns the current state of a subscription by its `subscriptionHandle` — the pull-based fallback when a `subscription.canceled` webhook is missed. `status` is \"pending\" (the customer has not completed the first checkout), \"active\" (billing), or \"canceled\" (terminated). `nextBillingEstimate` is a computed ESTIMATE (last renewal + interval), not a provider-authoritative billing date.","operationId":"getSubscription","parameters":[{"name":"subscriptionHandle","in":"path","required":true,"schema":{"type":"string"},"description":"The opaque handle from the create response."}],"responses":{"200":{"description":"Subscription state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionDetail"}}}},"400":{"description":"Invalid handle (`invalid_subscription_handle`) or the id is not a subscription (`not_a_subscription`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing/invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found / not owned by this key (`subscription_not_found`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60/min per IP) — see Retry-After header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Transient upstream failure (`subscription_lookup_failed`) — safe to retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders/{id}":{"get":{"tags":["Payments"],"summary":"Get order status","description":"Polls an order. Pass the `statusToken` and `whitesiteId` returned by `POST /v1/payment-links`.","operationId":"getOrder","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The `orderId` from the create response."},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"The `statusToken` from the create response."},{"name":"whitesiteId","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"The `routedVia.whitesiteId` from the create response."}],"responses":{"200":{"description":"Order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Missing token/whitesiteId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found / not owned by this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (60/min per key, 30/min per IP) — see Retry-After header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Transient upstream failure (`upstream_error`) — retry the poll","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/refunds":{"post":{"tags":["Refunds"],"summary":"Refund a payment","description":"Refunds a succeeded payment, fully or partially. Idempotent per (paymentId, amountCents).","operationId":"createRefund","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundRequest"}}}},"responses":{"201":{"description":"Refund created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"}}}},"400":{"description":"Validation error / exceeds remaining","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Payment/whitesite not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (10/min per key, 5/min per IP) — see Retry-After header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Transient upstream failure (`upstream_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/routing-status":{"get":{"tags":["Status"],"summary":"Check routing availability","description":"Returns whether NxtPay can currently route a payment for this key. Use it to show/hide the NxtPay option at checkout.","operationId":"getRoutingStatus","responses":{"200":{"description":"Routing status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingStatus"}}}}}}},"/v1/webhook-endpoint":{"post":{"tags":["Integration"],"summary":"Register your webhook URL","description":"Self-registers (or re-points) the HTTPS endpoint NxtPay delivers signed webhooks to, across every processor in your routing pool. Mints ONE fresh `whsec_…` signing secret and returns it — **store the `secret`**: it is only shown in this response and it verifies every `X-Nxtpay-Signature`. Idempotent, but each call rotates the secret, so always overwrite your stored value. The WooCommerce plugin calls this on every settings save.","operationId":"registerWebhookEndpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointRequest"}}}},"responses":{"200":{"description":"Registered — response carries the `whsec_…` signing secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointResponse"}}}},"400":{"description":"Invalid URL (`invalid_url`) or not HTTPS (`url_must_be_https`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing/invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"No active provider to register against (`no_active_providers`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Registration failed everywhere (`all_providers_failed`) or transient (`registration_unavailable`) — retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/heartbeat":{"post":{"tags":["Integration"],"summary":"Plugin liveness ping","description":"Best-effort periodic check-in (the WooCommerce plugin sends one on a schedule). All body fields are optional metadata. Always answers 200; `routingAvailable` says whether any processor is currently active.","operationId":"postHeartbeat","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeartbeatRequest"}}}},"responses":{"200":{"description":"Acknowledged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeartbeatResponse"}}}},"401":{"description":"Missing/invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"order.paid":{"post":{"summary":"Sent when an order is fully paid","description":"NxtPay POSTs a signed event to the endpoint you configure in your dashboard. Verify the `X-Nxtpay-Signature` header (HMAC-SHA256 of `\"{t}.{rawBody}\"` with your `whsec_…` secret) before trusting it.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledge with any 2xx."}}}},"subscription.renewed":{"post":{"summary":"Sent when a recurring cycle bills successfully","description":"`data`: `{ subscriptionId, orderId, orderNo, amountCents, currency, paidAt, partnerReference? }`. `orderId`/`orderNo` are a NEW order booked for the renewal cycle; `partnerReference` echoes the one from the original create call so you can link the renewal back to your subscription. An `order.paid` for the renewal order follows. Verify the signature as for every event.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledge with any 2xx."}}}},"subscription.payment_failed":{"post":{"summary":"Sent when a renewal charge is declined","description":"`data`: `{ subscriptionId, orderId, failureMessage, partnerReference? }`. `orderId` is the original subscription order. The processor keeps retrying the charge on its own schedule.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledge with any 2xx."}}}},"subscription.canceled":{"post":{"summary":"Sent when a subscription is canceled","description":"`data`: `{ subscriptionId, orderId, canceledAt, reason?, partnerReference? }`. No further cycles will bill; already-settled charges are not refunded. `canceledAt` is ISO-8601 (provider-authoritative when available). If the event is missed, `GET /v1/subscriptions/{subscriptionHandle}` shows `status: \"canceled\"`.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledge with any 2xx."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key as a bearer token: `Authorization: Bearer npk_…`"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key in the `X-API-Key` header."}},"schemas":{"Address":{"type":"object","properties":{"street1":{"type":"string"},"street2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"postalCode":{"type":"string"},"country":{"type":"string","description":"ISO-3166 alpha-2"}}},"Customer":{"type":"object","required":["email"],"description":"The buyer. Whatever identity fields you supply (email, name, phone, billing/shipping address) are pre-filled on the hosted checkout so the customer does not re-enter them. Card number/CVC are always entered on the secure checkout page — NxtPay never handles card data.","properties":{"email":{"type":"string","format":"email"},"firstName":{"type":"string"},"lastName":{"type":"string"},"fullName":{"type":"string"},"phone":{"type":"string"},"shippingAddress":{"$ref":"#/components/schemas/Address"},"billingAddress":{"$ref":"#/components/schemas/Address"}}},"PaymentLinkRequest":{"type":"object","required":["amountCents","successUrl","cancelUrl","customer"],"properties":{"amountCents":{"type":"integer","minimum":1,"maximum":10000000,"description":"Amount in minor units (e.g. 2500 = $25.00). Max 10,000,000 (100,000.00)."},"currency":{"type":"string","enum":["USD"],"default":"USD","description":"Currency code. Only USD is supported — non-USD requests are rejected with 400 unsupported_currency."},"successUrl":{"type":"string","format":"uri","description":"Where the customer returns after a successful payment."},"cancelUrl":{"type":"string","format":"uri","description":"Where the customer returns if they cancel."},"customer":{"$ref":"#/components/schemas/Customer"},"description":{"type":"string","maxLength":255,"default":"Payment"},"partnerReference":{"type":"string","description":"Your own order/reference id. Echoed back for reconciliation — does NOT deduplicate; use the Idempotency-Key header for that."},"referringUrl":{"type":"string","format":"uri"},"metadata":{"type":"object","additionalProperties":true,"description":"Up to 30 keys; values string (≤500 chars)/number/boolean/null. Never sent to the processor."},"environment":{"type":"string","enum":["sandbox","production"],"default":"production"}}},"PaymentLinkResponse":{"type":"object","properties":{"orderId":{"type":"string","format":"uuid"},"orderNo":{"type":"string","example":"ORD-202606-DW9PGM"},"paymentId":{"type":"string","format":"uuid"},"paymentUrl":{"type":"string","format":"uri","description":"Hosted checkout URL — redirect the customer here."},"expiresAt":{"type":["string","null"],"format":"date-time","description":"Null when the provider link has no fixed server-side expiry."},"provider":{"type":"string","example":"stripe"},"statusToken":{"type":"string","description":"Opaque token for GET /v1/orders/{id} (valid 30 days)."},"routedVia":{"type":"object","properties":{"whitesiteId":{"type":"string","format":"uuid"},"whitesiteName":{"type":"string"},"routeId":{"type":"string","format":"uuid"},"attemptNumber":{"type":"integer"},"capOverride":{"type":"boolean"}}}}},"SubscriptionRequest":{"type":"object","required":["amountCents","successUrl","cancelUrl","customer"],"properties":{"amountCents":{"type":"integer","minimum":1,"maximum":10000000,"description":"Amount billed each cycle, in minor units (e.g. 2500 = $25.00/period)."},"interval":{"type":"string","enum":["day","week","month","year"],"default":"month","description":"Billing cadence."},"intervalCount":{"type":"integer","minimum":1,"default":1,"description":"Intervals between charges (e.g. 3 with interval \"month\" = quarterly). The billing period is capped at one year: day ≤ 365, week ≤ 52, month ≤ 12, year = 1."},"signupFeeCents":{"type":"integer","minimum":0,"maximum":10000000,"default":0,"description":"One-time fee added to the first invoice, in cents. amountCents stays the recurring price."},"trialDays":{"type":"integer","minimum":0,"maximum":730,"default":0,"description":"Free-trial days before the first recurring charge. The immediate charge — (trial ? 0 : amountCents) + signupFeeCents — must be ≤ 10000000."},"currency":{"type":"string","enum":["USD"],"default":"USD","description":"Currency code. Only USD is supported — non-USD requests are rejected with 400 unsupported_currency."},"successUrl":{"type":"string","format":"uri"},"cancelUrl":{"type":"string","format":"uri"},"customer":{"$ref":"#/components/schemas/Customer"},"description":{"type":"string","maxLength":255},"partnerReference":{"type":"string","description":"Your own reference id. Echoed back; never sent to the processor."},"referringUrl":{"type":"string","format":"uri"},"metadata":{"type":"object","additionalProperties":true,"description":"Up to 30 keys; values string (≤500 chars)/number/boolean/null. Never sent to the processor."},"environment":{"type":"string","enum":["sandbox","production"],"default":"production"}}},"SubscriptionResponse":{"type":"object","properties":{"orderId":{"type":"string","format":"uuid"},"orderNo":{"type":"string"},"paymentId":{"type":"string","format":"uuid"},"paymentUrl":{"type":"string","format":"uri","description":"Hosted subscription checkout — redirect the customer here."},"expiresAt":{"type":["string","null"],"format":"date-time","description":"Null when the provider link has no fixed server-side expiry."},"provider":{"type":"string","example":"stripe"},"statusToken":{"type":"string"},"mode":{"type":"string","enum":["subscription"]},"interval":{"type":"string","enum":["day","week","month","year"]},"intervalCount":{"type":"integer","description":"Echoed when greater than 1."},"subscriptionHandle":{"type":"string","description":"Opaque handle — pass to POST /v1/subscriptions/cancel. Store it."},"routedVia":{"type":"object","properties":{"whitesiteId":{"type":"string","format":"uuid"},"whitesiteName":{"type":"string"},"routeId":{"type":"string","format":"uuid"},"attemptNumber":{"type":"integer"},"capOverride":{"type":"boolean"}}}}},"CancelSubscriptionRequest":{"type":"object","required":["subscriptionHandle"],"properties":{"subscriptionHandle":{"type":"string","description":"The handle from the create response."}}},"CancelSubscriptionResponse":{"type":"object","properties":{"ok":{"type":"boolean"},"subscriptionId":{"type":"string"},"alreadyCanceled":{"type":"boolean","description":"True on an idempotent replay — the response carries the ORIGINAL canceledAt."},"canceledAt":{"type":"string","format":"date-time","description":"When the subscription was canceled (ISO-8601). Cancellation is immediate — billing stops at this moment."}}},"SubscriptionDetail":{"type":"object","properties":{"orderId":{"type":"string","format":"uuid","description":"The anchor order the subscription was created on."},"orderNo":{"type":"string"},"subscriptionId":{"type":["string","null"],"description":"Processor subscription id — null while status is \"pending\"."},"status":{"type":"string","enum":["pending","active","canceled"],"description":"\"pending\" = the customer has not completed the first checkout; \"active\" = billing; \"canceled\" = terminated."},"canceledAt":{"type":["string","null"],"format":"date-time","description":"Null unless canceled."},"cancelReason":{"type":["string","null"],"description":"Best-effort reason when canceled (e.g. cancellation_requested, payment_failed, payment_disputed); null otherwise."},"interval":{"type":"string","enum":["day","week","month","year"]},"intervalCount":{"type":"integer"},"amountCents":{"type":"integer","description":"The recurring per-cycle price in minor units — not the first-invoice total."},"currency":{"type":"string"},"trialDays":{"type":["integer","null"],"description":"Echo of the create call; null when not set."},"signupFeeCents":{"type":["integer","null"],"description":"Echo of the create call; null when not set."},"createdAt":{"type":"string","format":"date-time"},"startedAt":{"type":["string","null"],"format":"date-time","description":"When the first payment succeeded; null while \"pending\"."},"lastRenewalAt":{"type":["string","null"],"format":"date-time"},"renewalCount":{"type":"integer","description":"How many renewal cycles have billed."},"nextBillingEstimate":{"type":["string","null"],"format":"date-time","description":"Computed ESTIMATE (last renewal + interval) — NOT a provider-authoritative billing date. Null when no further cycle is expected."},"subscriptionHandle":{"type":"string","description":"The handle from the request path, echoed back."}}},"Payment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","succeeded","failed","expired","canceled"]},"attemptNo":{"type":"integer"},"amountCents":{"type":"integer"},"hostedUrl":{"type":"string","format":"uri"},"expiresAt":{"type":["string","null"],"format":"date-time","description":"Null when the provider link has no fixed server-side expiry."},"failureCode":{"type":["string","null"]},"failureMessage":{"type":["string","null"]}}},"Order":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"orderNo":{"type":"string"},"status":{"type":"string","enum":["pending","awaiting_payment","paid","partially_refunded","refunded","disputed","canceled","fulfilled","completed","failed"]},"currency":{"type":"string"},"totalCents":{"type":"integer"},"refundedTotalCents":{"type":"integer"},"paidAt":{"type":["string","null"],"format":"date-time"},"payments":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}}}},"RefundRequest":{"type":"object","required":["whitesiteId","paymentId","amountCents"],"properties":{"whitesiteId":{"type":"string","format":"uuid","description":"From the create response `routedVia.whitesiteId`."},"paymentId":{"type":"string","format":"uuid"},"amountCents":{"type":"integer","minimum":1,"description":"Must not exceed the unrefunded balance."},"reason":{"type":"string"}}},"Refund":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"paymentId":{"type":"string","format":"uuid"},"externalRefundId":{"type":"string"},"amountCents":{"type":"integer"},"reason":{"type":["string","null"]},"status":{"type":"string","enum":["pending","succeeded","failed"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"RoutingStatus":{"type":"object","properties":{"available":{"type":"boolean"},"whitesiteCount":{"type":"integer"},"moneySiteId":{"type":"string","format":"uuid"},"cacheAgeSeconds":{"type":"integer"}}},"WebhookEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique delivery id (also in X-Nxtpay-Delivery-Id) — use for idempotency."},"type":{"type":"string","enum":["order.created","order.paid","order.failed","order.refunded","order.disputed","payment.succeeded","payment.failed","payment.expired","refund.succeeded","dispute.created","dispute.updated","subscription.renewed","subscription.payment_failed","subscription.canceled"]},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","additionalProperties":true}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code, e.g. positive_amountCents_required."},"message":{"type":"string"},"detail":{"type":"string"},"retryAfter":{"type":"integer","description":"Seconds to wait (rate_limited only)."},"correlationId":{"type":"string","description":"Opaque support id on ambiguous 502s (payment_unconfirmed, subscription_unconfirmed, all_whitesites_failed) — also in the X-Request-Id response header."},"upstreamStatus":{"type":"integer","description":"Upstream HTTP status on payment_link_rejected / subscription_rejected."}}},"WebhookEndpointRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"HTTPS endpoint to deliver signed webhooks to."}}},"WebhookEndpointResponse":{"type":"object","properties":{"ok":{"type":"boolean"},"secret":{"type":"string","description":"The `whsec_…` signing secret — store it; it verifies X-Nxtpay-Signature and is only shown here. Rotated on every call."},"webhookUrl":{"type":"string","format":"uri"},"connected":{"type":"array","items":{"type":"object","properties":{"whitesiteId":{"type":"string","format":"uuid"},"name":{"type":"string"}}}},"failed":{"type":"array","items":{"type":"object","properties":{"whitesiteId":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}}},"HeartbeatRequest":{"type":"object","properties":{"pluginVersion":{"type":"string"},"wpVersion":{"type":"string"},"siteUrl":{"type":"string","format":"uri"}}},"HeartbeatResponse":{"type":"object","properties":{"ok":{"type":"boolean"},"routingAvailable":{"type":"boolean","description":"Whether any processor is currently active."}}}}}}