/** * TypeScript type definitions for the Delopay API. * Field names match the JSON wire format (snake_case). */ type Currency = 'AED' | 'AFN' | 'ALL' | 'AMD' | 'ANG' | 'AOA' | 'ARS' | 'AUD' | 'AWG' | 'AZN' | 'BAM' | 'BBD' | 'BDT' | 'BGN' | 'BHD' | 'BIF' | 'BMD' | 'BND' | 'BOB' | 'BRL' | 'BSD' | 'BTN' | 'BWP' | 'BYN' | 'BZD' | 'CAD' | 'CDF' | 'CHF' | 'CLF' | 'CLP' | 'CNY' | 'COP' | 'CRC' | 'CUC' | 'CUP' | 'CVE' | 'CZK' | 'DJF' | 'DKK' | 'DOP' | 'DZD' | 'EGP' | 'ERN' | 'ETB' | 'EUR' | 'FJD' | 'FKP' | 'GBP' | 'GEL' | 'GHS' | 'GIP' | 'GMD' | 'GNF' | 'GTQ' | 'GYD' | 'HKD' | 'HNL' | 'HRK' | 'HTG' | 'HUF' | 'IDR' | 'ILS' | 'INR' | 'IQD' | 'IRR' | 'ISK' | 'JMD' | 'JOD' | 'JPY' | 'KES' | 'KGS' | 'KHR' | 'KMF' | 'KPW' | 'KRW' | 'KWD' | 'KYD' | 'KZT' | 'LAK' | 'LBP' | 'LKR' | 'LRD' | 'LSL' | 'LYD' | 'MAD' | 'MDL' | 'MGA' | 'MKD' | 'MMK' | 'MNT' | 'MOP' | 'MRU' | 'MUR' | 'MVR' | 'MWK' | 'MXN' | 'MYR' | 'MZN' | 'NAD' | 'NGN' | 'NIO' | 'NOK' | 'NPR' | 'NZD' | 'OMR' | 'PAB' | 'PEN' | 'PGK' | 'PHP' | 'PKR' | 'PLN' | 'PYG' | 'QAR' | 'RON' | 'RSD' | 'RUB' | 'RWF' | 'SAR' | 'SBD' | 'SCR' | 'SDG' | 'SEK' | 'SGD' | 'SHP' | 'SLE' | 'SLL' | 'SOS' | 'SRD' | 'SSP' | 'STD' | 'STN' | 'SVC' | 'SYP' | 'SZL' | 'THB' | 'TJS' | 'TMT' | 'TND' | 'TOP' | 'TRY' | 'TTD' | 'TWD' | 'TZS' | 'UAH' | 'UGX' | 'USD' | 'UYU' | 'UZS' | 'VES' | 'VND' | 'VUV' | 'WST' | 'XAF' | 'XCD' | 'XOF' | 'XPF' | 'YER' | 'ZAR' | 'ZMW' | 'ZWL' | string; type IntentStatus = 'succeeded' | 'failed' | 'declined' | 'cancelled' | 'cancelled_post_capture' | 'processing' | 'requires_customer_action' | 'requires_merchant_action' | 'requires_payment_method' | 'requires_confirmation' | 'requires_capture' | 'partially_captured' | 'partially_captured_and_capturable' | 'partially_authorized_and_requires_capture' | 'partially_captured_and_processing' | 'conflicted' | 'expired'; type CaptureMethod = 'automatic' | 'manual' | 'manual_multiple' | 'scheduled' | 'sequential_automatic'; type AuthenticationType = 'three_ds' | 'no_three_ds'; type RefundStatus = 'succeeded' | 'failed' | 'pending' | 'review'; type RefundType = 'scheduled' | 'instant'; type PaymentMethod = 'card' | 'card_redirect' | 'pay_later' | 'wallet' | 'bank_redirect' | 'bank_transfer' | 'crypto' | 'bank_debit' | 'reward' | 'real_time_payment' | 'upi' | 'voucher' | 'gift_card' | 'open_banking' | 'mobile_payment' | 'network_token' | 'game_items' | string; type PaymentMethodType = string; type ConnectorType = 'payment_processor' | 'payment_vas' | 'fin_operations' | 'fiz_operations' | 'networks' | 'banking_entities' | 'non_banking_finance' | 'payout_processor' | 'payment_method_auth' | 'authentication_processor' | 'tax_processor' | 'billing_processor' | 'vault_processor'; /** * Snake-case connector identifiers recognized by the Delopay API. The * trailing `string` keeps this type open: new connectors may be added * to the API before the SDK is updated, so any name can be passed * without a type error. Known names get IDE autocomplete. */ type Connector = 'aci' | 'adyen' | 'adyenplatform' | 'affirm' | 'airwallex' | 'amazonpay' | 'archipel' | 'authipay' | 'authorizedotnet' | 'bambora' | 'bamboraapac' | 'bankofamerica' | 'barclaycard' | 'billwerk' | 'bitpay' | 'blackhawknetwork' | 'bluesnap' | 'boku' | 'braintree' | 'breadpay' | 'calida' | 'cardinal' | 'cashtocode' | 'celero' | 'checkbook' | 'checkout' | 'coinbase' | 'coingate' | 'creem' | 'cryptomus' | 'cryptopay' | 'ctp_mastercard' | 'ctp_visa' | 'cybersource' | 'cybersourcedecisionmanager' | 'datatrans' | 'delopaythreedsserver' | 'deutschebank' | 'digitalvirgo' | 'dlocal' | 'dwolla' | 'ebanx' | 'elavon' | 'envoy' | 'epayouts' | 'facilitapay' | 'finix' | 'fiserv' | 'fiservcommercehub' | 'fiservemea' | 'fiuu' | 'flexiti' | 'forte' | 'getnet' | 'gigadat' | 'globalpay' | 'globepay' | 'gocardless' | 'gpayments' | 'helcim' | 'hipay' | 'hyperpg' | 'iatapay' | 'inespay' | 'itaubank' | 'jpmorgan' | 'klarna' | 'loonio' | 'mifinity' | 'mollie' | 'moneris' | 'multisafepay' | 'netcetera' | 'nexinets' | 'nexixpay' | 'nmi' | 'nomupay' | 'nomupay_oppwa' | 'noon' | 'nordea' | 'novalnet' | 'nowpayments' | 'nuvei' | 'opennode' | 'paybox' | 'payload' | 'payme' | 'payone' | 'paypal' | 'paysafe' | 'paystack' | 'paytm' | 'payu' | 'payjustnow' | 'payjustnowinstore' | 'peachpayments' | 'phonepe' | 'placetopay' | 'plaid' | 'powertranz' | 'prophetpay' | 'rapyd' | 'razorpay' | 'recurly' | 'redsys' | 'revolv3' | 'riskified' | 'santander' | 'shift4' | 'signifyd' | 'silverflow' | 'skinsback' | 'square' | 'stax' | 'stripe' | 'stripebilling' | 'taxjar' | 'tesouro' | 'threedsecureio' | 'tokenex' | 'tokenio' | 'truelayer' | 'trustly' | 'trustpay' | 'trustpayments' | 'tsys' | 'vgs' | 'volt' | 'wellsfargo' | 'wise' | 'worldline' | 'worldpay' | 'worldpaymodular' | 'worldpayvantiv' | 'worldpayxml' | 'xendit' | 'zen' | 'zift' | 'zsl' | 'stripe_billing_test' | 'phonypay' | 'fauxpay' | 'pretendpay' | 'stripe_test' | 'adyen_test' | 'checkout_test' | 'paypal_test' | (string & {}); type DisputeStage = 'pre_dispute' | 'dispute' | 'pre_arbitration' | 'arbitration' | 'dispute_reversal'; type DisputeStatus = 'dispute_opened' | 'dispute_expired' | 'dispute_accepted' | 'dispute_cancelled' | 'dispute_challenged' | 'dispute_won' | 'dispute_lost'; type MandateStatus = 'active' | 'inactive' | 'pending' | 'revoked'; type PayoutStatus = 'success' | 'failed' | 'cancelled' | 'initiated' | 'expired' | 'reversed' | 'pending' | 'ineligible' | 'requires_creation' | 'requires_confirmation' | 'requires_payout_method_data' | 'requires_fulfillment' | 'requires_vendor_account_creation'; type PayoutType = 'card' | 'bank' | 'wallet' | 'bank_redirect'; type FeeType = 'percentage' | 'flat' | 'combined'; type FeeOwner = 'platform' | 'merchant'; type ApiKeyExpiration = 'never' | string; type AuthenticationStatus = 'started' | 'pending' | 'success' | 'failed'; type MandateType = 'single_use' | 'multi_use'; type MerchantAccountType = 'standard' | 'platform' | 'connected'; /** * The event types `events.trigger()` accepts. * * Firing an event by hand works by overwriting the payload's `status`, so it * is defined only where the object HAS a status a shop's order state keys off: * payment intents and refunds. The endpoint refuses everything else with a * 400, so this is the wire contract, not a convenience narrowing. * * `EventType` is built from this union rather than repeating its members, so * the two cannot drift apart. */ type ManualTriggerEventType = 'payment_succeeded' | 'payment_failed' | 'payment_processing' | 'payment_cancelled' | 'payment_cancelled_post_capture' | 'payment_authorized' | 'payment_partially_authorized' | 'payment_captured' | 'payment_expired' | 'action_required' | 'refund_succeeded' | 'refund_failed'; type EventType = ManualTriggerEventType | 'dispute_opened' | 'dispute_expired' | 'dispute_accepted' | 'dispute_cancelled' | 'dispute_challenged' | 'dispute_won' | 'dispute_lost' | 'mandate_active' | 'mandate_revoked' | 'payout_success' | 'payout_failed' | 'payout_initiated' | 'payout_processing' | 'payout_cancelled' | 'payout_expired' | 'payout_reversed' | 'invoice_paid' /** * A chargeback was recorded against a subscription cycle the billing * processor charged itself. The cycle has no payment behind it, so this is * not a `dispute_*` event: `content.object` is a * {@link SubscriptionDisputeWebhook}, never a {@link DisputeResponse}. */ | 'invoice_disputed' | SubscriptionLifecycleEventType; /** * The subscription lifecycle events, as one union so a handler can match the * whole family. * * Every one of them carries `content.type === 'subscription_details'` with a * {@link LifecycleWebhook} as `object` — an immutable snapshot of the * subscription at the transition — while `invoice_paid` keeps its * {@link ConfirmSubscriptionResponse} payload unchanged and `invoice_disputed` * carries a {@link SubscriptionDisputeWebhook}. See * {@link SubscriptionWebhookContent} for telling the three apart. * * What the backend guarantees about them: * * - **Adoption is silent.** A subscription that already existed when lifecycle * events started emits nothing for the state it is found in; its next * transition emits normally. * - **A scheduled cancellation emits `subscription_cancelled` while the status * is still `active`**, with `cancel_at_period_end: true`. The status moves to * `cancelled` when the paid period ends. * - **The provider's own vocabulary does not leak.** PayPal has no separate * resumed or past-due callback; consumers see DeloPay's generic events * regardless of the billing processor. * - **`transition_id` is stable across delivery retries** of one transition, * which is what to deduplicate on. */ type SubscriptionLifecycleEventType = 'subscription_created' | 'subscription_active' | 'subscription_past_due' | 'subscription_paused' | 'subscription_resumed' | 'subscription_cancelled' | 'subscription_expired' | 'invoice_payment_failed'; type EventClass = 'payments' | 'refunds' | 'disputes' | 'mandates' | 'payouts' | 'subscriptions'; type WebhookDeliveryAttempt = 'initial_attempt' | 'automatic_retry' | 'manual_retry' /** * An event a dashboard operator fired by hand. * * Not a replay. `manual_retry` redelivers the recorded bytes of an event the * router produced; this is a brand-new event whose payload status was forced * to agree with the chosen event type, so it may assert a state DeloPay's own * records contradict. Treat it as an assertion by a person, never as evidence * of what the object did. */ | 'manual_trigger'; /** * Why DeloPay stopped delivering an event. * * Absent on an undelivered event means no reason was recorded — normally a * delivery still inside its retry budget — so read it as "not delivered", not as * "failed". A value here is what says no further attempt will be made. * * - `endpoint_rejected` — the endpoint answered with a status redelivery cannot * fix (a permanent 4xx such as 401, 403, 404 or 422). * - `retries_exhausted` — every attempt in the retry budget was made and none * succeeded. * - `no_webhook_url` — the profile has no usable webhook URL, so delivery has * stopped. Reached when the URL is removed or broken partway through the retry * schedule, so the event may already have been attempted. */ type WebhookDeliveryTerminalReason = 'endpoint_rejected' | 'retries_exhausted' | 'no_webhook_url'; type BlocklistDataKind = 'payment_method' | 'card_bin' | 'extended_card_bin'; type RelayType = 'refund' | 'capture' | 'incremental_authorization' | 'void'; type RelayStatus = 'created' | 'pending' | 'success' | 'failure'; type ThreeDSDecision = 'no_three_ds' | 'challenge_requested' | 'challenge_preferred' | 'three_ds_exemption_requested_tra' | 'three_ds_exemption_requested_low_value' | 'issuer_three_ds_exemption_requested'; type PollStatus = 'pending' | 'completed' | 'not_found'; type TransactionType = string; interface AddressDetails { city?: string | null; country?: string | null; line1?: string | null; line2?: string | null; line3?: string | null; zip?: string | null; state?: string | null; first_name?: string | null; last_name?: string | null; } interface PhoneDetails { number?: string | null; country_code?: string | null; } interface Address { address?: AddressDetails | null; phone?: PhoneDetails | null; email?: string | null; } interface AutoRechargeConfig { enabled: boolean; threshold_amount: number; recharge_amount: number; } interface CardDetail { card_number: string; card_exp_month: string; card_exp_year: string; card_holder_name?: string | null; card_cvc?: string | null; card_issuing_country?: string | null; card_network?: string | null; card_issuer?: string | null; nick_name?: string | null; } interface CardDetailFromLocker { scheme?: string | null; issuer_country?: string | null; issuer_country_code?: string | null; last4_digits?: string | null; expiry_month?: string | null; expiry_year?: string | null; card_token?: string | null; card_holder_name?: string | null; card_fingerprint?: string | null; nick_name?: string | null; card_network?: string | null; card_isin?: string | null; card_issuer?: string | null; card_type?: string | null; card_extended_bin?: string | null; card_exp_month?: string | null; card_exp_year?: string | null; payment_checks?: Record | null; authentication_data?: Record | null; saved_to_locker: boolean; } /** * Parameters for creating a new payment intent. * * @example * ```typescript * const payment = await delopay.payments.create({ * amount: 5000, * currency: 'EUR', * customer_id: 'cus_123', * confirm: true, * payment_token: 'tok_...', * }); * ``` */ interface PaymentCreateRequest { /** Amount in minor units (e.g. `5000` for €50.00). */ amount: number; /** Three-letter ISO 4217 currency code (e.g. `'EUR'`, `'USD'`). */ currency: Currency; /** Idempotency key — provide your own payment ID to avoid duplicate creation. */ payment_id?: string | null; /** Tax amount included in the order total, in minor units. */ order_tax_amount?: number | null; /** Amount to capture when using manual capture, in minor units. */ amount_to_capture?: number | null; /** Shipping cost included in the order total, in minor units. */ shipping_cost?: number | null; /** Override routing algorithm for this specific payment. */ routing?: Record | null; /** Restrict which connectors may be used for this payment. */ connector?: string[] | null; /** How and when funds are captured (`'automatic'` or `'manual'`). */ capture_method?: CaptureMethod | null; /** Whether to request 3-D Secure authentication (`'three_ds'` or `'no_three_ds'`). */ authentication_type?: AuthenticationType | null; /** Billing address for the payment. */ billing?: Address | null; /** Set to `true` to confirm the payment immediately upon creation. */ confirm?: boolean | null; /** ID of an existing customer to associate with this payment. */ customer_id?: string | null; /** Inline customer details (used when creating a customer on-the-fly). */ customer?: { id?: string | null; name?: string | null; email?: string | null; phone?: string | null; phone_country_code?: string | null; } | null; /** Human-readable description shown on receipts and in the dashboard. */ description?: string | null; /** URL to redirect the customer to after payment (required for redirect-based methods). */ return_url?: string | null; /** URL to leave an unpaid hosted checkout; never falls back to `return_url`. */ cancel_url?: string | null; /** * How long the client secret / hosted checkout link stays usable, in * seconds from creation. Omit to inherit the shop's default * (`business_profile.session_expiry`, itself defaulting to 15 minutes). * The backend rejects values outside its accepted window with a 400. */ session_expiry?: number | null; /** Descriptor shown on the customer's bank statement (name portion). */ statement_descriptor_name?: string | null; /** Descriptor shown on the customer's bank statement (suffix portion). */ statement_descriptor_suffix?: string | null; /** Arbitrary key-value metadata stored alongside the payment. */ metadata?: Record | null; /** Shipping address for the order. */ shipping?: Address | null; /** Payment method type (e.g. `'card'`, `'wallet'`, `'bank_redirect'`). */ payment_method?: PaymentMethod | null; /** Payment method sub-type (e.g. `'credit'`, `'apple_pay'`). */ payment_method_type?: PaymentMethodType | null; /** Payment method data payload (card details, wallet token, etc.). */ payment_method_data?: Record | null; /** A previously generated payment token representing a saved payment method. */ payment_token?: string | null; /** Whether to save the payment method for future use (`'off_session'` or `'on_session'`). */ setup_future_usage?: 'off_session' | 'on_session' | null; /** ID of an existing mandate to use for this payment. */ mandate_id?: string | null; /** Set to `true` when charging a customer without their active involvement (recurring). */ off_session?: boolean | null; /** Shop (business profile) ID to process the payment under. */ profile_id?: string | null; /** Customer email address. */ email?: string | null; /** Customer name. */ name?: string | null; /** Customer phone number. */ phone?: string | null; /** Customer phone country code (e.g. `'+1'`). */ phone_country_code?: string | null; /** Restrict which payment method types the customer may choose. */ allowed_payment_method_types?: PaymentMethodType[] | null; /** Browser metadata for 3-D Secure fingerprinting. */ browser_info?: Record | null; /** Set to `true` to generate a hosted payment link for this payment. */ payment_link?: boolean | null; /** * Render this payment's hosted checkout with a named appearance variant. * * The name must be one of the shop's stored variants (its * `business_specific_configs` keys). A name that matches nothing renders the * shop's default appearance rather than failing — a stale link must still be * payable, so an unknown variant is never an error. * * Create-only. The buyer can override it for a single render with * `?theme=` on the checkout URL, which is what support uses to * reproduce a complaint and what a merchant uses to preview a variant before * pointing anything at it. Neither changes what is stored. * * Only meaningful together with `payment_link: true`; a payment with no * hosted checkout has no appearance to select. */ payment_link_config_id?: string | null; /** * Whether this is a test payment. The environment belongs to the payment, not to * the processor. * * - `true` — run against the processor's sandbox and record the transaction as a * test, keeping it out of live transaction lists and analytics. A processor with * no sandbox credentials stored is called with the only credentials it has, so a * live-only processor will charge for real. * - `false` — run live, even if the processor still carries the deprecated * account-level test-mode toggle. * - omit — the same as `false`. The backend resolves an omitted `test_mode` to * live when it creates the payment, so the processor's account-level toggle is * never consulted for a new payment. Send the field explicitly rather than * relying on the omission: a deliberate live choice and never having looked are * otherwise indistinguishable. * * Create-only: `confirm` and `update` can be called from the browser with a client * secret, so the environment is fixed when you create the payment on your server. * * Requires a DeloPay backend that knows this field. The payments API rejects * unknown fields, so sending it to an older deployment fails the whole create * with `IR_06` rather than ignoring it. */ test_mode?: boolean | null; } interface PaymentUpdateRequest { amount?: number | null; currency?: Currency | null; /** See {@link PaymentCreateRequest.session_expiry}. */ session_expiry?: number | null; order_tax_amount?: number | null; amount_to_capture?: number | null; shipping_cost?: number | null; routing?: Record | null; connector?: string[] | null; capture_method?: CaptureMethod | null; authentication_type?: AuthenticationType | null; billing?: Address | null; confirm?: boolean | null; customer_id?: string | null; description?: string | null; return_url?: string | null; /** See {@link PaymentCreateRequest.cancel_url}. */ cancel_url?: string | null; statement_descriptor_name?: string | null; statement_descriptor_suffix?: string | null; metadata?: Record | null; shipping?: Address | null; payment_method?: PaymentMethod | null; payment_method_type?: PaymentMethodType | null; payment_method_data?: Record | null; payment_token?: string | null; setup_future_usage?: 'off_session' | 'on_session' | null; profile_id?: string | null; email?: string | null; name?: string | null; phone?: string | null; phone_country_code?: string | null; } interface PaymentConfirmRequest { /** Client secret from the created payment, required for client-side confirmation with a publishable key. */ client_secret?: string | null; payment_method?: PaymentMethod | null; payment_method_type?: PaymentMethodType | null; payment_method_data?: Record | null; payment_token?: string | null; return_url?: string | null; /** See {@link PaymentCreateRequest.cancel_url}. */ cancel_url?: string | null; billing?: Address | null; shipping?: Address | null; mandate_id?: string | null; off_session?: boolean | null; customer_id?: string | null; email?: string | null; name?: string | null; phone?: string | null; phone_country_code?: string | null; browser_info?: Record | null; capture_method?: CaptureMethod | null; metadata?: Record | null; } interface PaymentCaptureRequest { merchant_id?: string | null; amount_to_capture?: number | null; refund_uncaptured_amount?: boolean | null; statement_descriptor_suffix?: string | null; statement_descriptor_prefix?: string | null; } interface PaymentCancelRequest { cancellation_reason?: string | null; } /** * Response of `POST /payments/{id}/abandon-attempt`: the attempt the buyer * left open on a connector's hosted page was voided and a fresh attempt now * awaits a payment method, so the same payment can be confirmed again with * another method or connector. */ interface PaymentAbandonAttemptResponse { payment_id: string; /** `requires_payment_method` on success. */ status: IntentStatus; /** The attempt that is now active and awaiting a payment method. */ attempt_id: string; /** The attempt that was abandoned; `null` when nothing was open (idempotent repeat). */ abandoned_attempt_id?: string | null; } /** * A Delopay payment intent returned by the API. * * Check `status` to determine the payment outcome. When `status` is * `'requires_customer_action'`, use `next_action` to redirect or render * additional authentication steps. */ /** * Refund/dispute magnitudes tracked on the payment intent (minor units). * A payment's `status` never changes when it is refunded or disputed — this * object is where that state is visible. */ interface PaymentIntentStateMetadata { /** Sum of succeeded refunds against this payment, in minor units. */ total_refunded_amount?: number | null; /** Sum of open/lost disputed amounts against this payment, in minor units. */ total_disputed_amount?: number | null; } interface PaymentResponse { /** Identifier assigned by the connector to this payment, when available. */ connector_transaction_id?: string | null; /** Unique payment intent ID. */ payment_id: string; /** Merchant account ID that owns this payment. */ merchant_id: string; /** Current lifecycle status of the payment intent. */ status: IntentStatus; /** Authorised amount in minor units. */ amount: number; /** Net amount after discounts and tax, in minor units. */ net_amount: number; /** Amount still available to capture, in minor units. */ amount_capturable: number; /** Connector-assigned merchant identifier. */ processor_merchant_id: string; /** Three-letter ISO 4217 currency code. */ currency: Currency; /** Payment method type used for this payment. */ payment_method: PaymentMethod; /** Number of authorisation attempts made. */ attempt_count: number; /** Tax amount included in the total, in minor units. */ order_tax_amount?: number | null; /** Shipping cost included in the total, in minor units. */ shipping_cost?: number | null; /** Actual captured amount, in minor units. Present after capture. */ amount_received?: number | null; /** Refunded/disputed totals; the payment `status` itself never reflects them. */ state_metadata?: PaymentIntentStateMetadata | null; /** Capture method used. */ capture_method?: CaptureMethod | null; /** Authentication type used (3DS or none). */ authentication_type?: AuthenticationType | null; /** Associated customer ID. */ customer_id?: string | null; /** Payment description. */ description?: string | null; /** Redirect URL used after payment completion. */ return_url?: string | null; /** URL the buyer may use to leave before paying. */ cancel_url?: string | null; /** Billing address. */ billing?: Address | null; /** Shipping address. */ shipping?: Address | null; /** Arbitrary key-value metadata. */ metadata?: Record | null; /** Payment method sub-type. */ payment_method_type?: PaymentMethodType | null; /** Connector used to process this payment. */ connector?: string | null; /** Gateway error code, if the payment failed. */ error_code?: string | null; /** Human-readable error message, if the payment failed. */ error_message?: string | null; /** Token representing the saved payment method. */ payment_token?: string | null; /** Client secret for client-side confirmation (do not log or share). */ client_secret?: string | null; /** ISO 8601 timestamp when the payment was created. */ created?: string | null; /** * ISO 8601 timestamp when the payment (its client secret / hosted * checkout link) expires — the backend's resolved `session_expiry`, which * may differ from the requested one if it was omitted or clamped. * Backends predating the field omit the key entirely (`undefined` here); * backends that expose it may still send `null` when no expiry applies. */ expires_on?: string | null; /** ISO 8601 timestamp of the last status update. */ last_updated?: string | null; /** Whether the payment method is saved for future use. */ setup_future_usage?: string | null; /** Mandate ID associated with this payment. */ mandate_id?: string | null; /** Shop (business profile) ID the payment was processed under. */ profile_id?: string | null; /** Refunds issued against this payment. */ refunds?: RefundResponse[] | null; /** Disputes raised against this payment. */ disputes?: DisputeResponse[] | null; /** Instructions for completing additional customer actions (3DS redirect, etc.). */ next_action?: Record | null; /** Hosted payment link details, present when the payment was created with `payment_link: true`. */ payment_link?: PaymentLinkResponse | null; /** Reason provided when the payment was cancelled. */ cancellation_reason?: string | null; /** Bank statement descriptor (name portion). */ statement_descriptor_name?: string | null; /** Bank statement descriptor (suffix portion). */ statement_descriptor_suffix?: string | null; /** * Which environment this payment ran in. Reflects the `test_mode` you sent at * create, and an omitted `test_mode` is resolved to live at create, so a payment * created through this field carries `true` or `false` rather than nothing. * `null` appears on payments that predate that resolution, and counts as live * everywhere it is filtered on. */ test_mode?: boolean | null; /** * Where this payment's money has got to, according to its rail — for the * active attempt, the one every other attempt-level field here describes. * * Present on the payments list and on retrieve. Absent on the create, * confirm and update responses, where it is not computed, and absent for a * profile-scoped (shop) viewer, who is not shown the host's settlement with * the rail. When present the value is total: `not_recorded` is a value, * never `null`. `available` is **not** paid out — see * {@link ConnectorSettlementStatus}. */ connector_settlement_status?: ConnectorSettlementStatus; /** * How the latest attempt's connector was chosen — the category of the * decision, never the rule. Per attempt, read * `payments.listAttempts()`, whose rows carry their own value. `null` on a * payment recorded before the backend stored it. */ routing_approach?: RoutingApproach | null; [key: string]: unknown; } interface PaymentListParams { customer_id?: string | null; starting_after?: string | null; ending_before?: string | null; limit?: number; created?: string | null; 'created.lt'?: string | null; 'created.gt'?: string | null; 'created.lte'?: string | null; 'created.gte'?: string | null; } /** Optional query flags for `payments.retrieve`. See the method's JSDoc. */ interface PaymentRetrieveOptions { /** Reconcile intent state with the connector before responding. */ force_sync?: boolean; /** Force a connector sync even for intents in early states like * `requires_payment_method` that would otherwise return the local snapshot. */ all_keys_required?: boolean; } interface PaymentListResponse { size: number; data: PaymentResponse[]; } /** * Status of a single payment attempt. Mirrors the backend `AttemptStatus` * enum, and is distinct from a payment intent's status: one intent can carry * several attempts (retries across connectors), each with its own status. */ type AttemptStatus = 'started' | 'authentication_failed' | 'router_declined' | 'declined' | 'authentication_pending' | 'authentication_successful' | 'authorized' | 'authorization_failed' | 'charged' | 'authorizing' | 'cod_initiated' | 'voided' | 'voided_post_charge' | 'void_initiated' | 'capture_initiated' | 'capture_failed' | 'void_failed' | 'auto_refunded' | 'partial_charged' | 'partially_authorized' | 'partial_charged_and_chargeable' | 'unresolved' | 'pending' | 'failure' | 'payment_method_awaited' | 'confirmation_awaited' | 'device_data_collection_pending' | 'integrity_failure' | 'expired'; /** How the customer is expected to complete the payment (redirect, SDK invoke, QR, etc.). */ type PaymentExperience = 'redirect_to_url' | 'invoke_sdk_client' | 'display_qr_code' | 'one_click' | 'link_wallet' | 'invoke_payment_app' | 'display_wait_screen' | 'collect_otp'; /** * Resolved failure detail for a payment attempt: the Delopay-normalised * decline reason plus the raw issuer/connector detail it was derived from. * Each sub-object is an untyped blob whose exact shape depends on the connector. */ interface PaymentErrorDetails { /** Delopay-unified code + message, resolved from the gateway_status_map. */ unified_details?: Record | null; /** Raw issuer decline detail (e.g. network decline code) when available. */ issuer_details?: Record | null; /** Raw connector error detail as returned by the gateway. */ connector_details?: Record | null; } /** * A single attempt made on a payment, with full failure detail. Returned in * the `data` array of `payments.listAttempts`. */ interface PaymentAttemptResponse { /** Unique identifier for this attempt. */ attempt_id: string; /** Status of this attempt (distinct from the parent intent's status). */ status: AttemptStatus; /** Attempt amount in the smallest currency unit (e.g. cents). */ amount: number; /** Order-level tax amount, in the smallest currency unit. */ order_tax_amount?: number | null; currency?: Currency | null; /** Connector (gateway) this attempt was routed to, e.g. `stripe`. */ connector?: string | null; /** Human-readable error message, when the attempt failed. */ error_message?: string | null; payment_method?: PaymentMethod | null; /** Connector's transaction identifier for this attempt. */ connector_transaction_id?: string | null; capture_method?: CaptureMethod | null; authentication_type?: AuthenticationType | null; /** When the attempt was created (RFC 3339). */ created_at: string; /** When the attempt was last modified (RFC 3339). */ modified_at: string; cancellation_reason?: string | null; mandate_id?: string | null; /** Raw connector error code, when the attempt failed. */ error_code?: string | null; payment_token?: string | null; connector_metadata?: Record | null; payment_experience?: PaymentExperience | null; payment_method_type?: PaymentMethodType | null; reference_id?: string | null; /** Delopay-unified decline code (from the gateway_status_map). */ unified_code?: string | null; /** Delopay-unified, human-readable decline message. */ unified_message?: string | null; client_source?: string | null; client_version?: string | null; /** Structured failure detail resolved from the unified + raw error info. */ error_details?: PaymentErrorDetails | null; /** * The connector account (`mca_…`) this attempt ran on. `null` for an * attempt that never reached a * connector, and absent for a caller whose connector grant does not cover * the account. */ merchant_connector_id?: string | null; /** * How this attempt's connector was chosen — the category of the decision, * never the rule. `null` on an attempt recorded * before the backend stored it. */ routing_approach?: RoutingApproach | null; [key: string]: unknown; } /** * The mechanism that chose a payment attempt's connector: the **category** * of the decision, never the rule itself — which rule, which bucket, is not * published here. The eight known * values are listed; the backend publishes the type as an open string, so a * value this SDK has not heard of is passed through as recorded rather than * mapped to a neighbour. `default_fallback` is the weak reading — no routing * rule matched — and not a claim that a default account was chosen. */ type RoutingApproach = 'rule_based_routing' | 'volume_based_routing' | 'straight_through_routing' | 'success_rate_exploitation' | 'success_rate_exploration' | 'contract_based_routing' | 'debit_routing' | 'default_fallback' | (string & {}); /** * A value of the payments list's `routing_approach` filter * ({@link PaymentListFilterConstraints.routing_approach}). * * The eight recorded approaches, spelled exactly as a row's * `routing_approach` carries them, plus two that select an absence: * * - `not_recorded` — the attempt carries no approach at all (the row's * `routing_approach` is `null` or missing). * - `other` — the attempt carries an approach the server has no name for, * which a row passes through as recorded. * * Both are distinct from `default_fallback`, which is a routing decision that * was made and recorded. Unlike {@link RoutingApproach} this set is closed: * the server refuses a value outside it. */ type RoutingApproachFilter = 'rule_based_routing' | 'volume_based_routing' | 'straight_through_routing' | 'success_rate_exploitation' | 'success_rate_exploration' | 'contract_based_routing' | 'debit_routing' | 'default_fallback' | 'not_recorded' | 'other'; /** Response body for `payments.listAttempts` — every attempt on a single payment. */ interface PaymentAttemptsListResponse { /** The number of attempts returned for this payment. */ size: number; /** Every attempt made on this payment, including failed retries across connectors. */ data: PaymentAttemptResponse[]; } /** * Which entity a status-history event belongs to. * * `risk`, `checkout` and `routing` are timeline events with no underlying * status change: `risk` carries a processor risk signal (early fraud warning, * manual review), `checkout` a buyer-side checkout event (native-pane * selection, external tab opened/blocked, abandonment), and `routing` a rail * handover — the payment settled on a different provider than the one that * last failed on the same attempt, which happens when a buyer falls back to * the card form after a native pane errors. For all three, `status` holds the * event name rather than a payment state, and `entity_id` names what it is * about (the processor's signal id, the native-pane method key, or the * connector the payment moved away from — with `connector` naming the one * that settled it). */ type PaymentStatusHistoryEntityType = 'payment' | 'attempt' | 'refund' | 'dispute' | 'risk' | 'checkout' | 'routing'; /** * One event on a payment's status timeline: the creation of, or a status * transition on, the payment intent or one of its attempts / refunds / * disputes. */ interface PaymentStatusHistoryEvent { entity_type: PaymentStatusHistoryEntityType; /** Id of the attempt / refund / dispute the event belongs to (absent for intent-level events). */ entity_id?: string | null; /** The attempt this event is associated with, where known. */ attempt_id?: string | null; /** The status before this event. Absent on creation events and derived events. */ previous_status?: string | null; /** * The status after this event. Absent only on a derived creation event, * where the initial status was not recorded. */ status?: string | null; /** Connector involved in the event, where known. */ connector?: string | null; error_code?: string | null; error_message?: string | null; /** `true` when this event records the creation of the entity. */ is_creation: boolean; /** * `true` when this event was reconstructed from current records (payments * predating the persisted status log) — its timestamp is approximate and * intermediate transitions may be missing. */ derived: boolean; /** ISO-8601 timestamp of the event. */ timestamp: string; [key: string]: unknown; } /** Response body for `payments.listStatusHistory` — the status timeline of a payment. */ interface PaymentStatusHistoryResponse { /** The payment this timeline belongs to. */ payment_id: string; /** * `true` when every event comes from the persisted status-transition log; * `false` when any event had to be reconstructed from current records. */ complete: boolean; /** The number of events returned. */ count: number; /** All recorded events, oldest first. */ events: PaymentStatusHistoryEvent[]; } /** * Why an attempt's candidate list is what it is — one value per code path in * the backend that produces a connector list. * * Not to be confused with {@link RoutingApproach}, which names the *category* * a path stamped on the attempt, nor with the decision-manager configuration * under `routing.decision.*`, which is a different resource that happens to * share the word. * * - `straight_through` — the request or the attempt pinned the connector. * - `single` / `priority` / `volume_split` — the active configuration is that * shape and was applied as written. * - `rule_matched` — a rule of the active program selected the list; * {@link RoutingDecision.matched_rule_name} names it. * - `no_rule_matched` — no rule matched and the program's **own default * selection** was used. This is not a fallback: the merchant wrote that * default. * - `fallback_no_algorithm` — the shop has no active configuration. * - `fallback_algorithm_unavailable` — the active configuration could not be * loaded or compiled. * - `fallback_empty_selection` — the configuration ran and selected nothing. * - `fallback_routing_error` — the routing stage failed. * - `fallback_eligibility` — the configuration produced candidates and the * eligibility analysis removed every one of them. * - `decision_engine` — the Decision Engine's hybrid stage answered. * - `mandate` — a recurring charge took the connector from the stored mandate. * - `pre_routed` — the confirm took the connector session-token routing had * stored on the attempt. * - `pinned_account` — a native checkout pane pinned the account. * - `auto_retry` — this attempt was created by auto-retry and took the next * candidate of the decision on * {@link RoutingDecision.retry_of_attempt_id}. * - `session_flow` — session-token routing across the payment-method types the * session offered. Provisional: shown only while the attempt has reached no * connector, never in place of a recorded dispatch. * - `unknown` — a record exists and the **backend** release serving you cannot * read its kind (a rolling deploy stored a newer release's value). * * Widened with `(string & {})` deliberately. The backend maps values it cannot * name to `unknown` before serialising, so in a single-version deployment the * vocabulary is closed — but a newer backend may add a value, and a closed * union here would turn that into a type error in every consumer. Render an * unrecognised value raw rather than mapping it to a neighbour. */ type RoutingDecisionKind = 'straight_through' | 'single' | 'priority' | 'volume_split' | 'rule_matched' | 'no_rule_matched' | 'fallback_no_algorithm' | 'fallback_algorithm_unavailable' | 'fallback_empty_selection' | 'fallback_routing_error' | 'fallback_eligibility' | 'decision_engine' | 'mandate' | 'pre_routed' | 'pinned_account' | 'auto_retry' | 'session_flow' | 'unknown' | (string & {}); /** * Where a candidate came from: `routed` (the routing stage produced it) or * `fallback` (the eligibility analysis appended it from the shop's default * list). */ type RoutingCandidateOrigin = 'routed' | 'fallback' | (string & {}); /** One candidate connector, in the order routing produced it. */ interface RoutingDecisionCandidate { /** The connector, as the routing rule names it. */ connector: string; /** The connector account, when the rule named one. */ merchant_connector_id: string | null; origin: RoutingCandidateOrigin; /** * The **first** filter that removed this candidate, or `null` for one that * reached the connector list: `eligibility` (constraint graph, eligible list * or inactive account — the analysis does not say which), `disabled`, * `restricted`, `payment_cap`. */ eliminated_by: string | null; } /** One entry of a volume split, with its configured weight and whether the draw landed on it. */ interface RoutingDecisionVolumeSplitEntry { connector: string; merchant_connector_id: string | null; /** * The configured weight **as written in the rule** — not a percentage. The * backend does not normalise these, so a set of weights need not sum to 100. * To show a share, divide by the sum of the weights recorded here. */ split: number; /** Whether the draw landed on this entry. */ drawn: boolean; } /** * A `routing_volume` counter as the program read it **at decision time**. * * Distinct from {@link RoutingVolumeCounter}, which is the *live* counter a * shop's active program would read right now: that one is keyed by window and * scope with a USD figure, this one is the frozen reading behind one decision. */ interface RoutingDecisionVolumeCounter { /** `null` for the program-wide budget; the rule-level budget key otherwise. */ key: string | null; /** * The span the counter aggregated over. Recorded as free text by the * backend, so the known {@link RoutingVolumeWindow} values are offered for * completion but anything may arrive. */ window: RoutingVolumeWindow | (string & {}); /** What the counter aggregated over. See {@link RoutingVolumeScope}. */ scope: RoutingVolumeScope | (string & {}); /** The currency the threshold is written in; `null` means the payment's own currency. */ currency: string | null; /** The counter, in minor units of that currency; `null` when the read yielded nothing. */ value: number | null; /** The rules that condition on this counter, by name. */ rules: string[]; } /** * The recorded routing decision of one attempt: which rule matched, which * candidates routing produced and which filter removed each, which volume * bucket was drawn and what the counters read, which configuration version was * live. * * A caller whose role is narrowed to specific connector accounts sees only the * candidates and split entries on accounts it may see, and **nothing marks * what was omitted**. Rule names and volume counters are merchant-authored and * are never withheld. */ interface RoutingDecision { decision_kind: RoutingDecisionKind; /** The category the attempt carries in `routing_approach`, when the path stamped one. */ routing_approach: RoutingApproach | null; /** The active configuration consulted, when one was. */ algorithm_id: string | null; /** * When the content the decision was evaluated against took effect (seconds * since epoch) — the `valid_from` of its window in the configuration's * history. Stamped from the program the routing cache evaluated, so it names * the content that actually ran even if the configuration was edited moments * later. */ algorithm_valid_from: number | null; /** The configuration's name during that window. */ algorithm_name: string | null; /** * The configuration's 1-based version for that window, numbered exactly as * the configuration's history numbers its entries. `null` when the * configuration is unknown to this merchant or no window starts at the * stamped instant. */ algorithm_version: number | null; /** * The version row of that window, when it is a closed one. `null` for the * live window, which has no row until it is superseded. */ algorithm_version_id: string | null; /** The euclid rule that selected the candidates. */ matched_rule_name: string | null; candidates: RoutingDecisionCandidate[]; volume_split: RoutingDecisionVolumeSplitEntry[] | null; volume_counters: RoutingDecisionVolumeCounter[] | null; /** The attempt this one retried, on an auto-retry. */ retry_of_attempt_id: string | null; /** When the decision was taken (seconds since epoch). */ decided_at: number; } /** One attempt of the payment and, when recorded, its routing decision. */ interface RoutingDecisionAttempt { attempt_id: string; /** The connector the attempt ran on, if it reached one. */ connector: string | null; merchant_connector_id: string | null; /** * `null` means **no decision was recorded** for this attempt. That is a * statement about storage and never about routing: the recorder is * best-effort, attempts that never reached routing (a sync, a cancel) have * nothing to record, and attempts predating the record exist in quantity. * Do not model it as an error. */ decision: RoutingDecision | null; } /** Response body for `payments.routingDecisions` — the routing decision behind each attempt. */ interface PaymentRoutingDecisionsResponse { payment_id: string; /** * The number of attempts **returned** — never the number that exist, since a * narrowed connector grant may have hidden some. */ count: number; attempts: RoutingDecisionAttempt[]; } /** * One slice of the buyer's checkout recording, on its way to our own * storage. * * Sent by the hosted checkout as the buyer works through the page, never by * a merchant integration. Every slice of one recording carries the same * `session_id` — hexadecimal digits and dashes only, at most 64 characters * (a `crypto.randomUUID()` qualifies); the backend refuses any other shape — * and `sequence` is the recorder's own counter within the recording, starting * at 0. A slice carries at most 500 events and at most 1 MiB of them; a * recording holds at most 400 slices and 8 MiB. The event count is **not** a * size bound: one rrweb full snapshot is a single event of a few hundred KB. */ interface ReplayIngestRequest { /** * The payment's client secret. `CheckoutSession.storeReplaySlice` injects * it from the session; a caller never passes it. */ client_secret: string; /** The recording this slice belongs to, as the recorder named it. */ session_id: string; /** The recorder's own counter within this recording, starting at 0. */ sequence: number; /** * The recorded events, in the order they happened. Opaque to the backend: * the recorder's format is the recorder's business. */ events: Record[]; } /** What the recorder learns after handing over a slice. */ interface ReplayIngestResponse { /** The slice's number, echoed so a recorder that pipelines requests can tell the answers apart. */ sequence: number; /** * Whether this slice was already stored. A retry of a slice whose response * was lost answers `true` and stores nothing — the backend keeps a number * once, and refuses the same number with different content. */ duplicate: boolean; /** * Whether the recording has reached the point where further slices will be * refused, so a recorder can stop cleanly rather than send into rejections. */ full: boolean; } /** One stored slice of a recording, as the playback's manifest lists it. */ interface ReplaySliceInfo { sequence: number; event_count: number; byte_size: number; /** The recorder's clock: rrweb timestamps, milliseconds since the epoch. */ first_event_at: number; last_event_at: number; } /** * Response body for `payments.openReplayPlayback` — a playback of a payment's * stored checkout recording, opened: its manifest and the id every slice read * carries. * * Opening a playback is the audited act of watching: one audit row, whose id * is `playback_id`. Slices are served only against that id, so reading every * slice of one playback adds no rows. * * `slices` is ordered by `sequence`. A missing number between two present * ones is a **gap** (a slice whose upload never arrived), not the end of the * recording — a player should say so rather than play the two halves as one. * A payment that was never recorded answers `200` with `slices: []` and * `session_id: null`; `404` means the payment does not exist. */ interface ReplayPlaybackResponse { /** The playback this manifest opened; pass it to `payments.replaySlice`. */ playback_id: string; /** * The recording's name as the recorder chose it, or `null` when nothing is * stored. Optional because the artifact lists it as not required (the * backend serialises the key with `null`; the contract does not promise it). */ session_id?: string | null; /** Earliest and latest event timestamps across every stored slice (rrweb ms), `null` when nothing is stored. */ first_event_at?: number | null; last_event_at?: number | null; /** Events across every stored slice. */ total_events: number; /** * Whether the buyer was expected to leave the page mid-flow — a redirect or * 3-D Secure rail — so a pause in the recording is time off-site, not a * lost upload. Derived from the payment's attempt, the same way the vendor * share route reports it. */ has_redirect_gap: boolean; slices: ReplaySliceInfo[]; } /** Response body for `payments.replaySlice` — one slice's events. */ interface ReplaySliceResponse { sequence: number; /** rrweb events, in the order they happened. Opaque to the backend. */ events: Record[]; } /** * Parameters for creating a refund. * * Omit `amount` to issue a full refund. Provide a value less than the * original payment amount for a partial refund. */ interface RefundCreateRequest { /** ID of the payment to refund. */ payment_id: string; /** Idempotency key — provide your own refund ID to avoid duplicate creation. */ refund_id?: string | null; /** Merchant account ID (inferred from API key if omitted). */ merchant_id?: string | null; /** Amount to refund in minor units. Defaults to the full payment amount. */ amount?: number | null; /** Reason for the refund (stored for reporting purposes). */ reason?: string | null; /** Refund processing type (`'instant'` or `'scheduled'`). */ refund_type?: RefundType | null; /** Arbitrary key-value metadata stored alongside the refund. */ metadata?: Record | null; } interface RefundUpdateRequest { /** Omit to leave the reason unchanged; send an explicit `null` to clear it. */ reason?: string | null; metadata?: Record | null; } /** * Response of `GET /refunds/aggregate`. NOTE: keyed by the backend's internal * refund-status enum (`success`, `failure`, `transaction_failure`, `pending`, * `manual_review`) — NOT the collapsed `RefundStatus` values * (`succeeded`/`failed`/`pending`/`review`) that refund rows carry. */ interface RefundAggregateResponse { status_with_count: Partial>; } /** * That an operator recorded a transition for this resource out of band. * * **Present means somebody recorded a transition here; absent means nobody * did.** It records what was *asserted*, not where the resource's current * status came from, and the difference is load-bearing: a recording is written * before the transition is applied and before the shop is notified, so that a * retry collides instead of applying a second transition, and a later * processor sync can move the resource again afterwards. In both cases the * marker is correct and the `status` beside it may not be the recorded one. * * There is deliberately no `source` field. It could only ever read `operator`, * and it would make exactly the claim about provenance that this object does * not make. * * It names the **most recent** recording and says how many there are: the * underlying table is insert-only with no unique index, because a dispute lost * and then won is two assertions over one resource rather than an edit. * `recording_count` above 1 says that history exists without a second call. */ interface ExternalRecordMarker { /** Who asserted it. */ recorded_by: string; /** When it was recorded here — not when it happened. */ recorded_at: string; /** Why, as the operator stated it. Compelled by the recording route. */ reason: string; /** The processor's own reference for the transition, where one was given. */ connector_reference?: string | null; /** * When it happened at the processor, if the operator knew. * * Absent when they did not, rather than defaulted to the moment the form was * filled in. */ occurred_at?: string | null; /** Whether the recording created this resource here rather than moving one. */ created_by_recording: boolean; /** How many recordings this resource carries, including this one. */ recording_count: number; } /** A Delopay refund returned by the API. */ interface RefundResponse { /** * Present when somebody recorded a refund transition here out of band, * naming the most recent such recording. Absent means nobody did. * * See {@link ExternalRecordMarker}: it says an assertion was made, not where * `status` beside it came from. */ external_record?: ExternalRecordMarker | null; /** Unique refund ID. */ refund_id: string; /** ID of the payment this refund belongs to. */ payment_id: string; /** Refunded amount in minor units. */ amount: number; /** Three-letter ISO 4217 currency code. */ currency: string; /** Current status of the refund. */ status: RefundStatus; /** Connector that processed the refund. */ connector: string; /** Reason provided at creation. */ reason?: string | null; /** Arbitrary key-value metadata. */ metadata?: Record | null; /** Human-readable error message if the refund failed. */ error_message?: string | null; /** Gateway error code if the refund failed. */ error_code?: string | null; /** Normalised cross-connector error code. */ unified_code?: string | null; /** Normalised cross-connector error message. */ unified_message?: string | null; /** ISO 8601 timestamp when the refund was created. */ created_at?: string | null; /** ISO 8601 timestamp of the last status update. */ updated_at?: string | null; /** Shop (business profile) ID the refund was processed under. */ profile_id?: string | null; /** Connector account ID used. */ merchant_connector_id?: string | null; /** Connector-assigned refund identifier. */ connector_refund_id?: string | null; /** Error code received from the issuer for failed refunds. */ issuer_error_code?: string | null; /** Error message received from the issuer for failed refunds. */ issuer_error_message?: string | null; /** Raw connector response payload (debugging). */ raw_connector_response?: string | null; /** * Whether the refund ran in test mode, inherited from the parent payment's * environment (a live payment always refunds live). Null when unknown/legacy * (treated as live). */ test_mode?: boolean | null; } interface RefundListParams { /** Literal ID prefix (3+ characters), otherwise exact; supported across connectors. */ connector_refund_id?: string | null; payment_id?: string | null; refund_id?: string | null; profile_id?: string | null; limit?: number | null; offset?: number | null; start_time?: string | null; end_time?: string | null; connector?: string[] | null; currency?: Currency[] | null; refund_status?: RefundStatus[] | null; /** Filter by environment: true = test only, false = live only, omit for all. */ test_mode?: boolean; } interface RefundListResponse { count: number; data: RefundResponse[]; total_count: number; } /** Parameters for creating a new customer. */ interface CustomerCreateRequest { /** Idempotency key — provide your own customer ID to avoid duplicate creation. */ customer_id?: string | null; /** Customer's full name. */ name?: string | null; /** Customer's email address. */ email?: string | null; /** Customer's phone number (digits only, without country code). */ phone?: string | null; /** Internal notes about the customer. */ description?: string | null; /** Customer's phone country code (e.g. `'+44'`). */ phone_country_code?: string | null; /** Customer's default billing/shipping address. */ address?: AddressDetails | null; /** Arbitrary key-value metadata stored alongside the customer. */ metadata?: Record | null; /** VAT or tax registration number for B2B invoicing. */ tax_registration_id?: string | null; } interface CustomerUpdateRequest { name?: string | null; email?: string | null; phone?: string | null; description?: string | null; phone_country_code?: string | null; address?: AddressDetails | null; metadata?: Record | null; tax_registration_id?: string | null; } /** A Delopay customer returned by the API. */ interface CustomerResponse { /** Unique customer ID. */ customer_id: string; /** ISO 8601 timestamp when the customer was created. */ created_at: string; /** Customer's full name. */ name?: string | null; /** Customer's email address. */ email?: string | null; /** Customer's phone number. */ phone?: string | null; /** Customer's phone country code. */ phone_country_code?: string | null; /** Internal notes. */ description?: string | null; /** Customer's default address. */ address?: AddressDetails | null; /** Arbitrary key-value metadata. */ metadata?: Record | null; /** ID of the customer's default saved payment method, if any. */ default_payment_method_id?: string | null; /** VAT or tax registration number for B2B invoicing. */ tax_registration_id?: string | null; /** Structured identity / KYC documents attached to the customer. */ document_details?: Record | null; /** * Shops (business profiles) this customer has transacted in, derived from * their payments — the same rule the `profile_ids` list filter applies. A * customer created but never charged belongs to no shop. * * Returned by `customers.retrieve` only. Deriving it costs a query per * customer, so the list endpoints omit it: **absent means "not computed", * not "no shops"**. An empty array is a real answer and stays * distinguishable from a field nobody filled in. * * A shop-scoped caller only sees their own shop here — the server narrows * the list to the caller's scope. */ profile_ids?: string[] | null; } interface CustomerListParams { limit?: number | null; offset?: number | null; /** Filter by the unique customer identifier. */ customer_id?: string | null; /** * Case-insensitive prefix search over customer id, name, and email. * Each whitespace-delimited term must contain at least three characters. */ search?: string | null; /** * Filter customers by the shop (business profile) they have transacted in. * Membership is derived from `payment_intent` — a customer who never * transacted appears under no shop. */ profile_id?: string | null; /** * Filter customers by several shops at once. Unions with `profile_id` and * `project_id` — a customer matches if they transacted in any of the * resolved shops. * * Sent as a single comma-separated value (`profile_ids=pro_a,pro_b`); the * SDK joins the array for you. Every id must belong to the calling * merchant, otherwise the request is rejected with `AccessForbidden`. */ profile_ids?: string[] | null; /** * Filter customers by project; expands to all shops (business profiles) * under that project. */ project_id?: string | null; } interface CustomerListWithCountParams extends CustomerListParams { /** Only include customers created at or after this ISO 8601 timestamp. */ start_time?: string | null; /** * Only include customers created at or before this ISO 8601 timestamp. * Requires `start_time`. */ end_time?: string | null; } interface CustomerListResponse { data: CustomerResponse[]; total_count: number; } interface PaymentMethodCreateRequest { payment_method: PaymentMethod; payment_method_type?: PaymentMethodType | null; payment_method_issuer?: string | null; card?: CardDetail | null; metadata?: Record | null; customer_id?: string | null; card_network?: string | null; bank_transfer?: Record | null; wallet?: Record | null; client_secret?: string | null; billing?: Address | null; } interface PaymentMethodUpdateRequest { card?: { card_exp_month?: string | null; card_exp_year?: string | null; card_holder_name?: string | null; } | null; wallet?: Record | null; client_secret?: string | null; } interface PaymentMethodResponse { merchant_id: string; payment_method_id: string; payment_method: PaymentMethod; customer_id?: string | null; payment_method_type?: PaymentMethodType | null; card?: CardDetailFromLocker | null; recurring_enabled?: boolean | null; installment_payment_enabled?: boolean | null; payment_experience?: string[] | null; metadata?: Record | null; created?: string | null; last_used_at?: string | null; client_secret?: string | null; status?: string | null; billing?: Address | null; /** Token used to charge this saved method on a later payment (pass as `payment_token` to `payments.create`). */ payment_token?: string | null; } interface PaymentMethodListParams { client_secret?: string | null; /** * ISO 3166-1 alpha-2 country code. Drives the geo-aware availability resolver * as the highest-precedence country signal (explicit hint > billing > IP-geo), * so the returned set reflects what a customer in that country would see. */ country?: string | null; /** * Order value in minor units. Filters out methods the connector does not * accept at that amount, and applies the merchant's own order-value rules — * so the returned set is what a customer with this cart would actually see. */ amount?: number | null; /** Only return methods that can be used for recurring payments. */ recurring_enabled?: boolean | null; /** Only return methods that support installments. */ installment_payment_enabled?: boolean | null; /** Maximum number of methods to return. */ limit?: number | null; /** Only return card methods supporting all of these networks. */ card_networks?: string[] | null; } /** * Human-facing name and icon for a payment method type, so you can render your * own checkout without maintaining a parallel name/logo table. */ interface PaymentMethodDisplayInfo { /** Canonical name, e.g. `"Credit Card"`, `"PayPal"`. English only for now. */ display_name: string; /** * Stable lowercase icon identifier, e.g. `"card"`, `"paypal"`. Same value as * `payment_method_type`, except that `credit` and `debit` share the `card` * slug. Safe to map 1:1 onto your own icon set. */ icon_slug: string; /** * Absolute URL to a DeloPay-hosted icon, when icon hosting is configured for * the deployment. Always `null` today — map `icon_slug` against your own * assets. */ icon_url?: string | null; } /** A closed, inclusive band of order values in minor units. */ interface AmountRange { min_amount: number; max_amount: number; } /** * The order values a payment method is available for, so you can re-evaluate * your own tiles as the cart total changes instead of re-listing on every * keystroke. * * A method is available when `min_amount <= amount <= max_amount` and the amount * falls in none of `excluded_ranges`. Bounds are inclusive minor units in * `currency`; `null` means unbounded on that side. * * Folds together the connector's own configured limits and the merchant's * order-value availability rules. Stated in the payment's currency and never * converted, which is how the merchant rules are evaluated — so the field is * absent when the call resolves no currency. */ interface PaymentMethodAmountLimits { currency: Currency; min_amount?: number | null; max_amount?: number | null; /** * Bands *inside* `[min_amount, max_amount]` where the method is nevertheless * unavailable, produced by a merchant rule hiding it for a closed range. * Almost always empty — but the backend enforces these rules on * `payments.create`, so a cart total inside one of these bands is refused, * not merely untiled. */ excluded_ranges: AmountRange[]; } interface PaymentExperienceTypes { payment_experience_type?: PaymentExperience | null; /** Merchant-internal: omitted for publishable-key callers. */ eligible_connectors?: string[]; } interface CardNetworkTypes { card_network?: string | null; surcharge_details?: SurchargeDetailsResponse | null; /** Merchant-internal: omitted for publishable-key callers. */ eligible_connectors?: string[]; } interface BankCodeResponse { bank_name: string[]; /** Merchant-internal: omitted for publishable-key callers. */ eligible_connectors?: string[]; } interface BankDebitTypes { /** Merchant-internal: omitted for publishable-key callers. */ eligible_connectors?: string[]; } interface BankTransferTypes { /** Merchant-internal: omitted for publishable-key callers. */ eligible_connectors?: string[]; } interface RequiredFieldInfo { required_field: string; display_name: string; field_type: string; value?: string | null; } type SurchargeResponse = { type: 'fixed'; value: number; } | { type: 'rate'; value: { percentage: number; }; }; /** * Where a pane's tile is offered. * * - `always` — wherever the checkout renders. * - `embedded_only` — inside a merchant iframe only, which keeps the wallet * inside the connector's own form at top level. * - `external_only` — the inverse: only when the checkout renders at top level * (a hosted payment link or the focused view), hidden inside a merchant * iframe. * * Wallet rail only. A redirect pane is suppressed server-side, before any * render knows whether it is framed, so a framing-dependent value there would * leave the method unpayable on one side and the router forces it back to * `always`. */ type PaneVisibility = 'always' | 'embedded_only' | 'external_only'; /** * How the checkout opens a pane's focused view: in the same page * (`same_page` — the buyer's window navigates to the processor and comes back * to the return URL), a new browser tab (`tab`, the historical default) or a * centred popup window (`popup`). `tab` and `popup` only matter when the * checkout renders inside an iframe — a top-level render always navigates in * place. Browsers that refuse popup windows fall back to a tab on their own. */ type PaneOpenTarget = 'same_page' | 'tab' | 'popup'; /** * The adjustment a buyer pays (or is credited) for one pane. * * Every amount is **signed** — negative is a provider discount — and the minor * and major-unit figures are both sent: format from `display_total_amount`, * reconcile against `total_amount`. A client that derived major units itself * would have to carry the zero-decimal currency table the router already has. */ interface PaneSurcharge { /** The adjustment excluding tax, in minor units. Signed. */ surcharge_amount: number; /** Tax on the adjustment, in minor units. Same sign as `surcharge_amount`. */ tax_amount: number; /** `surcharge_amount + tax_amount`, in minor units. Signed. */ total_amount: number; /** The same total in the payment's currency major units. Signed. */ display_total_amount: number; /** * Whether the buyer is charged or credited. Redundant with the sign, and * carried so a renderer picks its word from a stated fact rather than from a * comparison against zero. */ direction: SurchargeDirection; /** * What this adjustment is as an unsigned percentage of the order amount — * `1.5` for 1.5% — so a tile can show a share instead of, or beside, a figure * in money. `null` only on an order of zero. * * Derived by the router from the charge rather than read off the merchant's * rule, so the two figures a tile can print always describe the same money: a * fixed rule has no configured rate and still has a share of this cart, and a * rate rule carrying tax charges more than its own rate. * * Unsigned — `direction` carries the sign. */ percent_of_order?: number | null; } /** * One resolved pane as the buyer-facing checkout receives it on the * payment-link payload (`native_panes`). Labels are already localized for the * render's locale and icons already sanitized — snake_case because this is the * API wire shape, not the editor's. */ interface PaneView { method: string; /** * Connector brand that owns this pane (`stripe`, `klarna`, …). * * Pass straight to {@link focusedCheckoutUrl}'s `connector` to mint a link * that resolves to this tile and no other: `method` alone is ambiguous the * moment two connectors publish one key, and a bare `pane=` then resolves to * whichever tile sorts first server-side. * * It names a brand, not an account — {@link PaneView.merchant_connector_id} * is what separates two accounts of the same connector. * * Optional only because a router predating the field omits it; every router * that has it always serializes it, and it is never `null`. */ connector?: string; /** * Merchant connector **account** this pane was configured on. * * Pass straight to {@link focusedCheckoutUrl}'s `merchantConnectorId`. The * checkout echoes this back on confirm as `native_pane_merchant_connector_id` * and the router re-validates it against the profile's live accounts, so a * pane charges the credentials it was configured on rather than a sibling * account's. * * Absent on the wallet rail — that rail charges the PaymentIntent the card * connector already created, so there is no routing decision to pin — and on * payloads predating the field. Omitted rather than `null` when unset. */ merchant_connector_id?: string; rail: PaneRail; /** * The **connector's own** name for this method, on the rails where the * browser needs it: `hosted_element` mounts the connector's SDK element by * that name (`applepay`, `ideal`, `klarna` for Airwallex). Published rather * than derived in the checkout, so one table owns the translation between * DeloPay's vocabulary and each processor's. Absent on the rails that do * not mount by name, and on a router predating the field. */ connector_method_name?: string | null; label: string; sublabel: string; /** * `true` when {@link PaneView.label} is the router's compiled catalog * default rather than anything the merchant typed. * * The catalog defaults are compiled in English only — the merchant's * `labelTranslations` are the sole localized path — so a merchant who * configures nothing gets an English tile label under a translated section * heading. This flag is what lets a localizing surface substitute its own * copy for exactly those tiles and leave merchant-authored ones alone. * * Key that copy on `method` alone. Two connectors may publish one key — * Cryptomus and NOWPayments both publish `crypto`, Stripe and Klarna both * publish `klarna` — and the catalog copy is identical for both on purpose. * What tells such a pair apart is * {@link PaneView.connector_display_name}, which is not translated and must * be appended to whichever copy wins. * * Absent on payloads that predate the field, which read as `false` — * merchant-authored, so nothing gets rewritten. */ label_is_default?: boolean; /** * `true` when {@link PaneView.sublabel} is the router's compiled catalog * default. Same contract as {@link PaneView.label_is_default}. * * An explicitly-empty sublabel is a merchant decision ("hide the second * line") and reports `false`, so substituting copy there would restore a * line they deliberately cleared. */ sublabel_is_default?: boolean; /** * The connector's brand, present **only** when this render would otherwise * show two tiles a buyer cannot tell apart — a merchant running both * Cryptomus and NOWPayments, or both Klarna rails. * * Append it to the sublabel you render (`` `${sublabel} · ${name}` ``). * It travels separately from {@link PaneView.sublabel} precisely because that * string is replaced wholesale when {@link PaneView.sublabel_is_default} is * set: a brand baked into it would be discarded with it, collapsing the two * tiles again. Do not translate it — brand names are the same in every * locale, which is why it can arrive as data at all. * * Absent for the common case of one connector per method. A merchant with * only Stripe must never read "· via Stripe" on a tile there is nothing * to distinguish it from. */ connector_display_name?: string | null; category: string; icon?: string | null; icon_svg?: string | null; display_order: number; /** * What this tile's own provider adds to (or takes off) the buyer's total. * * Resolved server-side against this pane's {@link PaneView.merchant_connector_id}, * and the pane's confirm is pinned to that same account — so the figure here * is a promise about what the statement will say, not an estimate. Render it * on the tile: with provider-scoped surcharges a buyer choosing between panes * is choosing between prices. * * **Absent means show nothing** — no `+0.00`, no empty slot that shifts the * layout. It is absent whenever no rule applies to this provider, whenever * the pane names no account to price against, and on every payload from a * router that predates the field. Never substitute a merchant-wide figure for * it; that is a price this tile cannot deliver. */ surcharge?: PaneSurcharge | null; /** Redirect rail only — echo verbatim on confirm, never derive. */ payment_method?: string | null; /** Redirect rail only — echo verbatim on confirm, never derive. */ payment_method_type?: string | null; /** Redirect rail only — echo verbatim on confirm, never derive. */ payment_method_data?: Record | null; /** * The shipped look the merchant chose for this tile (`paypal`, `card`), * present only when the router knows it for the method. Draw the preset's * `style` in place of the icon-and-text tile; absent → draw `icon` / * `icon_svg`. */ preset?: string | null; /** The resolved style of `preset` (`native`, `yellow_button`, `mc_visa`, `two_line`, …). */ style?: string | null; /** * Metadata the confirm for this pane must carry — merge it into the * confirm's `metadata`. An aggregator's pane names its vendor here * (`payment_type`) and the instrument the pane stands for (`payment_pane`); * absent on every other connector. */ confirm_metadata?: Record | null; /** * The confirm body needs the buyer's country: merged into * `billing.address.country` and echoed into the single `payment_method_data` * variant's `billing_country`. */ requires_billing_country?: boolean; /** * `true` when the tile is only offered inside an iframe. Wallet rail only. * * Superseded by {@link PaneView.visibility}, which carries all three states, * and kept by the router at exactly its historical meaning (`rail === * 'wallet' && visibility === 'embedded_only'`) so checkout builds that * predate that field keep working. Such a build reads an `external_only` * pane as `embedded_only: false` and shows it in the embed too — it * over-shows, which loses a placement rule, rather than hiding a tile the * buyer needs. Read {@link paneViewVisibility} instead of either field. */ embedded_only?: boolean; /** * Which render contexts this tile is offered in. * * **This is the resolved value, not the merchant's stored one, and the two do * not round-trip.** The router coerces anything the render path cannot * honour before emitting: a pane whose suppression is decided server-side * reads `always` here whatever the merchant configured. Stripe's redirect * panes are the case to know about — the router forces every redirect-rail * pane back to `always` (suppression happens before any render knows whether * it is framed), so a redirect pane stored as `embedded_only` on the config * {@link Pane} still arrives here as `always`. Do not read this field back as * the merchant's setting; read {@link Pane.visibility} off the connector * account's `metadata.native_panes` for that. * * Optional because a router predating this field omits it, not because the * router ever skips it: it is always serialized once present. A value * outside the union can also arrive from a router newer than this SDK, so * read it through {@link paneViewVisibility} rather than comparing it * directly. */ visibility?: PaneVisibility; /** * Where this pane is drawn as the wallet's own button instead of its tile — * see {@link PaneNativeElement}. * * **Absent means "tile only"**, and absent is the common case: the router * omits the field on every method that cannot draw a native button, so a * client never has to know which keys those are, and a router predating the * field omits it everywhere. */ native_element?: PaneNativeElement | null; /** * How the embedded checkout opens this tile's focused view. Absent on * payloads from older backends — treat as `tab`. */ open_in?: PaneOpenTarget; } /** * Which way an adjustment moves the buyer's total. * * The magnitude and the sign travel separately because the router's * `Percentage<2>` is unsigned by construction and is persisted — see * `SurchargeDirection` in `common_utils`. Read this rather than comparing an * amount against zero: a renderer should pick its word from a stated fact. */ type SurchargeDirection = 'surcharge' | 'discount'; interface SurchargeDetailsResponse { surcharge: SurchargeResponse; tax_on_surcharge?: { percentage: number; } | null; /** Signed: negative on a provider discount. */ display_surcharge_amount: number; /** Signed, same direction as `display_surcharge_amount`. */ display_tax_on_surcharge_amount: number; display_total_surcharge_amount: number; /** * Whether the figures above are charged to or credited to the buyer. * * Optional here, required on the wire: a router that predates provider * discounts sends no `direction`, and absent means `surcharge` — which is * what every such response was. */ direction?: SurchargeDirection; } /** One payment method type offered for a payment, with everything needed to render it. */ interface ResponsePaymentMethodTypes { payment_method_type: PaymentMethodType; payment_experience?: PaymentExperienceTypes[] | null; card_networks?: CardNetworkTypes[] | null; /** @deprecated Use the bank list from `required_fields` instead. */ bank_names?: BankCodeResponse[] | null; bank_debits?: BankDebitTypes | null; bank_transfers?: BankTransferTypes | null; /** Fields the customer must supply for this method, keyed by field path. */ required_fields?: Record | null; surcharge_details?: SurchargeDetailsResponse | null; /** Merchant-internal: omitted for publishable-key callers. */ pm_auth_connector?: string | null; display?: PaymentMethodDisplayInfo | null; amount_limits?: PaymentMethodAmountLimits | null; } /** The method types offered under one broad payment method (`card`, `wallet`, …). */ interface ResponsePaymentMethodsEnabled { payment_method: PaymentMethod; payment_method_types: ResponsePaymentMethodTypes[]; } /** * What `paymentMethods.list()` returns: the methods available for a payment, * already filtered by country, order value and the merchant's availability * rules. */ interface PaymentMethodListResponse { redirect_url?: string | null; currency: Currency; payment_methods: ResponsePaymentMethodsEnabled[]; mandate_payment: MandateType; merchant_name?: string | null; show_surcharge_breakup_screen: boolean; payment_type?: string | null; request_external_three_ds_authentication: boolean; collect_shipping_details_from_wallets?: boolean | null; collect_billing_details_from_wallets?: boolean | null; is_tax_calculation_enabled: boolean; sdk_next_action: { next_action: string; }; is_guest_customer: boolean; /** Payment intent details, present when the call was made with a client secret. */ intent_data?: PaymentMethodListIntentData | null; } /** Intent details echoed back with the method list, so a checkout can render without a second call. */ interface PaymentMethodListIntentData { payment_id: string; status: IntentStatus; amount: number; currency?: Currency | null; client_secret?: string | null; description?: string | null; customer_id?: string | null; return_url?: string | null; setup_future_usage?: FutureUsage | null; billing?: Address | null; shipping?: Address | null; metadata?: Record | null; order_details?: Record[] | null; created?: string | null; expires_on?: string | null; profile_id?: string | null; merchant_order_reference_id?: string | null; attempt_count: number; installment_options?: PaymentMethodListInstallmentOption[] | null; } interface PaymentMethodListInstallmentOption { payment_method: PaymentMethod; available_plans: PaymentMethodListInstallmentPlan[]; } interface PaymentMethodListInstallmentPlan { number_of_installments: number; billing_frequency: string; interest_rate: number; amount_details: PaymentMethodListInstallmentAmountDetails; } interface PaymentMethodListInstallmentAmountDetails { /** Amount charged per installment, in major units. */ amount_per_installment: number; /** * Total across all installments, in major units. May differ slightly from the * order amount because per-installment amounts are rounded up. */ total_amount: number; } interface CustomerPaymentMethodsListParams { client_secret?: string | null; accepted_countries?: string[] | null; accepted_currencies?: string[] | null; amount?: number | null; recurring_enabled?: boolean | null; installment_payment_enabled?: boolean | null; limit?: number | null; card_networks?: string[] | null; } interface CustomerPaymentMethodsListResponse { customer_payment_methods: PaymentMethodResponse[]; is_guest_customer?: boolean | null; } interface PaymentMethodDeleteResponse { payment_method_id: string; deleted: boolean; } /** * Whether a processor tells DeloPay how a dispute ended. Four states rather * than a bool: "we have not checked" (`unknown`) is not "it does not report" * (`not_reported`), and "it reports a loss and never a win" (`loss_only`) is * neither — rounding it to `reported` would promise an outcome that never * comes for a dispute the merchant wins. */ type DisputeOutcomeReporting = 'reported' | 'loss_only' | 'not_reported' | 'not_recorded' | 'unknown'; /** Explicit association evidence; it never nets refund and dispute money. */ interface DisputeRefundAssociation { merchant_id: string; dispute_id: string; refund_id: string | null; payment_id: string; merchant_connector_id: string; currency: string; test_mode: boolean; allocated_amount: number | null; declared_by: string | null; provider_reference: string | null; provider_provenance: string | null; created_at: string; modified_at: string; } /** PUT returns the stored declaration without list-time provider enrichment. */ interface DisputeRefundDeclaration { merchant_id: string; dispute_id: string; refund_id: string; payment_id: string; merchant_connector_id: string; currency: string; test_mode: boolean; allocated_amount: number | null; declared_by: string | null; provider_reference: string | null; created_at: string; modified_at: string; } interface DisputeRefundAssociationsResponse { associations: DisputeRefundAssociation[]; } interface DisputeRefundAssociationRequest { refund_id: string; /** Required: positive minor units, or explicit null to clear the declaration and retain history. */ allocated_amount: number | null; } interface DisputeResponse { /** * Present when somebody recorded a dispute transition here out of band, * naming the most recent such recording. Absent means nobody did. * * See {@link ExternalRecordMarker}: it says an assertion was made, not where * `dispute_status` beside it came from. */ external_record?: ExternalRecordMarker | null; dispute_id: string; payment_id: string; attempt_id: string; amount: string; currency: Currency; dispute_stage: DisputeStage; dispute_status: DisputeStatus; connector: string; connector_status: string; connector_dispute_id: string; is_already_refunded: boolean; /** Whether this dispute's processor reports outcomes — see {@link DisputeOutcomeReporting}. */ outcome_reporting: DisputeOutcomeReporting; connector_reason?: string | null; connector_reason_code?: string | null; challenge_required_by?: string | null; connector_created_at?: string | null; connector_updated_at?: string | null; created_at: string; updated_at?: string | null; profile_id?: string | null; merchant_connector_id?: string | null; /** * Whether the dispute is in test mode, inherited from the disputed payment's * environment. Null when unknown/legacy (treated as live). */ test_mode?: boolean | null; } interface DisputeListParams { /** Literal ID prefix (3+ characters), otherwise exact; supported across connectors. */ connector_dispute_id?: string | null; limit?: number | null; offset?: number | null; dispute_id?: string | null; /** Filter disputes raised against one payment. */ payment_id?: string | null; profile_id?: string | null; dispute_status?: DisputeStatus | null; dispute_stage?: DisputeStage | null; reason?: string | null; connector?: string | null; currency?: Currency | null; /** ISO 8601 creation-time range start (required to time-filter). */ start_time?: string | null; /** ISO 8601 creation-time range end; defaults to now. */ end_time?: string | null; /** Filter by environment: true = test only, false = live only, omit for all. */ test_mode?: boolean; } /** Common filters for the unified list, counts, options and complete export. */ interface DisputeWorkspaceRequest { /** Literal case-insensitive identifier substring, including invoice and provider transaction IDs. */ search?: string | null; /** Exact native dispute identifier, resolved through the same scoped deduplicated view. */ native_dispute_id?: string | null; /** Exact identifier of a chargeback against a subscription cycle. */ subscription_dispute_id?: string | null; /** A shop within the authenticated scope. Unassigned provider cases stay merchant-scoped. */ profile_id?: string | null; /** Limit results to the project's authorized shops. */ project_id?: string | null; /** Exact connector account identifier, intersected with role grants. */ merchant_connector_id?: string | null; /** Comma-separated connector names. */ connector?: string[] | null; /** Comma-separated currency codes. */ currency?: string[] | null; /** Comma-separated native status names; unknown selects an unclassified provider outcome. */ dispute_status?: string[] | null; /** Comma-separated native stage names. */ dispute_stage?: string[] | null; /** Exact provider reason. */ reason?: string | null; /** Inclusive UTC received-time lower bound (RFC3339). */ start_time?: string | null; /** Inclusive UTC received-time upper bound (RFC3339). */ end_time?: string | null; /** True selects test, false selects resolved live (including a parent's legacy null mode). */ test_mode?: boolean | null; /** Page size, 1 through 100; defaults to 50. Ignored by counts, options and export. */ limit?: number | null; /** Rows to skip after shared filtering and deduplication. Ignored by counts, options and export. */ offset?: number | null; } /** A view row, never a fabricated payment or financial dispute record. */ interface DisputeWorkspaceRow { /** Stable identifier of the selected stored native row, PayPal case or subscription-cycle chargeback. */ id: string; native_dispute_id?: string | null; paypal_case_id?: string | null; /** * Set for a chargeback against a subscription cycle. Such a row has no * payment: `payment_id` and `attempt_id` are null, and `subscription_id` and * `invoice_id` name the cycle. */ subscription_dispute_id?: string | null; payment_id?: string | null; attempt_id?: string | null; invoice_id?: string | null; subscription_id?: string | null; connector: string; connector_dispute_id: string; merchant_connector_id?: string | null; profile_id?: string | null; /** Native parent mode; null remains unknown. Provider cases carry their verified mode. */ test_mode?: boolean | null; currency: string; /** Native minor-unit decimal string. Never convert through floating point. */ amount_minor?: string | null; /** PayPal major-unit decimal string. Never convert through floating point. */ amount_decimal?: string | null; /** Exact normalized status when known; null is not an invented winner or loser. */ dispute_status?: string | null; dispute_stage?: string | null; provider_status?: string | null; provider_stage?: string | null; provider_outcome?: string | null; connector_reason?: string | null; reason_code?: string | null; challenge_required_by?: string | null; /** First receipt in DeloPay, UTC. The deterministic list order is received time then ID. */ created_at: string; updated_at: string; /** Provider-advertised tools; their existing action routes still authorize execution. */ available_actions: string[]; } interface DisputeWorkspaceResponse { /** Authorized, deduplicated rows in received-time/id order. */ data: DisputeWorkspaceRow[]; /** More matching rows exist beyond this page in the same query snapshot. */ has_more: boolean; } interface DisputeWorkspaceFilters { connector: string[]; currency: string[]; dispute_status: string[]; dispute_stage: string[]; } interface DisputeWorkspaceAggregate { /** All authorized distinct rows matching the filters, without pagination. */ total_count: number; /** Normalized status counts, including an unknown bucket; their sum is total_count. */ status_with_count: Record; /** The unknown bucket repeated for convenience, not an additional count. */ unknown_count: number; } /** Complete filtered match set, or an explicit export-limit refusal; never a truncated export. */ interface DisputeWorkspaceExport { data: DisputeWorkspaceRow[]; total_count: number; } interface DisputeEvidenceRequest { cancel_dispute?: boolean | null; customer_email_address?: string | null; customer_name?: string | null; customer_signature?: string | null; product_description?: string | null; receipt?: string | null; refund_policy?: string | null; refund_policy_disclosure?: string | null; refund_refusal_explanation?: string | null; service_date?: string | null; service_documentation?: string | null; shipping_address?: string | null; shipping_carrier?: string | null; shipping_date?: string | null; shipping_documentation?: string | null; shipping_tracking_number?: string | null; uncategorized_file?: string | null; uncategorized_text?: string | null; access_activity_log?: string | null; billing_address?: string | null; cancellation_policy?: string | null; cancellation_policy_disclosure?: string | null; cancellation_rebuttal?: string | null; customer_communication?: string | null; customer_purchase_ip?: string | null; /** * File id of the document showing the disputed charge and the alleged * duplicate are distinct transactions. This is the field Stripe calls * `duplicate_charge_documentation`. */ invoice_showing_distinct_transactions?: string | null; /** File id of the recurring-transaction agreement (subscription disputes). */ recurring_transaction_agreement?: string | null; /** Explanation of why the disputed charge is not a duplicate. */ duplicate_charge_explanation?: string | null; /** Transaction id of the prior charge the disputed one allegedly duplicates. */ duplicate_charge_id?: string | null; } /** Backend evidence-type identifiers, serde snake_case (e.g. `receipt`, * `invoice_showing_distinct_transactions`). Used for delete + retrieval. */ type DisputeEvidenceType = 'cancellation_policy' | 'customer_communication' | 'customer_signature' | 'receipt' | 'refund_policy' | 'service_documentation' | 'shipping_documentation' | 'invoice_showing_distinct_transactions' | 'recurring_transaction_agreement' | 'uncategorized_file'; /** * One stored file-evidence entry, as `GET /disputes/evidence/{id}` returns * it. The endpoint reports FILE evidence only — text evidence (customer * name, product description, …) is not retrievable once submitted. */ interface DisputeEvidenceBlock { evidence_type: DisputeEvidenceType; file_metadata_response: { file_id: string; file_name?: string | null; file_size?: number; file_type?: string; available?: boolean; }; } /** * Body of `DELETE /disputes/evidence`. * * `deleteEvidence` was typed as `DisputeEvidenceRequest` — the body for * ATTACHING text evidence, which shares no field with this one. A caller * sending what the type asked for got a request the API rejects, and a caller * sending what the API wants did not compile. */ interface DeleteEvidenceRequest { dispute_id: string; evidence_type: DisputeEvidenceType; } interface MandateResponse { mandate_id: string; status: MandateStatus; payment_method_id: string; payment_method: string; payment_method_type?: string | null; card?: { last4_digits?: string | null; card_exp_month?: string | null; card_exp_year?: string | null; card_holder_name?: string | null; card_network?: string | null; card_isin?: string | null; } | null; customer_acceptance?: { acceptance_type?: string | null; accepted_at?: string | null; online?: Record | null; } | null; } interface MandateListParams { /** * `MandateListConstraints` server-side also accepts `limit`, `offset`, * `connector` and the four `created_time.*` bounds, and `GET /mandates/list` * is a real route (`retrieve_mandates_list`, under `olap`). None of it is in * the generated OpenAPI spec — the route is not annotated at all — so it is * deliberately not published here. Widening the SDK past its own contract * would put the drift somewhere nothing checks, which is the failure this * whole PR is about. */ mandate_status?: MandateStatus | null; } interface MandateRevokedResponse { mandate_id: string; status: MandateStatus; error_code?: string | null; error_message?: string | null; } /** Charset of the random body of a custom-format payment id. */ type PaymentIdStyle = 'numeric' | 'alphanumeric' | 'alphanumeric_uppercase'; /** * Merchant-configurable format for generated payment ids (cloaking), * e.g. `ORD-74219807`. Applied per shop (business profile): payments * created without an explicit `payment_id` get ``. */ interface PaymentIdFormatConfig { /** * Static prefix, e.g. `ORD-`. May be empty. Max 16 chars; allowed * characters: ascii alphanumerics, `-`, `_`. Internal prefixes * (`pay`, `cus`, ...) are rejected. */ prefix: string; /** Charset used for the random body of the id. */ style: PaymentIdStyle; /** Length of the random body (6..=32). */ length: number; } interface ShopCreateRequest { shop_name: string; return_url?: string | null; webhook_url?: string | null; project_id?: string | null; } interface ShopUpdateRequest { shop_name?: string | null; return_url?: string | null; webhook_url?: string | null; is_active?: boolean | null; /** Webhook signing secret for this shop. Outgoing webhooks are HMAC-SHA512 signed with this key. */ payment_response_hash_key?: string | null; payment_link_config?: BusinessPaymentLinkConfig | null; /** * Origins permitted to embed this shop's hosted checkout in an iframe. * Each entry must be a full origin (`scheme://host[:port]`, no path, * no wildcards). Empty/null defaults to same-origin only — strict * clickjacking defense. Example: `["https://shop.acme.com"]`. */ iframe_allowed_origins?: string[] | null; /** * Custom format for generated payment ids (cloaking), e.g. * `ORD-74219807`. `null` = default `pay_` ids. */ payment_id_format?: PaymentIdFormatConfig | null; /** * The shop's home country (ISO alpha-2) for cross-border analytics. * `null` = international / no home country (the default; on updates, * explicit `null` clears, absent leaves unchanged). */ home_country?: string | null; } interface ShopResponse { shop_id: string; shop_name: string; is_active: boolean; created_at: string; modified_at: string; return_url?: string | null; webhook_url?: string | null; project_id?: string | null; payment_link_config?: BusinessPaymentLinkConfig | null; /** * Origins permitted to embed this shop's hosted checkout in an iframe. * See {@link ShopUpdateRequest.iframe_allowed_origins}. */ iframe_allowed_origins?: string[] | null; /** * Custom format for generated payment ids (cloaking), e.g. * `ORD-74219807`. `null` = default `pay_` ids. */ payment_id_format?: PaymentIdFormatConfig | null; /** * The shop's home country (ISO alpha-2) for cross-border analytics. * `null` = international / no home country (the default; on updates, * explicit `null` clears, absent leaves unchanged). */ home_country?: string | null; } /** * Branding and behavior overrides for a shop's hosted checkout. * * On the wire the {@link PaymentLinkConfigRequest} fields sit at the TOP * LEVEL of `payment_link_config` (the backend flattens them), with the * fields declared below alongside them. */ interface BusinessPaymentLinkConfig extends PaymentLinkConfigRequest { /** Custom domain name used to host the link on the merchant's own domain. */ domain_name?: string | null; /** Per-sub-business overrides, keyed by a merchant-defined identifier. */ business_specific_configs?: Record | null; /** Host domains (glob patterns) the payment link may be embedded / opened from. */ allowed_domains?: string[] | null; /** Toggle for DeloPay branding visibility. */ branding_visibility?: boolean | null; } /** Appearance and behavior customization for the hosted checkout. */ interface PaymentLinkConfigRequest { /** Primary theme color (hex). */ theme?: string | null; /** Merchant logo URL shown on the checkout page. */ logo?: string | null; /** Display name shown to customers instead of the shop name. */ seller_name?: string | null; /** SDK layout: 'tabs' | 'accordion' | 'spaced_accordion'. */ sdk_layout?: string | null; display_sdk_only?: boolean | null; enabled_saved_payment_method?: boolean | null; hide_card_nickname_field?: boolean | null; show_card_form_by_default?: boolean | null; transaction_details?: PaymentLinkTransactionDetails[] | null; background_image?: PaymentLinkBackgroundImageConfig | null; details_layout?: 'layout1' | 'layout2' | null; payment_button_text?: string | null; custom_message_for_card_terms?: string | null; custom_message_for_payment_method_types?: Record | null; payment_button_colour?: string | null; skip_status_screen?: boolean | null; payment_button_text_colour?: string | null; background_colour?: string | null; /** CSS-variable overrides applied to the embedded SDK iframe. */ sdk_ui_rules?: Record> | null; /** CSS-variable overrides applied to the outer payment-link page. */ payment_link_ui_rules?: Record> | null; enable_button_only_on_form_ready?: boolean | null; payment_form_header_text?: string | null; payment_form_label_type?: 'above' | 'floating' | 'never' | null; show_card_terms?: 'always' | 'auto' | 'never' | null; is_setup_mandate_flow?: boolean | null; /** Hex color for the CVC icon during error state. */ color_icon_card_cvc_error?: string | null; } interface PaymentLinkTransactionDetails { key: string; value: string; ui_configuration?: { position?: number | null; is_key_bold?: boolean | null; is_value_bold?: boolean | null; } | null; } /** Response from `POST /shops/{merchantId}/{shopId}/logo`. */ interface ProfileLogoUploadResponse { /** Publicly-reachable HTTPS URL of the uploaded logo. */ logo_url: string; } /** * Event scope when registering a connector webhook. * - `'all_events'`: wildcard registration (cheapest default). * - `{ specific_event: '' }`: register only one event type. */ type ConnectorWebhookEventType = 'all_events' | { specific_event: string; }; /** * Which credential set a connector webhook registration targets. PSPs with * dual credential sets (e.g. Stripe) mint a separate endpoint + signing * secret per environment. */ type WebhookRegistrationEnvironment = 'live' | 'sandbox'; /** Body for `POST /account/{merchantId}/connectors/webhooks/{connectorId}`. */ interface ConnectorWebhookRegisterRequest { event_type?: ConnectorWebhookEventType; /** Credential set to register with. Defaults to `'live'`. */ environment?: WebhookRegistrationEnvironment; /** * Endpoint URL to register (plain `/webhooks/…` or obfuscated * `/webhooks_base/…` form). When omitted the server derives the canonical * plain form. Must route back to the connector account — the path has to * end with the `mca_…` id. */ webhook_url?: string; } interface ConnectorWebhookRegisterResponse { event_type: ConnectorWebhookEventType; environment: WebhookRegistrationEnvironment; connector_webhook_id: string | null; webhook_registration_status: 'success' | 'failure'; /** * Signing secret the connector minted for the new endpoint (e.g. Stripe's * `whsec_…`), when it returns one at creation. Already persisted into the * connector's webhook verification details server-side. */ webhook_secret?: string | null; error_code: string | null; error_message: string | null; } interface ConnectorWebhookEntry { event_type: ConnectorWebhookEventType; connector_webhook_id: string; /** Absent for registrations stored before environments were tracked (all live). */ environment?: WebhookRegistrationEnvironment; /** Callback URL the endpoint is registered to. Present when the list is * sourced live from the connector (e.g. Stripe); absent for entries * reconstructed from stored registration metadata only. */ webhook_url?: string; /** Endpoint status as the connector reports it (e.g. `enabled`/`disabled`). */ status?: string; /** Events the endpoint is subscribed to at the connector. */ enabled_events?: string[]; /** * Events Delopay handles that this endpoint is NOT subscribed to. A PSP * freezes an endpoint's event list at registration time, so an endpoint * registered before an event type was added keeps missing it — nothing * errors, the events simply never arrive. * * Empty means the subscription is current. Non-empty means those events are * being silently dropped; call * {@link Connectors.syncWebhookEvents | syncWebhookEvents} to fix it. * Absent when the subscription can't be inspected (non-Stripe connectors, or * a list reconstructed from stored metadata) — which is not the same as * "checked and healthy". */ missing_events?: string[]; /** Unix timestamp (seconds) the endpoint was created at the connector. */ created_at?: number; } interface ConnectorWebhookListResponse { connector: string; webhooks: ConnectorWebhookEntry[]; } /** One endpoint's outcome from `syncWebhookEvents`. */ interface ConnectorWebhookSyncResult { connector_webhook_id: string; environment?: WebhookRegistrationEnvironment; webhook_url?: string; /** Endpoint status as the connector reports it (e.g. `enabled`/`disabled`). */ status?: string; /** * Whether the subscription was actually rewritten. `false` with an empty * `added_events` means it was already current. */ updated: boolean; /** Events that were missing and have now been subscribed. */ added_events: string[]; /** Set when this endpoint could not be updated; the others are unaffected. */ error_message?: string; } interface ConnectorWebhookSyncResponse { connector: string; /** The full event set Delopay expects to be subscribed to. */ expected_events: string[]; /** * One entry per endpoint registered at this connector account, across every * credential set whose endpoints could be listed. */ endpoints: ConnectorWebhookSyncResult[]; } /** * Body for * `POST /account/{merchantId}/connectors/{connectorId}/stripe/payment-method-domains`. * * Registers the hosts of `urls` as Stripe *payment method domains* on the * connector's credential set for `environment`, so Apple Pay renders on those * pages (Stripe hides the button silently on unregistered domains). Call once * per environment to cover both Stripe modes. Stripe connectors only. */ interface StripePaymentMethodDomainsRegisterRequest { /** Credential set to register with. Defaults to `'live'`. */ environment?: WebhookRegistrationEnvironment; /** * Absolute URLs (or bare hostnames) whose hosts are registered — the hosted * checkout origin first, plus any of the merchant's own shop URLs. At most * 10 per request; duplicates by resolved host are collapsed server-side but * still receive their own result row. */ urls: string[]; } /** Outcome of registering one URL as a Stripe payment method domain. */ type StripePaymentMethodDomainStatus = 'registered' | 'already_registered' | 'invalid_url' | 'failed'; /** Per-URL outcome of a {@link StripePaymentMethodDomainsRegisterRequest}. */ interface StripePaymentMethodDomainResult { /** The URL exactly as sent. */ url: string; /** Host actually registered with Stripe; absent when the URL failed to parse. */ domain?: string | null; status: StripePaymentMethodDomainStatus; /** Stripe's `apple_pay.status` for the domain (`active`, `inactive`), when reported. */ apple_pay_status?: string | null; /** Human-readable failure or duplicate detail. Never contains credentials. */ message?: string | null; } interface StripePaymentMethodDomainsRegisterResponse { environment: WebhookRegistrationEnvironment; results: StripePaymentMethodDomainResult[]; } /** * Register checkout/shop domains as Airwallex Apple Pay domains. * * The Airwallex counterpart of {@link StripePaymentMethodDomainsRegisterRequest} * — Apple's requirement is the same for both processors, the API that satisfies * it is not. */ interface AirwallexApplePayDomainsRegisterRequest { /** Credential set to register with. Defaults to `'live'`. */ environment?: WebhookRegistrationEnvironment; /** * Absolute URLs (or bare hostnames) whose hosts are registered — the hosted * checkout origin first, plus any of the merchant's own shop URLs. At most 10 * per request. */ urls: string[]; } /** Outcome of registering one URL as an Airwallex Apple Pay domain. */ type AirwallexApplePayDomainStatus = 'registered' | 'already_registered' | 'invalid_url' | 'failed'; /** Per-URL outcome of an {@link AirwallexApplePayDomainsRegisterRequest}. */ interface AirwallexApplePayDomainResult { /** The URL exactly as sent. */ url: string; /** Host actually registered with Airwallex; absent when the URL failed to parse. */ domain?: string | null; status: AirwallexApplePayDomainStatus; /** * Human-readable failure detail. Never contains credentials. * * Airwallex validates the domain-association file when the domain is added, * so a failure here usually names a missing * `/.well-known/apple-developer-merchantid-domain-association`. */ message?: string | null; } interface AirwallexApplePayDomainsRegisterResponse { environment: WebhookRegistrationEnvironment; results: AirwallexApplePayDomainResult[]; /** * Every domain the account carries after the call, including any added from * Airwallex's own dashboard. Airwallex reports no per-domain Apple status, so * there is no counterpart to Stripe's `apple_pay_status`. */ registered_domains?: string[]; } interface PaymentLinkBackgroundImageConfig { url: string; position?: 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | null; size?: 'cover' | 'contain' | 'auto' | null; } /** Revenue taken in one currency. Amounts are in that currency's minor units. */ interface CurrencyRevenue { /** ISO 4217 code, e.g. `EUR`. */ currency: string; amount_minor: number; orders: number; } interface ShopStats { shop_id: string; shop_name: string; /** Project the shop is grouped under, or `null` when unassigned. */ project_id: string | null; orders: number; /** * @deprecated Raw sum of minor units across every currency, with no FX * conversion — meaningless for a multi-currency shop. Use `revenue_usd` * (FX-converted, USD **major** units) or `revenue_by_currency`. */ revenue: number; /** FX-converted revenue in USD major units. Excludes `unconverted_orders`. */ revenue_usd: number; /** Revenue split by the currency it was taken in, sorted by currency code. */ revenue_by_currency: CurrencyRevenue[]; /** * Orders left out of `revenue_usd` because their currency had no fresh USD * rate. Non-zero means the USD figure understates reality. */ unconverted_orders: number; } interface GatewayConnectRequest { connector_type: ConnectorType; connector_name: Connector; connector_label?: string | null; profile_id?: string | null; connector_account_details?: Record | null; payment_methods_enabled?: Record[] | null; metadata?: Record | null; test_mode?: boolean | null; disabled?: boolean | null; connector_webhook_details?: Record | null; additional_merchant_data?: Record | null; } interface GatewayResponse { connector_type: ConnectorType; connector_name: Connector; merchant_connector_id: string; profile_id: string; status: string; connector_label?: string | null; connector_account_details?: Record | null; payment_methods_enabled?: Record[] | null; metadata?: Record | null; test_mode?: boolean | null; disabled?: boolean | null; created_at?: string | null; } interface BillingProfileResponse { id: string; merchant_id: string; has_payment_method: boolean; balance_amount: number; balance_currency: string; billing_status: string; auto_recharge: AutoRechargeConfig; hard_floor_amount: number; consecutive_recharge_failures: number; stripe_customer_id?: string | null; suspended_at?: string | null; /** * Whether the merchant is on the trusted list. Trusted merchants are * exempt from automatic AND manual suspension until the flag is cleared. */ is_trusted: boolean; /** * When the trusted shield stops being honoured. Absent means it never * expires, which is what every profile did before expiry existed. */ trusted_until?: string | null; /** * Whether the trusted shield applies **right now** — `is_trusted` and not * past `trusted_until`. Derived server-side and read-only. * * Read this rather than `is_trusted` when deciding whether trust is in * force: `is_trusted` is the raw stored flag and says nothing about expiry, * so gating on it alone treats an expired shield as still active. */ trusted_effective: boolean; /** * What caused the current suspension: `auto_recharge` or `admin`. Absent * when the merchant is not suspended. An `admin` suspension is sticky — a * top-up will not auto-reactivate it; only an admin unsuspend lifts it. */ suspension_source?: string | null; /** Admin-provided reason for a manual suspension. Absent otherwise. */ suspension_reason?: string | null; /** * Free (not billed) merchant. While set, no platform fee is deducted on * any payment, the payment gate never blocks on `billing_status`, top-ups * are refused and auto-recharge is inert. Orthogonal to `is_trusted`, which * only shields against suspension. Absent on a router that predates the * flag; read absent as `false`. */ is_free?: boolean; /** Admin-provided reason the merchant is free. Absent when not free. */ free_reason?: string | null; /** When the free flag was set (ISO 8601). Absent when not free. */ free_set_at?: string | null; created_at: string; modified_at: string; /** * Volume-tier the merchant is currently locked to. Recomputed once * per calendar month from the previous month's processed volume in * USD. Omitted on legacy profiles created before tiers existed. */ current_tier?: TierSummary | null; } /** * The merchant's currently-locked platform-fee tier. Tier 6 ("Custom" / * Enterprise) sets `is_custom: true` and leaves `rate` unset — fees for * those merchants are deducted at the Tier 5 rate until Delopay writes * a negotiated per-merchant `FeeSchedule` override. */ interface TierSummary { /** 1 (Starter) through 6 (Enterprise). */ level: number; /** Human-readable tier name (e.g. "Growth"). */ name: string; /** Percentage as a decimal — `3.5` means 3.50%. `null` for custom tier. */ rate?: number | null; /** True when this is the custom (Tier 6) tier with no published rate. */ is_custom: boolean; /** ISO date (`YYYY-MM-DD`) the locked tier expires; next snapshot reruns. */ locked_until?: string | null; /** Volume in USD minor units (cents) that produced the current tier. */ volume_usd_minor?: number | null; } interface BillingSetupRequest { balance_currency?: string | null; } interface BillingSetupResponse { billing_profile_id: string; merchant_id: string; stripe_customer_id: string; setup_intent_id: string; setup_intent_client_secret: string; billing_status: string; } interface BillingCompleteSetupRequest { setup_intent_id: string; } interface TopupRequest { amount: number; /** * Client-supplied idempotency key. When set, the server passes it to * Stripe as `Idempotency-Key` on the PaymentIntent create, so a retry * with the same key is collapsed into a single charge. Generate a * fresh UUID per click on the dashboard; reuse across retries of the * same logical attempt. Optional — omitted requests behave the same * as historical SDK calls (no dedup). */ idempotency_key?: string; } interface TopupResponse { payment_intent_id: string; status: string; amount: number; currency: string; } interface LedgerEntry { id: string; entry_type: string; amount: number; currency: string; reference_kind: string; reference_id: string; balance_after: number; created_at: string; original_amount?: number | null; original_currency?: string | null; description?: string | null; profile_id?: string | null; } interface LedgerResponse { entries: LedgerEntry[]; total_count?: number | null; } interface LedgerListParams { limit?: number | null; offset?: number | null; profile_id?: string | null; } /** A payment attempt rejected by the billing suspension gate before any payment was created. */ interface BlockedAttempt { id: string; merchant_id: string; payment_id: string; /** `billing_suspended` | `billing_setup_incomplete` | `allocation_suspended`. */ block_reason: string; created_at: string; profile_id?: string | null; /** `auto_recharge` | `admin` — only set when the merchant was suspended. */ suspension_source?: string | null; amount?: number | null; currency?: string | null; billing_status?: string | null; } interface BlockedAttemptListResponse { entries: BlockedAttempt[]; total_count?: number | null; } interface BlockedAttemptListParams { limit?: number | null; offset?: number | null; profile_id?: string | null; /** `billing_suspended` | `billing_setup_incomplete` | `allocation_suspended`. */ block_reason?: string | null; /** ISO 8601 timestamp; only attempts at or after this time. */ created_after?: string | null; /** ISO 8601 timestamp; only attempts at or before this time. */ created_before?: string | null; } interface AutoRechargeUpdateRequest { enabled?: boolean | null; threshold_amount?: number | null; recharge_amount?: number | null; } interface AllocationTransferRequest { profile_id: string; amount: number; /** * Client-supplied idempotency key. When set, the server derives a * deterministic ledger row id from it so a retry collides on the * ledger UNIQUE index and the second call is treated as a * success-replay rather than a fresh transfer. Generate a fresh UUID * per click; reuse across retries of the same logical attempt. * Optional — omitted requests behave as before (no dedup). */ idempotency_key?: string; } interface AllocationTransferResponse { allocation_id: string; profile_id: string; allocation_balance: number; host_balance: number; ledger_entry_id: string; } interface AllocationResponse { id: string; merchant_id: string; profile_id: string; balance_amount: number; balance_currency: string; allocation_status: string; created_at: string; modified_at: string; } interface AllocationListResponse { allocations: AllocationResponse[]; } interface FeeScheduleCreateRequest { fee_type: FeeType; shop_id?: string | null; percentage_fee?: number | null; flat_fee_amount?: number | null; flat_fee_currency?: string | null; min_fee_amount?: number | null; max_fee_amount?: number | null; description?: string | null; } interface FeeScheduleUpdateRequest { fee_type?: FeeType | null; percentage_fee?: number | null; flat_fee_amount?: number | null; flat_fee_currency?: string | null; min_fee_amount?: number | null; max_fee_amount?: number | null; description?: string | null; is_active?: boolean | null; } interface FeeScheduleResponse { id: string; merchant_id: string; fee_type: FeeType; is_active: boolean; fee_owner: FeeOwner; shop_id?: string | null; percentage_fee?: number | null; flat_fee_amount?: number | null; flat_fee_currency?: string | null; min_fee_amount?: number | null; max_fee_amount?: number | null; description?: string | null; } /** * What a merchant cost rule records: the merchant's own goods, a partner's * share of the revenue, or any other cost of fulfilling a sale. The backend * refuses any other value on create. */ type MerchantCostRuleCategory = 'goods' | 'partner_share' | 'other'; /** * Record a merchant cost rule — `POST /cost-rules`. * * A percentage, a flat amount or both, merchant-wide or for one shop, from * `effective_from` (default: now) until `effective_to` (exclusive; default: * open-ended). Unknown fields are refused. */ interface CreateMerchantCostRuleRequest { category: MerchantCostRuleCategory; /** One shop. Omitted, the rule covers every shop. */ shop_id?: string | null; /** Percentage of the transaction, e.g. `42.5` for 42.5%. */ percentage?: number | null; /** Flat component in minor units of `flat_currency`. */ flat_amount?: number | null; /** ISO 4217 currency of the fixed components. Omitted, they are stated in the transaction currency. */ flat_currency?: string | null; /** Per-transaction floor. Lifts a priced cost; never creates one. */ min_amount?: number | null; /** Per-transaction ceiling. */ max_amount?: number | null; /** ISO 8601. When the rate starts applying. */ effective_from?: string | null; /** ISO 8601, exclusive. When the rate stops applying. */ effective_to?: string | null; /** What the merchant calls the rule: the supplier, the contract, the product line. */ label?: string | null; } /** A recorded merchant cost rule. */ interface MerchantCostRuleResponse { id: string; merchant_id: string; /** Absent when the rule covers every shop. */ shop_id?: string | null; /** A {@link MerchantCostRuleCategory}. Typed `string` so a category a newer backend adds still reads. */ category: string; percentage?: number | null; flat_amount?: number | null; flat_currency?: string | null; min_amount?: number | null; max_amount?: number | null; /** ISO 8601. */ effective_from: string; /** ISO 8601, exclusive. Absent while the rule is open-ended. */ effective_to?: string | null; is_active: boolean; label?: string | null; } /** * Close, deactivate or rename a merchant cost rule — `PUT /cost-rules/{rule_id}`. * * The rate, the category and the scope cannot be edited: a price change is a * new rule with a new window, so earlier periods keep the rate they were * recorded under. A closed window cannot be extended or reopened. */ interface UpdateMerchantCostRuleRequest { /** ISO 8601, exclusive. Closes the window at this instant. */ effective_to?: string | null; is_active?: boolean | null; /** Omitted leaves the label alone, `null` clears it, a string replaces it. */ label?: string | null; } /** The answer to `DELETE /cost-rules/{rule_id}`. */ interface MerchantCostRuleDeleteResponse { deleted: boolean; id: string; } /** * Record what a processor charges — `POST /processor-costs` for the * merchant's own contract, `POST /admin-portal/processor-costs` for * DeloPay's. * * A schedule prices the rails that report no fee on the payment itself: the * cost it produces is stamped `estimated` on the settlement line, never * `reported`. Scope narrows from the connector downwards — every scope field * omitted, the rate covers every shop, method and network of that connector. * * Unknown fields are refused. */ interface CreateProcessorCostScheduleRequest { /** * Connector the rate belongs to (e.g. `stripe`, `epayouts`). Required: a * cost is a rail's cost, and an unscoped one would price every rail alike. */ connector: string; /** One shop. Omitted, the schedule covers every shop. */ shop_id?: string | null; /** Payment-method scope (e.g. `card`, `wallet`). Omitted, any method. */ payment_method?: string | null; /** Card-network scope (e.g. `Visa`, `Amex`). Omitted, any network. */ card_network?: string | null; /** * Payment-method-type scope (e.g. `local_bank_transfer`, `paypal`). * Omitted, any type. */ payment_method_type?: string | null; /** * The processor's own method identifier, for rails where the payment method * type cannot tell two methods apart: the e-Payouts vendor code (e.g. * `spmxspei`). Accepted only for connectors that report one; omitted, any * method. */ connector_method_code?: string | null; /** The processor's own commission, in percent (e.g. `2.9` for 2.9%). */ percentage?: number | null; /** * The underlying provider's fee the processor passes on, in percent. Added * to `percentage`; the two together may not exceed 100. */ provider_fee_percentage?: number | null; /** Flat component in minor units of `flat_currency`. */ flat_amount?: number | null; /** * ISO 4217 currency the fixed components are quoted in. Omit to state them * in the transaction currency; set and different, they are converted at the * daily reference rate, and a missing or stale rate leaves the cost * unavailable rather than approximating it. */ flat_currency?: string | null; /** Per-transaction floor. Lifts a priced cost; never creates one. */ min_amount?: number | null; /** Per-transaction ceiling. */ max_amount?: number | null; /** ISO 8601. When the rate starts applying. Defaults to now. */ effective_from?: string | null; /** ISO 8601, exclusive. When it stops. Omitted, open-ended. */ effective_to?: string | null; /** Free-text note — which contract or clause this rate came from. */ description?: string | null; } /** A recorded processor cost schedule. */ interface ProcessorCostScheduleResponse { id: string; /** Absent on a DeloPay-wide default. */ merchant_id?: string | null; shop_id?: string | null; connector: string; payment_method?: string | null; card_network?: string | null; payment_method_type?: string | null; /** The processor's own method identifier this rate is scoped to. */ connector_method_code?: string | null; /** The processor's own commission, in percent. */ percentage?: number | null; /** The underlying provider's fee, in percent, additive to `percentage`. */ provider_fee_percentage?: number | null; flat_amount?: number | null; flat_currency?: string | null; min_amount?: number | null; max_amount?: number | null; /** ISO 8601. */ effective_from: string; /** ISO 8601, exclusive. Absent while the schedule is open-ended. */ effective_to?: string | null; is_active: boolean; description?: string | null; } /** * Close, deactivate or annotate a processor cost schedule — * `PUT /processor-costs/{schedule_id}`. * * The rate itself is deliberately not editable: it priced transactions that * already happened, and an edited rate leaves the table unable to explain the * figures those produced. A rate change is a new row — see * {@link ReplaceProcessorCostScheduleRequest}, which opens it without leaving * a gap. Reopening a closed window is not offered either. */ interface UpdateProcessorCostScheduleRequest { /** ISO 8601, exclusive. Closes the window at this instant. */ effective_to?: string | null; is_active?: boolean | null; description?: string | null; } /** * Record several schedules at once, all or none — * `POST /processor-costs/bulk`. For copying a price list across methods or * shops. * * At most 200 entries, at least one. Every entry is validated before anything * is stored, and one invalid entry stores none: the `422` names the offending * index. */ interface BulkCreateProcessorCostSchedulesRequest { /** 1 to 200 entries. */ schedules: CreateProcessorCostScheduleRequest[]; } /** * Replace a schedule's rate from an instant on — * `POST /processor-costs/{schedule_id}/replace`. * * Closes the old window at `effective_from` and opens a successor with the * same scope and the same end, in one step: no transaction is priced by both * rates, and none falls between them. The answer is the successor. */ interface ReplaceProcessorCostScheduleRequest { /** The processor's own commission, in percent. */ percentage?: number | null; /** The underlying provider's fee, in percent, additive to `percentage`. */ provider_fee_percentage?: number | null; /** Flat component in minor units of `flat_currency`. */ flat_amount?: number | null; /** ISO 4217 currency the fixed components are quoted in. */ flat_currency?: string | null; /** Per-transaction floor. Lifts a priced cost; never creates one. */ min_amount?: number | null; /** Per-transaction ceiling. */ max_amount?: number | null; /** * ISO 8601. When the new rate starts and the old one stops. Defaults to * now. Must fall after the old rate's start and before its end — outside * that window, and on an inactive or concurrently replaced schedule, the * backend answers `400`. */ effective_from?: string | null; /** Free-text note for the new rate. */ description?: string | null; } /** Euclid comparison operator. */ type EuclidComparisonType = 'equal' | 'not_equal' | 'less_than' | 'less_than_equal' | 'greater_than' | 'greater_than_equal'; /** A tagged Euclid value. Enum conditions use `enum_variant`; amount uses `number`. */ type EuclidValue = { type: 'number'; value: number; } | { type: 'enum_variant'; value: string; } | { type: 'str_value'; value: string; } | { type: 'number_array'; value: number[]; } | { type: 'enum_variant_array'; value: string[]; } | { type: 'metadata_variant'; value: { key: string; value: string; }; } | { type: 'number_comparison_array'; value: { comparisonType: EuclidComparisonType; number: number; }[]; }; /** * A single condition. `lhs` is a backend dimension key — e.g. `payment_method`, * `connector`, `currency`, `card_network`, `amount`, or a payment-method-type key * (`crypto`, `wallet`, `bank_redirect`, …). camelCase on the wire. */ interface EuclidComparison { lhs: string; comparison: EuclidComparisonType; value: EuclidValue; metadata: Record; } /** An IF block; all comparisons in `condition` are ANDed. camelCase on the wire. */ interface EuclidIfStatement { condition: EuclidComparison[]; nested?: EuclidIfStatement[] | null; } /** How a matched rule prices the transaction. snake_case on the wire. */ type PlatformFeeKind = 'percentage' | 'flat' | 'combined'; interface PlatformFeeOutput { fee_type: PlatformFeeKind; /** Percentage fee, e.g. `2.5` means 2.5%. */ percentage_fee?: number | null; /** Flat fee in minor units. */ flat_fee_amount?: number | null; flat_fee_currency?: string | null; min_fee_amount?: number | null; max_fee_amount?: number | null; } /** The Euclid program output `O`: a matched branch may set a concrete `fee`. */ interface PlatformFeeRuleOutput { fee?: PlatformFeeOutput | null; } /** A single fee rule (`Rule`). camelCase on the wire. */ interface PlatformFeeRule { name: string; connectorSelection: PlatformFeeRuleOutput; statements: EuclidIfStatement[]; } /** The fee-rule program (`Program`). camelCase on the wire. */ interface PlatformFeeProgram { defaultSelection: PlatformFeeRuleOutput; rules: PlatformFeeRule[]; /** Required on the wire — send `{}` when empty. */ metadata: Record; } /** * What a program's `defaultSelection` means when no rule matched. * * `authoritative` (the default, and what an absent field means): the default's * fee is charged and the per-connector fee schedule / volume tier are not * consulted. `fall_through`: a transaction matching no rule is priced by the * schedule/tier chain instead, even when the default carries a fee. */ type FeeRuleDefaultPrecedence = 'authoritative' | 'fall_through'; /** Full request body. `fee_owner` is injected by the SDK per surface. */ interface PlatformFeeRuleRequest { algorithm: PlatformFeeProgram; /** Defaults to `authoritative` server-side. */ default_precedence?: FeeRuleDefaultPrecedence | null; name?: string | null; profile_id?: string | null; fee_owner: FeeOwner; /** RFC3339 timestamp; defaults to now server-side. */ valid_from?: string | null; /** RFC3339 timestamp; open-ended when unset. */ valid_until?: string | null; } /** User-facing input; the resource method adds `fee_owner`. */ type PlatformFeeRuleInput = Omit; /** The stored program. `created_at`/`modified_at` are unix-second integers. */ interface PlatformFeeRuleRecord { name: string; fee_owner: FeeOwner; algorithm: PlatformFeeProgram; /** Absent on programs stored before this field existed ⇒ `authoritative`. */ default_precedence?: FeeRuleDefaultPrecedence; created_at: number; modified_at: number; } /** Sample transaction + candidate program for `POST /admin/fees/rules/preview`. */ interface FeeRulePreviewRequest { algorithm: PlatformFeeProgram; /** Preview honours this, so a `fall_through` program previews the fall-through. */ default_precedence?: FeeRuleDefaultPrecedence | null; /** Amount in minor units. */ amount: number; currency: Currency; payment_method?: PaymentMethod | null; connector?: Connector | null; card_network?: string | null; /** USD minor units (previous-month volume snapshot). */ merchant_volume?: number | null; } interface FeeRulePreviewResponse { /** Matched rule name, or null when only the default selection matched. */ matched_rule: string | null; /** True when the matched branch sets no concrete fee (engine falls through to the legacy schedule/tier chain). */ fell_through: boolean; /** Computed fee in minor units, or null when fell_through. */ fee_amount: number | null; fee_currency: string | null; } interface ProjectCreateRequest { project_name: string; description?: string | null; } interface ProjectUpdateRequest { project_name?: string | null; description?: string | null; is_active?: boolean | null; } interface ProjectResponse { id: string; merchant_id: string; project_name: string; is_active: boolean; created_at: string; modified_at: string; description?: string | null; } interface ProjectStats { project_id: string; project_name: string; orders: number; /** @deprecated Cross-currency raw sum. Use `revenue_usd` / `revenue_by_currency`. */ revenue: number; revenue_usd: number; revenue_by_currency: CurrencyRevenue[]; unconverted_orders: number; shops: ShopStats[]; } interface ProjectStatsResponse { projects: ProjectStats[]; /** * Every shop of the merchant, including shops that belong to no project — * those never appear under `projects[].shops[]`, since projects are an * optional grouping layer. Look a single shop up here rather than walking * the project tree. */ shops: ShopStats[]; total_orders: number; /** @deprecated Cross-currency raw sum. Use `total_revenue_usd`. */ total_revenue: number; total_revenue_usd: number; total_revenue_by_currency: CurrencyRevenue[]; unconverted_orders: number; /** Window the figures cover, in days. `null` when `period: 'all'` was asked for. */ period_days: number | null; /** * The merchant has analytics exclusions configured that touch conversion or * volume, so the totals above may be filtered (best-effort matching over 95 * days). * * Deliberately conservative and deliberately cheap to read: it says only * that rules exist, **not** that any payment in this shop or this period * matched one, and it needs no rule-view permission — so a read-only user * can still be told the figures are filtered without being shown by what. */ analytics_exclusions_active: boolean; } /** Stats for one shop, addressed by shop id. Loadable by a shop-scoped user. */ interface ShopStatsResponse extends ShopStats { /** Window the figures cover, in days. `null` for an all-time total. */ period_days: number | null; /** * The merchant has analytics exclusions configured that touch conversion or * volume, so the totals above may be filtered (best-effort matching over 95 * days). * * Deliberately conservative and deliberately cheap to read: it says only * that rules exist, **not** that any payment in this shop or this period * matched one, and it needs no rule-view permission — so a read-only user * can still be told the figures are filtered without being shown by what. */ analytics_exclusions_active: boolean; } /** * Window for a stats query: a number of days (clamped 1..=365 server-side) or * `'all'` for an all-time total. Omitted means the server default, 30 days. */ type StatsPeriod = number | 'all'; interface MerchantOverviewStat { label: string; value: number; change_percent: number; } interface MerchantOverviewResponse { total_shops: MerchantOverviewStat; active_shops: MerchantOverviewStat; } interface ApiKeyCreateRequest { name: string; expiration: ApiKeyExpiration; description?: string | null; } interface ApiKeyUpdateRequest { name?: string | null; description?: string | null; expiration?: ApiKeyExpiration | null; } /** Returned only on creation — includes the plaintext api_key */ interface ApiKeyCreateResponse { key_id: string; merchant_id: string; name: string; api_key: string; created: string; expiration: ApiKeyExpiration; description?: string | null; /** * The shop (business profile) this key is pinned to, or `null`/absent for * merchant-wide keys. Only populated by backends with profile-scoped API * key support; absent on older backends. */ profile_id?: string | null; } /** Returned on retrieve/list — no plaintext key, only prefix */ interface ApiKeyResponse { key_id: string; merchant_id: string; name: string; prefix: string; created: string; expiration: ApiKeyExpiration; description?: string | null; /** * The shop (business profile) this key is pinned to, or `null`/absent for * merchant-wide keys. Only populated by backends with profile-scoped API * key support; absent on older backends. */ profile_id?: string | null; } interface ApiKeyRevokeResponse { merchant_id: string; key_id: string; revoked: boolean; } /** Pagination constraints for listing API keys. */ interface ApiKeyListConstraints { /** Maximum number of keys to return. */ limit?: number | null; /** Number of keys to skip (offset). */ skip?: number | null; } interface EphemeralKeyCreateRequest { customer_id: string; } interface EphemeralKeyCreateResponse { customer_id: string; created_at: number; expires: number; secret: string; } /** Any OFF is final; inherit removes this scope's opinion. */ type BrowserInfoScopeSetting = 'inherit' | 'on' | 'off'; /** A single scope's stored setting, not the effective collection policy. */ interface CheckoutBrowserInfoScopeResponse { setting: BrowserInfoScopeSetting; /** Unreadable or unrecognized storage is treated as a refusal. */ degraded: boolean; } interface UpdateCheckoutBrowserInfoScopeRequest { setting: BrowserInfoScopeSetting; } interface MerchantCheckoutBrowserInfoResponse { /** Whether the checkout should collect the five governed device fields. */ collect: boolean; merchant_setting: BrowserInfoScopeSetting; /** Admin merchant refusal only; connector policy is resolved later at delivery. */ blocked_by_admin: boolean; degraded: boolean; } /** Core checkout envelope fields; provider-specific fields remain available as unknown. */ interface PaymentLinkDetails { [key: string]: unknown; amount: string; attempt_count: number; currency: Currency; pub_key: string; client_secret: string; payment_id: string; session_expiry: string; merchant_logo: string; cancel_url?: string | null; merchant_name: string; max_items_visible_after_collapse: number; theme: string; sdk_layout: string; display_sdk_only: boolean; hide_card_nickname_field: boolean; show_card_form_by_default: boolean; status: IntentStatus; enable_button_only_on_form_ready: boolean; /** * Collect screen height/width, time zone and Java/JavaScript flags on confirm. * Missing means false for older payloads. A connector-level refusal may still * prevent delivery; language, headers, IP, color depth and referer are outside this setting. */ collect_browser_info?: boolean; } /** Core checkout envelope fields; provider-specific fields remain available as unknown. */ interface PaymentLinkStatusDetails { [key: string]: unknown; amount: string; currency: Currency; payment_id: string; merchant_logo: string; cancel_url?: string | null; merchant_name: string; created: string; status: IntentStatus | 'active' | 'expired'; redirect: boolean; theme: string; replay_masking_rules: { selector: string; mode: 'mask' | 'block' | 'mask_text'; }[]; sdk_layout: string; } /** Payable checkout or terminal/expired status; both are successful HTTP responses. */ type CheckoutDataResponse = (PaymentLinkDetails & { type: 'checkout'; }) | (PaymentLinkStatusDetails & { type: 'status'; }); /** Provider-hosted card form configuration from `PaymentLinkDetails.nomupay_oppwa`. */ interface NomupayOppwaCheckoutData { checkout_id: string; /** Per-checkout Subresource Integrity value returned by OPPWA. */ integrity: string; /** Server-selected widget URL; preserve its test or live host. */ widget_url: string; /** DeloPay return endpoint used as the COPYandPAY form action. */ shopper_result_url: string; /** * OPPWA identifiers for the form's `data-brands` attribute: the card * networks enabled on the account, in the provider's spelling. */ brands: string[]; /** * The account's OPPWA entity id, which Google Pay's sheet takes as * `gatewayMerchantId`. Not a secret. Absent on a payload rendered by a * router release before wallets. */ entity_id?: string | null; /** Google Pay sheet configuration read off the account. */ google_pay?: NomupayOppwaGooglePay | null; /** Apple Pay sheet configuration read off the account. */ apple_pay?: NomupayOppwaApplePay | null; /** * `true` when the widget may submit a card on this checkout only after * `CheckoutSession.nomupayCardAuthorization` answers `authorized: true`: the * card form sits beside always-shown wallet buttons of another account, * which that answer closes first. Such a checkout takes card brands alone. * Absent otherwise. */ submit_grant?: boolean | null; } /** Google Pay sheet configuration for the COPYandPAY widget. */ interface NomupayOppwaGooglePay { /** Google's merchant id; required in production, ignored in the test environment. */ merchant_id?: string | null; merchant_name?: string | null; } /** Apple Pay sheet configuration for the COPYandPAY widget. */ interface NomupayOppwaApplePay { /** The business name on the sheet's total line. */ label: string; } interface PaymentLinkResponse { link: string; payment_link_id: string; secure_link?: string | null; } interface PaymentLinkListParams { limit?: number | null; created?: string | null; 'created.lt'?: string | null; 'created.gt'?: string | null; 'created.lte'?: string | null; 'created.gte'?: string | null; } interface PaymentLinkListResponse { size: number; data: PaymentLinkResponse[]; } interface RoutingConfigCreateRequest { name?: string | null; description?: string | null; /** Prefer `StaticRoutingAlgorithm`; the raw-record escape hatch is kept for forward compat. */ algorithm?: StaticRoutingAlgorithm | Record | null; profile_id?: string | null; transaction_type?: TransactionType | null; } /** * Body for `PUT /routing/{id}` — partial edit of a static routing config. * Every field is optional; only the ones present are changed. * `algorithm` is a wholesale rule replacement (same shape as create), not a * partial merge — omit it for a rename/description-only edit. */ interface RoutingConfigUpdateRequest { name?: string | null; description?: string | null; /** Replacement rule. Validated against the shop exactly as at create. */ algorithm?: StaticRoutingAlgorithm | Record | null; } /** Outcome of `DELETE /routing/{id}` — the configuration is hidden, its history kept. */ interface RoutingConfigDeleteResponse { /** The configuration that was deleted. */ id: string; /** Profile the configuration belonged to. */ profile_id: string; /** Always `true` on a successful response; a refused delete is an error. */ deleted: boolean; } /** What kind of rule a routing configuration holds. */ type RoutingAlgorithmKind = 'single' | 'priority' | 'volume_split' | 'advanced' | 'dynamic' | 'three_ds_decision_rule'; /** * One content window of a routing configuration — the rule as it stood between * `valid_from` and `valid_until`. * * A config's rule can be edited in place, so the rule that decided a past * payment is only recoverable because every earlier version is kept. */ interface RoutingConfigVersion { /** 1-based position in the configuration's timeline, oldest first. */ version: number; /** Name the configuration had during this window. */ name: string; /** Description it had during this window. */ description: string; kind: RoutingAlgorithmKind; /** The rule itself, exactly as it was during this window. */ algorithm: StaticRoutingAlgorithm | Record; /** When this content took effect. Seconds since epoch — not milliseconds. */ valid_from: number; /** When it was replaced, or absent while it is still the live rule. */ valid_until?: number | null; } /** Query for `GET /routing/{id}/history`. */ interface RoutingHistoryParams { /** * Maximum entries to return, **counting the live one**. `0` is treated as `1`. */ limit?: number | null; /** Entries to skip, counting from the oldest. Advance it by `limit`. */ offset?: number | null; } /** * One page of a routing configuration's timeline, oldest first. * * Paging runs over the whole timeline with the live window as its last element, * so `versions` never holds more than `limit` entries and the live window — the * only one without a `valid_until` — appears on exactly one page and not on the * pages before or after it. A page past the end of the timeline is empty. A * configuration nobody has edited returns a single entry, the live one. */ interface RoutingConfigHistoryResponse { id: string; profile_id: string; versions: RoutingConfigVersion[]; /** * How long the whole timeline is: every closed window plus the live one. * * Lets you compute the last page directly rather than paging until a response * comes back empty. */ total_count: number; } /** * What a connector cap counts. * * `payments` — successful payments, one each — is the original unit and the * default. `amount` counts money, in the cap's own `currency` and in that * currency's minor units. "At most 1" and "at most 10000" are the same sentence * in different units, so a budget read as a payment count is wrong by orders of * magnitude in whichever direction the reader guessed. */ type ConnectorCapUnit = 'payments' | 'amount'; /** * How often a connector cap's counter starts again. * * `lifetime` — once, forever, with nothing ever giving the capacity back — is * the original window and the default. The calendar windows are UTC buckets: * the day, the ISO week (starting Monday) and the month the payment was spent * in. * * A lifetime cap read as a monthly one is wrong in the expensive direction: the * reader expects traffic to return to the account next month, and it never * does. Anything that renders a cap has to say which window it has. */ type ConnectorCapWindow = 'lifetime' | 'daily' | 'weekly' | 'monthly'; /** The part of a connector cap that does not depend on its unit. */ interface RoutingConnectorCapFields { /** * The connector *account*, not the acquirer. A shop with two Stripe accounts * has two of these, and a cap on one is not spent by the other. */ merchant_connector_id: string; /** * How much this account may take for this shop in one `window`, counted in * the cap's `unit`: successful payments, or `currency`'s minor units. `0` * means never route here, in either unit. */ limit: number; /** How often the counter starts again. Omitted in a write, it is `lifetime`. */ window?: ConnectorCapWindow; /** * How much of the current window the account has taken, in the cap's `unit`. * Present on reads only; ignored in a write. * * Absent when the router could not read the figure: treat that as unknown, * not as zero, because the cap may already have fired. An amount cap is also * reported without it when a payment inside the window could not be priced, * since a sum with a missing term is only a lower bound. A checkout still at * the bank has taken nothing yet; it is counted in `reserved`. */ used?: number | null; /** * How much of the cap payments still in flight hold, in the cap's `unit`. * Present on reads only; ignored in a write. * * Routing stops selecting the account once `used + reserved` reaches * `limit`, so a cap can be closed to new payments while `used` is still below * it. The capacity comes back if the checkout never completes. */ reserved?: number | null; } /** * A cap counted in successful payments. * * `limit: 1` over the `lifetime` window is the acquirer-onboarding case: a new * account takes the single live transaction its review needs, then routing * stops selecting it and traffic returns to the account that was there before. * Nothing resets a lifetime cap. */ interface RoutingConnectorPaymentCap extends RoutingConnectorCapFields { /** Omitted in a write, it is `payments`, which is what a cap written before units existed means. */ unit?: 'payments'; /** A payment count has no currency. The router refuses a write that gives it one. */ currency?: null; } /** * A budget: a cap counted in money. `limit: 50000` with `currency: 'GBP'` is * £500.00 per `window`. Payments in other currencies are converted before they * are compared with it. */ interface RoutingConnectorAmountCap extends RoutingConnectorCapFields { unit: 'amount'; /** * The currency `limit`, `used` and `reserved` are counted in, in its minor * units. Required: the router refuses an amount cap without one. */ currency: Currency; } /** * One connector account's cap for a shop: at most `limit` per `window`, in the * cap's `unit`. * * Narrow on `unit` before reading `limit`. A cap whose `unit` is absent is a * payment count, which is how a router that predates units reports every cap. */ type RoutingConnectorCap = RoutingConnectorPaymentCap | RoutingConnectorAmountCap; /** A shop's connector caps. */ interface RoutingConnectorCaps { /** The shop. Ignored in a request body — the path names the shop. */ profile_id?: string | null; /** * Every capped connector account. This list *is* the complete set: sending an * empty one removes every cap, which is how onboarding finishes. */ caps: RoutingConnectorCap[]; } /** * The span a `routing_volume` counter aggregates over. * * The calendar windows are UTC buckets — the day, ISO week (Monday-based) and * month a payment was charged in — so "£500 a week" is a week everyone agrees * on rather than seven days counted from whenever the question is asked. The * rolling windows are that other thing: the last 7 or 30 UTC days *including * today*. Their history begins with the release that introduced them: for the * first 30 days after it a `rolling_30d` figure is a lower bound, not a total. * * Set per advanced program in `Program.metadata["volume_window"]`, and per rule * in a `routing_volume` condition's own `metadata`. */ type RoutingVolumeWindow = 'monthly' | 'weekly' | 'daily' | 'rolling_7d' | 'rolling_30d'; /** * What a `routing_volume` counter aggregates *over*. `profile` is one shared * counter per shop; `payment_method` segments it by the payment's own method, * so a rule that pairs a `payment_method` condition with a `routing_volume` * threshold gets an independent budget per method. */ type RoutingVolumeScope = 'profile' | 'payment_method'; /** * One live `routing_volume` counter the shop's active program reads. * * This is **what routing will use right now**, not what the shop turned over: * the counters are held in Redis only, a lost Redis restarts the window at * zero, and a payment whose amount could not be priced in USD was never * counted. Turnover is the billing ledger's to answer. */ interface RoutingVolumeCounter { /** The span this counter aggregates over. */ window: RoutingVolumeWindow; /** Whether this is the shop-wide counter or one payment method's. */ scope: RoutingVolumeScope; /** Under `payment_method` scope, the method this counter is segmented by. Absent under `profile` scope. */ payment_method?: PaymentMethod | null; /** Start of the current window, UTC, inclusive (ISO 8601). */ window_start: string; /** End of the current window, UTC, exclusive — the moment the counter starts again at zero. */ window_end: string; /** The counter as stored: USD minor units, normalised at the rate of the day each payment was charged. */ amount_usd: number; /** * The currency the rules' thresholds are written in, when the program pins * one. Absent when thresholds are read in each payment's own currency — * there is then no single figure to compare against. */ threshold_currency?: Currency | null; /** * `amount_usd` in `threshold_currency`'s minor units at the current rate — * the number the rule compares its threshold to. Absent when no currency is * pinned or no usable rate exists; never a zero standing in for either. */ amount_in_threshold_currency?: number | null; /** The rules whose `routing_volume` conditions read this counter, by name. */ rules: string[]; } /** The live `routing_volume` counters behind a shop's active advanced program. */ interface RoutingVolumeCounters { /** The shop the counters belong to. */ profile_id: string; /** * The active advanced program the counters were resolved from. Absent when * the shop has no active program or it is not an advanced one. */ routing_algorithm_id?: string | null; /** When the counters were read, UTC (ISO 8601). The figures are a snapshot: the next charged payment moves them. */ read_at: string; /** * One entry per counter the program reads. Empty when no rule conditions on * `routing_volume`. Under `payment_method` scope only methods that have * charged something in the window appear — a method with no entry is at * zero. */ counters: RoutingVolumeCounter[]; } /** Body for `POST /routing/{id}/activate`. */ interface RoutingActivatePayload { transaction_type?: TransactionType | null; } /** Body for `POST /routing/deactivate`. */ interface RoutingDeactivateRequest { name?: string | null; description?: string | null; algorithm?: Record | null; profile_id?: string | null; transaction_type?: TransactionType | null; } /** Routable connector choice — used by Priority and as a member of VolumeSplit. */ interface RoutableConnectorChoice { connector: string; merchant_connector_id?: string | null; } interface ConnectorVolumeSplit { connector: RoutableConnectorChoice; /** Percentage weight. All splits in one selection must sum to exactly 100 (server-validated). */ split: number; } /** * Connector-selection leaf of an advanced routing rule: an ordered priority * list or a weighted volume split. snake_case `{type, data}` on the wire. * * Volume splits are drawn per payment via weighted random — the ratio converges * statistically over volume; it is not an exact quota. */ type ConnectorSelection = { type: 'priority'; data: RoutableConnectorChoice[]; } | { type: 'volume_split'; data: ConnectorVolumeSplit[]; }; /** * A single advanced-routing rule (`Rule`). camelCase on the * wire, like the fee-rule tree (see the note above `EuclidComparisonType`). * Conditions use the Euclid dimension keys, e.g. `payment_method`, `amount` * (minor units), `currency`, `card_network`. */ interface RuleConnectorSelection { name: string; connectorSelection: ConnectorSelection; statements: EuclidIfStatement[]; } /** * The advanced-routing program (`Program`) carried by * `{ type: 'advanced' }`. Rules are evaluated top-down, first match wins; * `defaultSelection` applies when no rule matches. Every referenced connector * must be an enabled connector (MCA) of the target profile. */ interface ProgramConnectorSelection { defaultSelection: ConnectorSelection; rules: RuleConnectorSelection[]; /** Required on the wire — send `{}` when empty. */ metadata: Record; } /** Static routing algorithm shape: `{type, data}` adjacently-tagged enum. */ type StaticRoutingAlgorithm = { type: 'single'; data: RoutableConnectorChoice; } | { type: 'priority'; data: RoutableConnectorChoice[]; } | { type: 'volume_split'; data: ConnectorVolumeSplit[]; } | { type: 'advanced'; data: ProgramConnectorSelection; } | { type: 'three_ds_decision_rule'; data: Record; }; /** Metadata record returned by list / create / activate / deactivate. No `algorithm` field. */ interface RoutingDictionaryRecord { id: string; profile_id: string; name: string; kind: string; description: string; /** Milliseconds since epoch. */ created_at: number; /** Milliseconds since epoch. */ modified_at: number; algorithm_for: TransactionType | null; decision_engine_routing_id?: string | null; } /** Response for `GET /routing/connector-restrictions/{profileId}`. */ interface ProfileDeniedConnectorsResponse { /** Connector names not routed for this shop (e.g. "paypal"). */ denied_connectors: string[]; } /** * A buyer-facing adjustment to the checkout amount. * * Either a flat amount in minor units, or a percentage of the order amount. * **Signed**: a negative value is a provider discount credited to the buyer, * not a charge. A rate discount steeper than -100% is rejected by the router; * a fixed discount larger than the order is clamped to the order amount. */ type MethodSurcharge = { fixed: { amount: number; }; } | { rate: { percent: number; }; }; /** What a {@link SurchargeCondition} reads off the payment. */ type SurchargeConditionSource = 'metadata' | 'currency' | 'amount'; /** * Operators a metadata condition admits. Metadata values are compared as * strings by the rule engine, so there is no ordering and no list form. */ type SurchargeMetadataOperator = 'equals' | 'not_equals'; /** Operators a currency condition admits. `in` / `not_in` take a list. */ type SurchargeCurrencyOperator = SurchargeMetadataOperator | 'in' | 'not_in'; /** Operators an order-amount condition admits. Ordering is amount-only. */ type SurchargeAmountOperator = SurchargeMetadataOperator | 'greater_than' | 'greater_than_or_equal' | 'less_than' | 'less_than_or_equal'; /** * Every operator the rule engine can evaluate inside one AND-ed condition. * * The union of the three source-specific sets above. Which of them a given * condition may use is decided by its source, and {@link SurchargeCondition} * enforces that at compile time — this alias exists for code that handles all * of them uniformly (an operator picker, a label map), not as the type of a * condition's `operator` field. */ type SurchargeOperator = SurchargeCurrencyOperator | SurchargeAmountOperator; /** * The value to compare against, as a string in every case: an ISO currency * code, a minor-unit integer, a metadata value, or a comma-separated list for * `in` / `not_in`. The `source` decides how the router reads it. */ interface SurchargeConditionValue { value: string; } /** * One extra condition a surcharge rule must satisfy, beyond its connector and * method scope. Conditions AND with the scope and with each other, so two * conditions on the same source express a range. * * Deliberately the same three sources the checkout's custom fields condition on * ({@link CustomFieldConditionSource}) — merchants already express "this is a * digital order" / "this is over EUR 500" that way. * * Modelled as a discriminated union rather than three loose fields because the * router rejects the invalid combinations with a 400 that names the DSL, not * the row: a metadata condition with no key has nothing to read, an ordering * operator on a string compares nothing, and `in` on an amount is not a * comparison the engine has. A rule set is money added to a buyer's total, so * those belong in the type rather than in a runtime error. */ type SurchargeCondition = (SurchargeConditionValue & { source: 'metadata'; /** The metadata key to read. Required — there is nothing to compare without it. */ key: string; operator: SurchargeMetadataOperator; }) | (SurchargeConditionValue & { source: 'currency'; /** Meaningless for this source; omit it, or send an explicit `null`. */ key?: null; operator: SurchargeCurrencyOperator; }) | (SurchargeConditionValue & { source: 'amount'; /** Meaningless for this source; omit it, or send an explicit `null`. */ key?: null; operator: SurchargeAmountOperator; }); /** * One surcharge entry. Every scope field is optional and they AND together, so * this expresses "Stripe, any method", "card on any provider" (the pre-#196 * shape), "Stripe card only", and — with `conditions` — "Stripe card over * EUR 500". */ interface PerMethodSurchargeItem { /** * e.g. "card", "crypto", "wallet". Omit/null = any method, which is what * makes a provider-only rule expressible. * * Required before the connector axis landed; every rule stored then carries * one, so those round-trip unchanged. */ payment_method?: string | null; /** Optional finer scope (e.g. a specific wallet/crypto). Not valid for cards — use `card_network`. */ payment_method_type?: string | null; /** Optional card-network scope (card only), e.g. "Visa". */ card_network?: string | null; /** * Provider **brand** scope (`stripe`, `cryptomus`, …). * * The axis a surcharge actually belongs on: what a payment costs the merchant * is set by the processor, so the same `card` charge routed to two providers * costs two different amounts and one method-keyed number cannot cover both. */ connector?: string | null; /** * Provider **account** scope, finer than `connector`. * * One shop can hold several enabled accounts for one connector and price them * differently, which the brand alone cannot express. */ merchant_connector_id?: string | null; /** Extra conditions on the payment itself. Omit/empty = none. */ conditions?: SurchargeCondition[]; surcharge: MethodSurcharge; /** * Optional tax on the surcharge, as a percentage of the surcharge amount. * Always non-negative — on a discount the tax follows the discount's sign, so * the two lines never disagree. */ tax_on_surcharge_percent?: number | null; } /** Request for `PUT /routing/surcharge/rules`. */ interface SurchargeRuleRequest { name?: string | null; /** Shop scope. Omit/null = merchant-wide. */ profile_id?: string | null; /** Surcharge entries, evaluated top-to-bottom (first match wins). */ surcharges?: PerMethodSurchargeItem[]; /** Applied when no entry matches. Omit/null = no adjustment. */ default_surcharge?: MethodSurcharge | null; show_surcharge_breakup_screen?: boolean | null; /** RFC3339; defaults to now when omitted. */ valid_from?: string | null; /** RFC3339; omit = open-ended. */ valid_until?: string | null; } /** Response for `GET/PUT /routing/surcharge/rules` (the structured config + metadata). */ interface SurchargeRuleResponse { name: string; profile_id: string | null; surcharges?: PerMethodSurchargeItem[]; default_surcharge?: MethodSurcharge | null; show_surcharge_breakup_screen?: boolean | null; version: number; /** RFC3339. */ valid_from: string; /** RFC3339, or null when open-ended. */ valid_until: string | null; /** Non-blocking advisories about the saved rule (e.g. network-specific card * surcharge not applied on Stripe card). Empty/absent when none. */ warnings?: string[]; } /** * The appearance a matched rule selects. * * `variant` names one of the shop's stored appearance variants (a key of the * shop's `business_specific_configs`). A name the shop does not define is not * an evaluation error — the checkout falls back to the shop default, exactly as * it does for an unknown `?theme=`, because a buyer who cannot pay is a worse * outcome than a buyer who sees the default look. The API reports such names in * `CheckoutThemeProgramResponse.warnings` instead. */ interface ThemeChoice { variant: string; } /** * The Euclid program output `O`: a matched branch may name a variant. * * Absent or `null` means *leave the appearance alone* — the shop's default * config is used. That is a legal program, and it is what a default selection * carries most of the time. */ interface CheckoutThemeOutput { theme?: ThemeChoice | null; } /** A single theme rule (`Rule`). camelCase on the wire. */ interface CheckoutThemeRule { name: string; connectorSelection: CheckoutThemeOutput; /** The theme-only statement type, not the shared one — see {@link ThemeCondition}. */ statements: ThemeIfStatement[]; } /** * The theme-selection program (`Program`). camelCase on the wire. * * Rules are evaluated top-to-bottom and the first match wins; * `defaultSelection` is what applies when none does. */ interface CheckoutThemeProgram { defaultSelection: CheckoutThemeOutput; rules: CheckoutThemeRule[]; /** Required on the wire — send `{}` when empty. */ metadata: Record; } /** Form factor, resolved from the user-agent at render time. */ type ThemeDeviceClass = 'phone' | 'tablet' | 'desktop'; /** * The buyer's language, **already normalised to one matchable token**. * * This is a closed set on purpose, and building a language picker from anything * else will produce rules that never fire. The DSL has only equality and * numeric ordering — no prefix match, no regex — while a browser sends * `Accept-Language: de-DE,de;q=0.9,en;q=0.8`, which equals no plain tag as raw * text. So the header is reduced to a primary subtag *before* evaluation, and * these are the resulting values: the languages the checkout itself ships * translations for, plus `other`. * * `other` means a language was stated and it is not one the checkout speaks. A * header carrying nothing usable is *absent* instead, and then no language * condition matches at all — absent and `other` are different claims. */ type ThemeBrowserLanguage = 'ar' | 'ca' | 'de' | 'en' | 'es' | 'fr' | 'he' | 'it' | 'ja' | 'nl' | 'pl' | 'pt' | 'ru' | 'sv' | 'zh' | 'other'; /** How the checkout is being rendered: inside the merchant's page, or hosted. */ type ThemeCheckoutChannel = 'embedded' | 'standalone'; /** * Where the buyer came from, bucketed from the `Referer` header. * * Coarse on purpose — the referrer is frequently stripped and is not trustworthy * enough to decide anything but a look. `direct` means no referrer was sent, * which includes "stripped by referrer policy" and not just "typed the URL". */ type ThemeTrafficSource = 'direct' | 'search' | 'social' | 'email' | 'other'; /** * A condition on a dimension whose values are a closed enum. * * Both shapes the engine accepts for an enum dimension: one value, or a list * meaning "any of". Only equality is available — the DSL has no prefix match, * which is exactly why the value types are closed. */ interface ThemeEnumCondition { lhs: L; comparison: 'equal' | 'not_equal'; value: { type: 'enum_variant'; value: V; } | { type: 'enum_variant_array'; value: V[]; }; /** Required on the wire — send `{}` when empty. */ metadata: Record; } /** A condition on a numeric dimension. Amounts are in minor units. */ interface ThemeNumberCondition { lhs: L; comparison: EuclidComparisonType; value: { type: 'number'; value: number; } | { type: 'number_array'; value: number[]; } | { type: 'number_comparison_array'; value: { comparisonType: EuclidComparisonType; number: number; }[]; }; /** Required on the wire — send `{}` when empty. */ metadata: Record; } /** * A condition on a merchant-supplied `payment_intent.metadata` entry. * * Carries nothing observed about the buyer: the merchant chose both the key and * the value, which is why this dimension is allowed here at all. */ interface ThemeMetadataCondition { lhs: 'metadata'; comparison: 'equal' | 'not_equal'; value: { type: 'metadata_variant'; value: { key: string; value: string; }; }; /** Required on the wire — send `{}` when empty. */ metadata: Record; } /** * Every condition a theme rule may express — and, by construction, no others. * * Deliberately **not** the shared `EuclidComparison`, whose `lhs` and enum * values are plain `string`. Reusing it would leave the closed sets above as * documentation: `payment_method` and `browser_language = 'de-DE'` would both * compile, and neither fails loudly. The first is rejected by the server; the * second is worse, because the backend normalises `Accept-Language` to a * primary subtag before evaluating, so a rule written against `de-DE` does not * error — it silently never matches, and never says why. * * The two country dimensions are both here and mean different things: * `buyer_country` is observed at render time, `billing_country` comes off the * address. They disagree routinely and legitimately — a German address opened * from an airport in Spain — so a shop may key on either. Their values are * ISO 3166-1 alpha-2 codes, left as `string` because this SDK has no country * union to point at; the *dimension* is still closed. */ type ThemeCondition = ThemeEnumCondition<'device_class', ThemeDeviceClass> | ThemeEnumCondition<'browser_language', ThemeBrowserLanguage> | ThemeEnumCondition<'checkout_channel', ThemeCheckoutChannel> | ThemeEnumCondition<'traffic_source', ThemeTrafficSource> | ThemeEnumCondition<'buyer_country', string> | ThemeEnumCondition<'billing_country', string> | ThemeEnumCondition<'currency', Currency> | ThemeNumberCondition<'amount'> | ThemeMetadataCondition; /** * The dimensions a theme rule may condition on — the whole list. * * Derived from {@link ThemeCondition} rather than written twice, so the picker * a dashboard builds from this cannot drift from what the AST accepts. * Anything outside the set is rejected at save time rather than ignored, so a * typo cannot become a rule that quietly never fires. * * A theme program decides a look and nothing else: `payment_method`, * `connector` and `card_network` are absent by design, not by oversight, and * the server enforces the same boundary on its side. */ type CheckoutThemeDimension = ThemeCondition['lhs']; /** An IF block; all conditions in `condition` are ANDed. camelCase on the wire. */ interface ThemeIfStatement { condition: ThemeCondition[]; nested?: ThemeIfStatement[] | null; } /** Request for `PUT /routing/checkout-theme/rules`. */ interface CheckoutThemeProgramRequest { /** Shown in the dashboard and echoed on retrieval. */ name?: string | null; /** * Shop scope. Omit/null = merchant-wide. * * A shop-scoped caller may omit it — the scope resolves to that caller's own * shop — and may not name any other shop. */ profile_id?: string | null; algorithm: CheckoutThemeProgram; /** * Whether the program takes effect on save. Defaults to `true` server-side. * * `false` stores a revision without retiring the live one, which is where a * program drafted against a variant that does not exist yet belongs. */ active?: boolean | null; /** RFC3339; defaults to now when omitted. */ valid_from?: string | null; /** RFC3339; omit = open-ended. */ valid_until?: string | null; } /** Response for `PUT/GET /routing/checkout-theme/rules`. */ interface CheckoutThemeProgramResponse { id: string; name: string; /** * The scope the program is stored against, resolved — a shop-scoped caller * that omitted `profile_id` gets its own shop back here. * * Optional, because the contract does not list it as required: a caller that * read an absent value as definitely `null` would turn "not stated" into * "merchant-wide". */ profile_id?: string | null; algorithm: CheckoutThemeProgram; version: number; is_active: boolean; /** RFC3339. */ valid_from: string; /** RFC3339, or null when open-ended. Optional for the same reason as `profile_id`. */ valid_until?: string | null; /** * Non-blocking advisories, recomputed on every read. Empty when none. * * The case this exists for: a rule naming a variant the shop has not defined. * That cannot be an error — programs and variants are edited independently, * so rejecting the save would make it impossible to author them in either * order — but the symptom is a rule that simply never appears to fire, which * is worse to debug than to be told about. */ warnings?: string[]; } /** * Which dimension a conversion breakdown is grouped by. * * `hour` is the UTC hour of the render, 0-23, as a string - not the buyer's * local hour. A shop reading it has to know which clock it is. */ type CheckoutThemeConversionSegment = 'none' | 'device' | 'country' | 'hour'; /** * How much a single measured rate can carry. * * - `too_small` - under the server's render floor. The rate is still reported, * because hiding it invites the reader to assume it is zero, but nothing may * be concluded from it. * - `indicative` - enough to quote, interval too wide to call a close race. * - `reliable` - interval tight enough that a clear difference is a real one. */ type CheckoutThemeSampleVerdict = 'too_small' | 'indicative' | 'reliable'; /** Which side of a separated comparison converted better. */ type CheckoutThemeComparisonSide = 'variant' | 'baseline'; /** * What the `rendered` counts are counts of. * * A code rather than a sentence so the wording can be translated and reworded * without deploying the payments service. Treat an unrecognised value as * "unknown basis" rather than failing. */ type CheckoutThemeDenominatorBasis = 'deduplicated_checkout_opens'; /** * A known way a conversion result is not the whole truth. * * Codes, not sentences - a consumer maps each to its own translated copy. The * list is additive: a client that does not know a code should render nothing * for it rather than throw. */ type CheckoutThemeConversionCaveat = /** * Merchant rules filter this attribution; best effort within 95 days. * Spelled in snake_case, unlike `ClientAnalyticsCaveat`'s codes — the two * unions are serialised differently by the server, so match each against the * one its own field carries. */ 'analytics_exclusions_active' | 'renders_deduplicated_by_device' | 'automated_traffic_excluded' | 'undeclared_mode_reported_separately' | 'no_device_class_in_window' | 'every_render_used_the_default' | 'some_payments_declared_no_mode'; /** One variant x segment cell of the rendered-to-paid funnel. */ interface CheckoutThemeConversionCell { /** * The named appearance variant, or `null` for the shop default - a real and * usually the largest cohort, not missing data. */ variant?: string | null; /** * Content fingerprint of what was actually rendered. Distinguishes two * renders of the "same" variant across a branding edit, which a name cannot. */ appearance?: string | null; /** * The value of the grouped dimension - a device class, an ISO country, or a * UTC hour as a string. `null` means the dimension was not observed for * these renders, which is its own bucket and must not be folded into another. */ segment?: string | null; /** * `true` test, `false` live, `null` the merchant declared no mode at create. * Its own bucket deliberately: a merchant who never declares should see that * rather than have their traffic silently filed as live. */ test_mode?: boolean | null; rendered: number; paid: number; /** * Conversion rate in `[0, 1]`, `null` when nothing was rendered. Not a * percentage - multiply if you want one, and there is then no question about * how it was rounded. */ rate?: number | null; /** Wilson interval bounds at ~95%. Both or neither; `null` when nothing rendered. */ interval_low?: number | null; interval_high?: number | null; verdict: CheckoutThemeSampleVerdict; } /** * One named variant measured against the shop's default appearance, inside one * segment and one mode. * * Only the comparison a merchant actually makes. Every-variant-against-every- * other would answer a question nobody asked and multiply the chance that one * pair separates by luck alone. */ interface CheckoutThemeConversionComparison { variant: string; segment?: string | null; test_mode?: boolean | null; variant_rate?: number | null; baseline_rate?: number | null; /** * Whether the two intervals are disjoint and both samples clear the floor. * * `false` means **"not shown to differ"**, never "shown not to differ". * Overlapping intervals are not evidence of equality, and rendering this as * "no difference" says something the data does not. */ separates: boolean; /** * Which side converted better - present only when `separates`. A direction on * a comparison that did not separate is the exact over-reading the interval * exists to prevent. */ higher?: CheckoutThemeComparisonSide | null; } /** Query for `GET /routing/checkout-theme/conversion`. */ interface CheckoutThemeConversionQuery { /** Shop to report on. Omit to report every shop the caller can see. */ profile_id?: string; /** Window start, RFC3339. Inclusive. */ start: string; /** Window end, RFC3339. Exclusive. */ end: string; /** Defaults to `none` server-side: one row per variant. */ segment?: CheckoutThemeConversionSegment; } /** Response for `GET /routing/checkout-theme/conversion`. */ interface CheckoutThemeConversionResponse { segment: CheckoutThemeConversionSegment; cells: CheckoutThemeConversionCell[]; /** * Every named variant measured against the shop default in its own bucket. * Empty when the window has no named variant, or no default to compare to. */ comparisons: CheckoutThemeConversionComparison[]; denominator: CheckoutThemeDenominatorBasis; /** * Known ways this is not the whole truth. Always present, possibly empty, so * a consumer can render the field unconditionally. */ caveats: CheckoutThemeConversionCaveat[]; } /** Full routing config returned by `GET /routing/{id}`. */ interface MerchantRoutingAlgorithm { id: string; profile_id: string; name: string; description: string; algorithm: StaticRoutingAlgorithm | Record; /** Milliseconds since epoch. */ created_at: number; /** Milliseconds since epoch. */ modified_at: number; algorithm_for: TransactionType; } /** Response from `GET /routing` and `GET /routing/list/profile`. */ interface RoutingDictionary { merchant_id: string; active_id: string | null; records: RoutingDictionaryRecord[]; } /** Per-profile default fallback routing returned by `GET /routing/default/profile`. */ interface ProfileDefaultRoutingConfig { profile_id: string; connectors: RoutableConnectorChoice[]; } /** * Response from `GET /routing/active`. Backend returns an untagged enum that's * either a struct `{ algorithm: ... }` (merchant-account based) or an array * of records (profile based). */ type LinkedRoutingConfigRetrieveResponse = { algorithm: MerchantRoutingAlgorithm | null; } | RoutingDictionaryRecord[]; /** * @deprecated Prefer the specific shapes: `RoutingDictionaryRecord` (list/create/activate) * or `MerchantRoutingAlgorithm` (retrieve). Kept for backwards compatibility. */ interface RoutingConfigResponse { id?: string | null; name?: string | null; description?: string | null; algorithm?: Record | null; profile_id?: string | null; created_at?: string | null; modified_at?: string | null; [key: string]: unknown; } interface PayoutCreateRequest { amount: number; currency: Currency; merchant_order_reference_id?: string | null; routing?: Record | null; connector?: string[] | null; confirm?: boolean | null; payout_type?: PayoutType | null; payout_method_data?: Record | null; billing?: Address | null; customer_id?: string | null; customer?: Record | null; description?: string | null; return_url?: string | null; entity_type?: string | null; metadata?: Record | null; profile_id?: string | null; session_expiry?: number | null; payout_link?: boolean | null; auto_fulfill?: boolean | null; } interface PayoutUpdateRequest { merchant_order_reference_id?: string | null; amount?: number | null; currency?: Currency | null; routing?: Record | null; connector?: string[] | null; confirm?: boolean | null; payout_type?: PayoutType | null; payout_method_data?: Record | null; billing?: Address | null; customer_id?: string | null; description?: string | null; return_url?: string | null; entity_type?: string | null; metadata?: Record | null; } interface PayoutResponse { payout_id: string; merchant_id: string; amount: number; currency: Currency; auto_fulfill: boolean; customer_id: string; client_secret: string; return_url: string; business_country: string; entity_type: string; recurring: boolean; status: PayoutStatus; profile_id: string; merchant_order_reference_id?: string | null; connector?: string | null; payout_type?: PayoutType | null; payout_method_data?: Record | null; billing?: Address | null; description?: string | null; metadata?: Record | null; error_code?: string | null; error_message?: string | null; created?: string | null; [key: string]: unknown; } interface PayoutListParams { customer_id?: string | null; starting_after?: string | null; ending_before?: string | null; limit?: number; created?: string | null; start_time?: string | null; end_time?: string | null; } interface PayoutListResponse { size: number; data: PayoutResponse[]; total_count?: number | null; } interface SignUpRequest { email: string; password: string; } /** Full signup with merchant details — creates a merchant admin user and the merchant account atomically. */ interface SignUpWithMerchantIdRequest { name: string; email: string; password: string; company_name: string; organization_type?: string | null; } /** @deprecated Use `SignUpWithMerchantIdRequest`. */ type SignUpWithMerchantRequest = SignUpWithMerchantIdRequest; interface SignInRequest { email: string; password: string; } interface AuthResponse { token: string; token_type?: string | null; expires?: number | null; [key: string]: unknown; } interface UserResponse { merchant_id: string; name: string; email: string; role_id: string; is_two_factor_auth_setup: boolean; profile_id: string; entity_type: string; version: string; verification_days_left?: number | null; recovery_codes_left?: number | null; theme_id?: string | null; /** * The caller's user-scoped metadata bucket (free-form JSON), as updated by * `users.updateMetadata`. Absent/null = bucket unused. */ user_metadata?: Record | null; /** * The merchant-scoped metadata bucket shared by every dashboard user of * the merchant, as updated by `users.updateMerchantMetadata`. */ merchant_metadata?: Record | null; } interface ChangePasswordRequest { old_password: string; new_password: string; } interface DeleteAccountRequest { password: string; /** Required when the caller has TOTP enrolled. Omit otherwise. */ totp_code?: string; } interface ForgotPasswordRequest { email: string; } interface ResetPasswordRequest { token: string; password: string; } interface SwitchMerchantRequest { merchant_id: string; } interface SwitchProfileRequest { profile_id: string; } /** * Body for `POST /user/employees/impersonate` — act as one of your own team * members. The member's role must rank strictly below the caller's (enforced * server-side). The returned token is meant for a fresh, isolated tab (e.g. * `/auth/impersonate?token=…`), not the caller's own session. */ interface ImpersonateEmployeeRequest { /** Email of the team member to impersonate. */ email: string; /** Disambiguates when the member holds several roles under this merchant. */ role_id?: string; /** Shop context — restricts the membership lookup to this shop's role row. */ profile_id?: string; } interface InviteUsersRequest { email: string; name: string; role_id: string; profile_ids?: string[]; } interface InviteUsersResponse { email: string; is_email_sent: boolean; /** * Temporary password, present only when the invite email could not be * delivered. Shown once — share it with the invitee out of band. They are * forced to change it on first sign-in. */ password?: string | null; error?: string | null; } interface AddUserRequest { email: string; name: string; /** * Password for the new member. Omit it to have the server generate one, which * is then returned once in `AddUserResponse.password`. Supplying it keeps the * password out of the response, since you already have it. */ password?: string; role_id: string; /** * Shop IDs to scope the role to, when `role_id` is a shop-scoped role. If * omitted, a shop-scoped role falls back to your current shop. */ profile_ids?: string[]; } interface UpdateUserRoleRequest { email: string; role_id: string; /** * Shop whose membership to update. Send this when managing a shop's team as a * merchant-scoped admin: your token points at your own shop, not the one you * are viewing, so without it the member is looked up in the wrong shop and * appears not to be a member at all. Defaults to your own shop when omitted. */ profile_id?: string; } interface DeleteUserRoleRequest { email: string; /** * Shop to remove the member from. Send this when managing a shop's team as a * merchant-scoped admin: your token points at your own shop, not the one you * are viewing, so without it the removal targets the wrong shop and fails. * Defaults to your own shop when omitted. */ profile_id?: string; } interface AddUserResponse { user_id: string; email: string; name: string; is_verified: boolean; is_active: boolean; /** * Present only when the server generated the password (the request omitted * one). Shown once — it is stored hashed and cannot be read back. The member * is forced to change it on first sign-in. */ password?: string; } interface TotpResponse { secret?: { secret: string; totp_url: string; } | null; } interface RecoveryCodesResponse { recovery_codes: string[]; } /** Request shape for exchanging an email-link token for a single-purpose token. */ interface FromEmailRequest { /** The JWT delivered in the password-reset / verify-email / invite link. */ token: string; } /** * Purpose of a single-purpose JWT. The API decides which purpose to issue * next based on the user's current state (e.g. TOTP not set → `totp`, TOTP * verified → `reset_password`). */ type TokenPurpose = 'totp' | 'reset_password' | 'sso' | 'verify_email' | string; /** Response shape from endpoints that issue a new single-purpose token. */ interface TokenResponse { /** Signed JWT — pass as `Authorization: Bearer ` on the next call. */ token: string; /** Kind of token — tells the client which step to perform next. */ token_type: TokenPurpose; } /** * One invitation still waiting on the caller, as `GET /user/list/invitation` * returns it: the entity they were invited to and the role they would hold. */ interface UserInvitation { entity_id: string; entity_type: EntityType; /** Display name of the entity, when the backend knows one. */ entity_name?: string | null; role_id: string; } /** One entity to accept, for `POST /user/employees/invite/accept/pre-auth`. */ interface InvitationEntity { entity_id: string; entity_type: EntityType; } /** Body for `POST /user/employees/invite/accept/pre-auth`: every entity to accept at once. */ type AcceptInvitationsPreAuthRequest = InvitationEntity[]; /** Body for `POST|PUT /user/2fa/totp/verify` — 6-digit code from authenticator app. */ interface VerifyTotpRequest { totp: string; } /** Optional query params for `GET /user/2fa/terminate`. */ interface Terminate2faQueryParams { /** * Skip the TOTP requirement entirely. Only honored when the server has * `force_two_factor_auth = false`. Use sparingly -- it weakens security. */ skip_two_factor_auth?: boolean; } /** * Optional query for `GET /user/me/login-activity`. The API clamps `limit` * to `1..=200` (default 25) and floors `offset` at 0 (default 0). */ interface LoginHistoryParams { limit?: number; offset?: number; } /** * One row from the authenticated user's login history. * * `event_type` enumerates the signin attempt outcome — current values: * `signin_success`, `signin_failure`, `signup_success`, `sso_success`, * `sso_failure`, `totp_success`, `totp_failure`, `recovery_code_success`, * `recovery_code_failure`, `magic_link_success`. Stored as a string so * older rows stay readable when the enum grows. * * `failure_reason` is set on failure rows. Current values: * `user_not_found`, `invalid_credentials`, `totp_invalid`, * `totp_max_attempts`, `recovery_code_invalid`, `not_in_admin_org`, * `rate_limited`, `other`. * * `auth_method` tells the dashboard how the attempt authenticated: * `password`, `magic_link`, `sso_oidc`, `totp`, `recovery_code`. * * Geo fields (`country_code`, `city`, `latitude`, `longitude`) are populated * by a GeoIP lookup at signin time. They stay `null` for private/loopback * IPs, when the lookup misses, or when GeoIP is not enabled. */ interface LoginHistoryEntry { id: string; event_type: string; failure_reason: string | null; auth_method: string; ip_address: string | null; user_agent: string | null; country_code: string | null; city: string | null; latitude: number | null; longitude: number | null; /** ISO-8601 timestamp the row was inserted (signin attempt time). */ created_at: string; } /** * Response shape for `GET /user/me/login-activity`. `total_count` is the * unpaginated row count for client-side page-count calculation. * * When a Delopay admin is impersonating a merchant, the API returns * `entries: []` and `total_count: 0` so the admin's IP / geo data is * not exposed inside the merchant dashboard. The admin can still see * their own activity by switching back to the admin organization. */ interface LoginHistoryResponse { entries: LoginHistoryEntry[]; total_count: number; offset: number; limit: number; } /** * One row from `GET /user/me/sessions`. * * A session is one minted login JWT. `id` is also the JWT's `jti`. The * row is mutable: `last_seen_at` is updated as the session makes API * calls (debounced to one DB write per 5 min), and `revoked_at` is set * when the user disconnects this session from the dashboard. * * `auth_method` enumerates the signin method that produced the session. * Today only `password` issues sessions; SSO / TOTP / recovery will follow. * * `is_current` is `true` for the row corresponding to the JWT used to * make the listing request — the dashboard renders a "This device" tag * and asks for confirmation before letting the user disconnect it. */ interface UserSessionEntry { id: string; auth_method: string; ip_address: string | null; user_agent: string | null; country_code: string | null; city: string | null; /** ISO-8601 timestamp the session was created (signin time). */ created_at: string; /** ISO-8601 timestamp of the most recent observed API call on this session. */ last_seen_at: string; /** ISO-8601 timestamp at which the session JWT exp lapses. */ expires_at: string; is_current: boolean; } interface UserSessionListResponse { sessions: UserSessionEntry[]; } interface UserSessionRevokeResponse { revoked: boolean; } /** Optional query params for `GET /user/employees/list`. */ interface ListUsersInLineageParams { /** * Lineage level to list members of: `'tenant'`, `'organization'`, * `'merchant'` or `'profile'`. Defaults to the widest level your role can * see. */ entity_type?: string; /** * Shop to list members of. Only meaningful together with * `entity_type: 'profile'`, and ignored otherwise. Send it when managing a * shop's team as a merchant-scoped admin: your token points at your own * shop, not the one you are viewing. Defaults to your own shop. */ profile_id?: string; } /** The scope level a role or resource belongs to. */ type EntityType = 'tenant' | 'organization' | 'merchant' | 'profile'; /** A role a member holds, as returned by `GET /user/employees/list`. */ interface MinimalRoleInfo { role_id: string; role_name: string; /** The scope the role is defined at. */ entity_type: EntityType; } /** One member of the current lineage. `GET /user/employees/list` */ interface UserInLineage { /** * Stable identifier of the member. Use it to target them specifically — for * a per-user operation-limit override, or to resolve a user id stamped on * another resource (such as a refund's `initiated_by`) back to a person. */ user_id: string; email: string; roles: MinimalRoleInfo[]; } /** Optional query params for `GET /user/role/list/invite`. */ interface ListInvitableRolesParams { /** * Scope the listing to roles invitable for a given entity. The dashboard * passes `'merchant'` when populating an invite-employee dialog so admin/ * platform-only roles don't leak into a merchant-side list. */ entity_type?: string; } interface PhoneOtpRequest { phone_number: string; } interface PhoneOtpResponse { message: string; } interface PhoneOtpVerifyRequest { phone_number: string; code: string; } interface PhoneOtpVerifyResponse { verified: boolean; } interface UpdateUserDetailsRequest { name?: string | null; } /** What a catalogue product is sold as. `one_time` or `recurring`. */ type ProductKind = 'one_time' | 'recurring'; /** Whether a product or one of its prices is still on sale. */ type ProductStatus = 'active' | 'archived'; /** The unit a recurring price bills in, and the unit a trial is measured in. */ type ProductPriceInterval = 'day' | 'week' | 'month' | 'year'; /** A price to create with a product, or to add to one. */ interface ProductPriceCreateRequest { nickname?: string | null; /** Minor units, in `currency`. */ amount: number; currency: Currency; /** Set together with `billing_interval_count` for a recurring price. */ billing_interval?: ProductPriceInterval | null; billing_interval_count?: number | null; trial_period?: number | null; trial_period_unit?: ProductPriceInterval | null; /** The price a checkout picks when none is named. */ is_default?: boolean | null; metadata?: Record | null; } /** A price as the catalogue reports it. */ interface ProductPriceResponse { id: string; product_id: string; nickname?: string | null; amount: number; currency: Currency; billing_interval?: ProductPriceInterval | null; billing_interval_count?: number | null; trial_period?: number | null; trial_period_unit?: ProductPriceInterval | null; is_default: boolean; status: ProductStatus; metadata?: Record | null; created_at: string; modified_at: string; archived_at?: string | null; } /** * A product to create, with at least one price. * * `prices` is required by the API: a product nobody can be charged for is not * a product, and creating one and pricing it later would leave a shop with * catalogue entries that cannot be sold. */ interface ProductCreateRequest { name: string; description?: string | null; kind: ProductKind; prices: ProductPriceCreateRequest[]; metadata?: Record | null; } /** * What may be changed on a product after it exists. * * Deliberately not `kind`, and not its prices: a product's kind decides how it * is billed, and a price is closed and replaced rather than edited, so a * charge that was made can always be explained by a price that still exists. */ interface ProductUpdateRequest { name?: string | null; description?: string | null; metadata?: Record | null; } /** What may be changed on a price: its label, its default flag, its metadata. */ interface ProductPriceUpdateRequest { nickname?: string | null; is_default?: boolean | null; metadata?: Record | null; } /** Whether a name is the product's current name or another name it goes by. */ type ProductAliasKind = 'name' | 'alias'; /** How a connected account's product or price was tied to a catalogue product. */ type ProductMappingMode = 'map_existing' | 'manual_id'; /** A name a product goes by. */ interface ProductAliasResponse { id: string; alias: string; kind: ProductAliasKind; created_at: string; } /** A connected account's product or price, tied to a catalogue product. */ interface ProductConnectorReferenceResponse { id: string; connector: string; merchant_connector_id: string; connector_product_id: string; /** Null on a reference to the connector's product itself. */ connector_price_id?: string | null; price_id?: string | null; display_name?: string | null; mapping_mode: ProductMappingMode; created_at: string; } /** A product as the catalogue reports it, prices and identity included. */ interface ProductResponse { id: string; profile_id: string; name: string; description?: string | null; kind: ProductKind; status: ProductStatus; prices: ProductPriceResponse[]; /** Every name, including the current name, oldest first. */ aliases: ProductAliasResponse[]; /** Oldest first; callers limited to connected accounts see only their references. */ connector_references: ProductConnectorReferenceResponse[]; metadata?: Record | null; created_at: string; modified_at: string; archived_at?: string | null; } /** One page of a shop's catalogue. */ interface ProductListResponse { data: ProductResponse[]; total_count: number; } /** What `products.list` narrows and pages by. */ interface ProductListParams { status?: ProductStatus; limit?: number; offset?: number; } /** * What an external record asserts, and about which resource. * * Tagged on `resource` rather than a flat `(kind, id)` pair, so each kind * carries exactly what it needs: a refund has an amount, a payment does not, * and a payout has no owning payment. `POST /external-records`. */ /** * Which environment an external record asserts the movement happened in. */ type ExternalRecordEnvironment = 'live' | 'sandbox'; type ExternalRecordTarget = { resource: 'payment'; payment_id: string; /** The status the rail actually holds it in. */ status: IntentStatus; /** * Which environment the movement happened in, stated by the operator. * * Required, and required here only: there is deliberately no default and * no inference from the connector's current settings, because a record * filed against the wrong environment is indistinguishable afterwards * from a real one. */ environment: ExternalRecordEnvironment; } | { /** * A refund that has no row here yet — the forcing case, one that exists * only in the PSP's dashboard. * * `amount` is required here and only here: there is no recorded figure * to fall back on, and the API answers `400 amount is required when * recording a refund that has no row here`. Splitting the two shapes is * what turns that into a compile error. */ resource: 'refund'; payment_id: string; /** * Absent, or explicitly `null` — the contract marks it nullable, and a * caller holding `refundId: string | null` should be able to pass it * straight through rather than delete the property first. */ refund_id?: null; status: WebhookRefundStatus; /** Minor units, in the payment's currency. */ amount: number; } | { /** * A refund that does have a row — one DeloPay attempted, that failed on * our side, and that the operator then completed by hand at the PSP. * * `amount` is ignored here: the figure is already recorded and is not an * operator's to restate. */ resource: 'refund'; payment_id: string; refund_id: string; status: WebhookRefundStatus; } | { resource: 'dispute'; payment_id: string; dispute_id?: string | null; status: DisputeStatus; /** Minor units, in the payment's currency. */ amount?: number | null; } | { resource: 'payout'; payout_id: string; status: PayoutStatus; }; /** * Record that a payment, refund, dispute or payout reached a state DeloPay * never observed. * * The transition is written through the normal path, an outgoing webhook is * raised for the shop, and an audit row records who asserted it and why. */ type ExternalRecordRequest = ExternalRecordTarget & { /** * Why this is being recorded. Compelled and non-empty, at most 1024 * characters after trimming — counted as characters, so a non-Latin reason * gets the same room as one in plain ASCII. */ reason: string; /** * The PSP's own identifier for what happened — a dashboard refund id, a case * number, a support ticket. Free text, because every rail shapes it * differently, and it is what this is reconciled against. */ connector_reference?: string | null; /** * When it happened at the PSP, if the operator knows, ISO 8601 * (`2026-09-05T14:22:00.000Z`). Never inferred: a missing value stays * missing rather than becoming the moment somebody typed it in. * * On a dispute it also orders the recording against the PSP's own updates; a * value older than the PSP's last update, or more than five minutes ahead of * DeloPay's clock, is refused with a 400. */ occurred_at?: string | null; }; /** What was recorded, and what it did. `POST /external-records`. */ interface ExternalRecordResponse { record_id: string; resource_kind: string; resource_id: string; payment_id?: string | null; /** Whether the row was created here rather than moved. */ created_resource: boolean; previous_status?: string | null; recorded_status: string; /** * What happened to the shop's notification, and a claim rather than a * receipt: `persisted` means an event was created and dispatch was handed * off — not that the shop received it. */ webhook_outcome?: string | null; recorded_by: string; recorded_at: string; } /** * One of the four kinds `POST /external-records` knows, named on its own. * * The same strings {@link ExternalRecordTarget} is tagged with, so a client * that reads a capability and then builds a request writes the same word twice * rather than translating between two vocabularies. */ type ExternalRecordResource = 'payment' | 'refund' | 'dispute' | 'payout'; /** * Whether `POST /external-records` will create a resource of one kind that has * no row here yet. * * Three answers rather than two, and the third is the one worth having: * `not_applicable` means creating this kind is refused by a rule, not by a * gap, so a caller who cannot tell it from `not_implemented` will keep asking * for something to be finished that was never going to be built. */ type ExternalRecordCreateSupport = { status: 'served'; } | { /** The path is not built on this release. */ status: 'not_implemented'; /** * The message the create call itself answers with, carried from the same * place, so the two can never drift. */ reason: string; } | { /** A rule refuses it, and it will not be built. */ status: 'not_applicable'; /** Which rule. */ reason: string; }; /** What `POST /external-records` will accept for one kind. */ interface ExternalRecordKindCapability { resource: ExternalRecordResource; /** * Whether a transition of an object that already has a row here is served. * `false` means the kind is declared and its arm is not built. */ transition: boolean; create: ExternalRecordCreateSupport; /** * Every **canonical** spelling this kind's `status` field takes — what a * client reading this should send. * * Not an exhaustive list of what parses: a refund's field additionally * accepts a PascalCase alias per variant. Send what is listed here. */ statuses: string[]; } /** * What this deployment serves, per kind. * * Read it before offering the operator a form, so a kind this release does not * serve is absent from it rather than failing after the form is filled in. */ interface ExternalRecordCapabilities { /** * One entry per kind, always all four, so a client can tell "not served" * from "this release does not know that kind at all". */ kinds: ExternalRecordKindCapability[]; } /** * Wire values accepted by {@link WebhookDetails.refund_statuses_enabled}. * * Deliberately not {@link RefundStatus}: that is the refund *object's* public * status enum (`succeeded`/`failed`/`pending`/`review`), whose strings this * field does not accept. */ type WebhookRefundStatus = 'failure' | 'manual_review' | 'pending' | 'success' | 'transaction_failure'; /** * Outgoing-webhook configuration for a merchant account or business profile. * * The backend merges this object field-by-field on update: omitting a key * keeps the stored value, and an empty string is how a URL is cleared. */ interface WebhookDetails { webhook_version?: string | null; webhook_username?: string | null; webhook_password?: string | null; /** The url event notifications are POSTed to. */ webhook_url?: string | null; /** * The url test-mode events are delivered to. Events whose object ran in * test mode (`test_mode: true` in the webhook payload — test payments, and * their refunds and disputes) are posted here; everything else keeps going * to `webhook_url`. Unset or empty means no environment split: every event * goes to `webhook_url`. Deliveries to this url are signed with the same * hash key as production deliveries. */ webhook_url_test?: string | null; /** POST a webhook whenever a new payment is created. */ payment_created_enabled?: boolean | null; /** POST a webhook whenever a payment succeeds. */ payment_succeeded_enabled?: boolean | null; /** POST a webhook whenever a payment fails. */ payment_failed_enabled?: boolean | null; /** Payment intent statuses that trigger a webhook. */ payment_statuses_enabled?: IntentStatus[] | null; /** Refund statuses that trigger a webhook. */ refund_statuses_enabled?: WebhookRefundStatus[] | null; /** Payout statuses that trigger a webhook. */ payout_statuses_enabled?: PayoutStatus[] | null; } interface MerchantAccountCreateRequest { merchant_id: string; merchant_name?: string | null; return_url?: string | null; webhook_details?: WebhookDetails | null; sub_merchants_enabled?: boolean | null; parent_merchant_id?: string | null; metadata?: Record | null; publishable_key?: string | null; /** * The language a shop created under this account starts with, as one BCP 47 * tag (e.g. `de`). A shop created without a `default_locale` of its own * copies this one; changing it later does not change shops that already * exist. Refused unless it is well formed and a language the hosted checkout * ships. */ default_locale?: string | null; } interface MerchantAccountUpdateRequest { merchant_id: string; merchant_name?: string | null; return_url?: string | null; webhook_details?: WebhookDetails | null; sub_merchants_enabled?: boolean | null; metadata?: Record | null; /** * The language a shop created under this account starts with. Omit it to * leave it unchanged; send `null` to clear it. Shops that already exist keep * their own setting either way. */ default_locale?: string | null; } interface MerchantAccountResponse { merchant_id: string; enable_payment_response_hash: boolean; redirect_to_merchant_with_http_post: boolean; primary_business_details: Record[]; organization_id: string; is_recon_enabled: boolean; recon_status: string; merchant_account_type: MerchantAccountType; merchant_name?: string | null; return_url?: string | null; payment_response_hash_key?: string | null; webhook_details?: WebhookDetails | null; sub_merchants_enabled?: boolean | null; parent_merchant_id?: string | null; publishable_key?: string | null; metadata?: Record | null; /** * The language a shop created under this account starts with. `null` when * the account has not chosen one. */ default_locale?: string | null; created_at?: string | null; modified_at?: string | null; [key: string]: unknown; } /** * A partial update of a connector's `connector_webhook_details`. * * **Merged over the stored block, not swapped for it.** Each key is * decided on its own: * * - **absent (or the whole object omitted / `null`) — keeps** whatever is * stored. This is how you edit one environment's signing secret without * touching the other's. * - **an explicit value — writes**, and the empty string is an explicit value: * sending `''` **clears a stored secret**. That is the only way to clear one * once omission means "keep", so it is deliberate on the server — and it is * why you must never pad the fields you are not editing with `''`. A live * connector cleared that way stops verifying inbound webhooks, silently. * * Only send the keys the operator actually typed. `connectors.retrieve()` * returns `null` for `connector_webhook_details`, so there is nothing to * prefill from and nothing to send back; read * {@link ConnectorResponse.has_live_webhook_secret} / * {@link ConnectorResponse.has_sandbox_webhook_secret} to tell a stored secret * from an unconfigured one. * * The spec's `MerchantConnectorWebhookDetailsUpdate`. */ interface MerchantConnectorWebhookDetailsUpdate { /** * Live-environment webhook verification secret. Omit to keep the stored one; * `''` clears it. */ merchant_secret?: string | null; /** * Live-environment secondary secret. Omit to keep the stored one; `''` * clears it. */ additional_secret?: string | null; /** * Sandbox-environment webhook verification secret. Omit to keep the stored * one; `''` clears it. */ sandbox_merchant_secret?: string | null; /** * Sandbox counterpart of `additional_secret`. Omit to keep the stored one; * `''` clears it. */ sandbox_additional_secret?: string | null; } interface ConnectorCreateRequest { connector_type: ConnectorType; connector_name: Connector; connector_label?: string | null; profile_id?: string | null; connector_account_details?: Record | null; payment_methods_enabled?: Record[] | null; metadata?: Record | null; test_mode?: boolean | null; disabled?: boolean | null; connector_webhook_details?: Record | null; additional_merchant_data?: Record | null; status?: string | null; } interface ConnectorUpdateRequest { connector_type: ConnectorType; /** * Required here because the generated OpenAPI contract lists it in * `MerchantConnectorUpdate.required`. * * The Rust field is `Option`, so the API itself accepts a * partial update without it — the contract says otherwise only because * `#[schema(value_type = ConnectorStatus)]` replaces the inferred type * INCLUDING its optionality. That is backend#1089, and until the spec is * regenerated this type must not contradict it: an SDK that is looser than * the published contract is its own kind of wrong. */ status: string; connector_label?: string | null; connector_account_details?: Record | null; payment_methods_enabled?: Record[] | null; metadata?: Record | null; test_mode?: boolean | null; disabled?: boolean | null; /** * **Merged key by key into the stored webhook details, not a whole-value * replacement.** An absent key keeps its stored value; an explicit value is * written, and an explicit `''` **clears** that secret. * * Send only the keys an operator typed. Padding the environment you are not * editing with `''` — the shape the old replacement semantics invited — * clears a live signing secret and breaks inbound webhook verification for * that connector. See {@link MerchantConnectorWebhookDetailsUpdate}. * * The other three fields below are still whole-value replacements; this one * alone merges. */ connector_webhook_details?: MerchantConnectorWebhookDetailsUpdate | null; /** * Whole-value replacement, not a patch. Send it only when an operator typed * a new value; omitting it leaves the stored one alone, which is the only * safe default now that `retrieve` returns `null` here. */ connector_wallets_details?: Record | null; /** Whole-value replacement — same rule as `connector_wallets_details`. */ pm_auth_config?: Record | null; /** Whole-value replacement — same rule as `connector_wallets_details`. */ additional_merchant_data?: Record | null; } /** * How a connector reaches its sandbox environment. A connector uses at most one * of these. * * - `credentials` — a separate sandbox credential set lives beside the live one * and `test_mode` picks between them. A form needs two credential blocks. * - `request_flag` — one credential set; a flag on the request makes the call a * test at the provider. One credential block, and `test_mode` means something. * - `deployment_host` — one credential set; the host this deployment is * configured to call decides the environment. Nothing on the account switches * it. * - `none` — no sandbox of any kind. `test_mode` records an intention and * changes nothing about where the money goes. * - `unaudited` — not established. The connector may well have a sandbox; * nobody has confirmed how it is reached. Treat it as one credential block and * promise nothing either way. In particular, do not read it as `none`. */ type ConnectorSandboxMechanism = 'credentials' | 'request_flag' | 'deployment_host' | 'none' | 'unaudited'; interface ConnectorCloneRequest { /** Target shop (business profile) to clone the connector into. Must belong * to the same merchant; it cannot be the source connector's own shop. */ profile_id: string; /** Label for the clone. Omit to let the server derive one from the source * connector's label. */ connector_label?: string | null; } interface ConnectorResponse { connector_type: ConnectorType; connector_name: Connector; merchant_connector_id: string; profile_id: string; status: string; /** * Whether a LIVE webhook signing secret is stored on this connector. * Presence only — the secret itself never comes back on `retrieve()`, so * this flag is the only thing that tells a stored secret from an * unconfigured one. An empty stored secret reads as `false`. */ has_live_webhook_secret: boolean; /** * Whether a SANDBOX webhook signing secret is stored on this connector. * Presence only, like its live counterpart. `false` on the many connectors * that issue a single secret for both environments — they store only the * live one. */ has_sandbox_webhook_secret: boolean; /** * How this connector reaches its sandbox, if it has one. Present on the * connector that `create`, `retrieve`, `update` and `clone` return, and on each * entry of `list` and `listByProfile`. * * Read it instead of keeping your own list of connector names: the answer * changes when a connector is added, and a copied list does not. Absent from * a router that predates the field. Treat absence as "not reported" and keep * whatever you did before, rather than as `none`. */ sandbox_mechanism?: ConnectorSandboxMechanism; connector_label?: string | null; connector_account_details?: Record | null; payment_methods_enabled?: Record[] | null; /** * PaymentMethodType strings this connector's Stripe account cannot use * (region/activation). The dashboard greys these out in the picker. Present * only for Stripe; absent/null means "gate nothing" (fail-open). */ unsupported_payment_method_types?: string[] | null; metadata?: Record | null; test_mode?: boolean | null; disabled?: boolean | null; /** * Credential-bearing, and **`null` on `retrieve()`** whatever is stored — * see that method. `create`, `update` and `clone` return a live value: * clone's is the copied secret, which the caller never sent. Do not log * these four. */ connector_webhook_details?: Record | null; /** Credential-bearing — same handling as `connector_webhook_details`. */ connector_wallets_details?: Record | null; /** Credential-bearing — same handling as `connector_webhook_details`. */ pm_auth_config?: Record | null; /** Credential-bearing — same handling as `connector_webhook_details`. */ additional_merchant_data?: Record | null; created_at?: string | null; } interface ConnectorListResponse { payload: ConnectorResponse[]; } interface ProfileCreateRequest { profile_name?: string | null; return_url?: string | null; /** Optional default unpaid-checkout exit URL for this shop. */ cancel_url?: string | null; enable_payment_response_hash?: boolean | null; payment_response_hash_key?: string | null; redirect_to_merchant_with_http_post?: boolean | null; webhook_details?: WebhookDetails | null; metadata?: Record | null; /** * Origins permitted to embed this profile's hosted checkout in an * iframe. Each entry is a full origin (`scheme://host[:port]`, no * path). Empty/null defaults to same-origin only — strict * clickjacking defense. */ iframe_allowed_origins?: string[] | null; /** * Merchant connector account ID of the billing processor (e.g. Stripe * Billing / PayPal) that owns this profile's native subscriptions. */ billing_processor_id?: string | null; /** * Custom format for generated payment ids (cloaking), e.g. * `ORD-74219807`. `null` = default `pay_` ids. */ payment_id_format?: PaymentIdFormatConfig | null; /** * The shop's home country (ISO alpha-2) for cross-border analytics. * `null` = international / no home country (the default; on updates, * explicit `null` clears, absent leaves unchanged). */ home_country?: string | null; /** * Whether the processor may see the payment `description`. Unset or * `false` (the default) withholds it from every connector; the description * is unaffected everywhere else — stored intent, checkout, dashboards, * webhooks and API responses all keep it. */ send_description_to_processor?: boolean | null; /** * Whether the processor may see the payment `metadata`. Unset or `false` * (the default) withholds the merchant's own keys from every connector that * forwards them; DeloPay's control keys ride either way. */ send_metadata_to_processor?: boolean | null; /** * The language this shop's hosted checkout renders in, as one BCP 47 tag * (e.g. `de`). Absent means the shop has not chosen one and the buyer's * browser decides. Refused unless it is well formed and a language the * checkout ships. */ default_locale?: string | null; /** * Whether `default_locale` outranks the buyer's browser language. Unset or * `false` keeps the browser first, with the shop default filling the gap * where nothing else answered. */ prefer_merchant_locale?: boolean | null; } interface ProfileUpdateRequest { profile_name?: string | null; return_url?: string | null; /** Absent leaves unchanged; explicit null clears the shop's Cancel URL. */ cancel_url?: string | null; enable_payment_response_hash?: boolean | null; payment_response_hash_key?: string | null; redirect_to_merchant_with_http_post?: boolean | null; webhook_details?: WebhookDetails | null; metadata?: Record | null; payment_link_config?: BusinessPaymentLinkConfig | null; /** * Origins permitted to embed this profile's hosted checkout in an * iframe. See {@link ProfileCreateRequest.iframe_allowed_origins}. */ iframe_allowed_origins?: string[] | null; /** * Merchant connector account ID of the billing processor (e.g. Stripe * Billing / PayPal) that owns this profile's native subscriptions. */ billing_processor_id?: string | null; /** * Custom format for generated payment ids (cloaking), e.g. * `ORD-74219807`. `null` = default `pay_` ids. */ payment_id_format?: PaymentIdFormatConfig | null; /** * The shop's home country (ISO alpha-2) for cross-border analytics. * `null` = international / no home country (the default; on updates, * explicit `null` clears, absent leaves unchanged). */ home_country?: string | null; /** * Whether the processor may see the payment `description`. Absent leaves * the setting unchanged; there is no clear case — send `false` to withhold * the description again. */ send_description_to_processor?: boolean | null; /** * Whether the processor may see the payment `metadata`. Absent leaves the * setting unchanged; send `false` to withhold it again. */ send_metadata_to_processor?: boolean | null; /** * The language this shop's hosted checkout renders in. Absent leaves it * unchanged; explicit `null` clears it again. */ default_locale?: string | null; /** * Whether `default_locale` outranks the buyer's browser language. Absent * leaves it unchanged; there is no clear case — send `false` to give the * browser priority again. */ prefer_merchant_locale?: boolean | null; } interface ProfileResponse { merchant_id: string; profile_id: string; profile_name: string; enable_payment_response_hash: boolean; redirect_to_merchant_with_http_post: boolean; /** * Whether the processor may see the payment `description`. `false` (the * default) withholds it from every connector. */ send_description_to_processor: boolean; /** * Whether the processor may see the payment `metadata`. `false` (the * default) withholds the merchant's own keys from every connector that * forwards them; DeloPay's control keys ride either way. */ send_metadata_to_processor: boolean; is_tax_connector_enabled: boolean; is_network_tokenization_enabled: boolean; is_auto_retries_enabled: boolean; is_click_to_pay_enabled: boolean; is_clear_pan_retries_enabled: boolean; force_3ds_challenge: boolean; is_pre_network_tokenization_enabled: boolean; return_url?: string | null; /** The shop's optional default unpaid-checkout exit URL. */ cancel_url?: string | null; payment_response_hash_key?: string | null; webhook_details?: WebhookDetails | null; metadata?: Record | null; /** * Origins permitted to embed this profile's hosted checkout in an * iframe. See {@link ProfileCreateRequest.iframe_allowed_origins}. */ iframe_allowed_origins?: string[] | null; /** * Merchant connector account ID of the billing processor (e.g. Stripe * Billing / PayPal) that owns this profile's native subscriptions. */ billing_processor_id?: string | null; /** * Custom format for generated payment ids (cloaking), e.g. * `ORD-74219807`. `null` = default `pay_` ids. */ payment_id_format?: PaymentIdFormatConfig | null; /** * The shop's home country (ISO alpha-2) for cross-border analytics. * `null` = international / no home country (the default; on updates, * explicit `null` clears, absent leaves unchanged). */ home_country?: string | null; /** The language this shop's hosted checkout renders in. `null` when the shop has not chosen one. */ default_locale?: string | null; /** Whether `default_locale` outranks the buyer's browser language. */ prefer_merchant_locale: boolean; [key: string]: unknown; /** The Stripe connected account this shop settles through, when one is linked. */ stripe_connected_account_id?: string | null; /** * Whether `stripe_connected_account_id` names an account DeloPay onboarded * onto its own Stripe platform. Read-only: set only by the Stripe onboarding * flow and cleared whenever the id is repointed through a profile update. * `false` means DeloPay's platform fee is collected from the prepaid balance * rather than as a Stripe application fee. */ stripe_connected_account_delopay_onboarded: boolean; } type BlocklistAddRequest = { type: 'card_bin'; data: string; } | { type: 'fingerprint'; data: string; } | { type: 'extended_card_bin'; data: string; }; interface BlocklistResponse { fingerprint_id: string; data_kind: BlocklistDataKind; created_at: string; } interface AuthenticationCreateRequest { amount: number; currency: Currency; authentication_id?: string | null; profile_id?: string | null; customer?: Record | null; authentication_connector?: string | null; return_url?: string | null; force_3ds_challenge?: boolean | null; psd2_sca_exemption_type?: string | null; profile_acquirer_id?: string | null; payment_method_data?: Record | null; } interface AuthenticationResponse { authentication_id: string; merchant_id: string; status: AuthenticationStatus; amount: number; currency: Currency; client_secret?: string | null; authentication_connector?: string | null; force_3ds_challenge?: boolean | null; return_url?: string | null; /** Connector error code, when the authentication call itself failed. */ error_code?: string | null; /** Connector error message, when the authentication call itself failed. */ error_message?: string | null; [key: string]: unknown; } interface StripeConnectAccountRequest { merchant_id: string; profile_id: string; country: string; business_type: string; email?: string | null; } interface StripeConnectAccountResponse { account_id: string; profile_id: string; } interface StripeConnectLinkRequest { merchant_id: string; profile_id: string; return_url: string; refresh_url: string; } interface StripeConnectLinkResponse { url: string; expires_at: number; } interface ApplePayVerificationRequest { domain_names: string[]; merchant_connector_account_id: string; } interface ApplePayVerificationResponse { status_message: string; } interface ApplePayVerifiedDomainsResponse { verified_domains: string[]; } interface EventListParams { created_after?: string | null; created_before?: string | null; limit?: number | null; offset?: number | null; object_id?: string | null; event_id?: string | null; profile_id?: string | null; event_classes?: EventClass[] | null; event_types?: EventType[] | null; /** Filter on whether the webhook was ultimately delivered. */ is_delivered?: boolean | null; /** * Filter on how the delivery was produced. Empty or absent means no * filtering; naming any kind excludes events written before the column * existed, whose kind is unknown. */ delivery_attempts?: WebhookDeliveryAttempt[] | null; } interface EventResponse { event_id: string; merchant_id: string; profile_id: string; object_id: string; event_type: EventType; event_class: EventClass; initial_attempt_id: string; created: string; /** * How this delivery was produced. Present on the list as well as the detail, * so a hand-fired `manual_trigger` event — the one event whose payload is an * assertion rather than an observation — is recognisable without opening it. * `null` on events written before the column existed. */ delivery_attempt?: WebhookDeliveryAttempt | null; is_delivery_successful?: boolean | null; delivery_terminal_reason?: WebhookDeliveryTerminalReason | null; latest_attempt_id?: string | null; /** * The environment of the object this event was created for: `true` means it * was routed as a test-mode event (delivered to the profile's test webhook * URL when one is configured). `null` means unknown — events created before * environment tracking, and payloads that carry no environment flag — which * is delivered to the production URL. */ test_mode?: boolean | null; } interface EventDetailResponse extends EventResponse { request: Record; response: Record; delivery_attempt?: WebhookDeliveryAttempt | null; } interface EventListResponse { events: EventResponse[]; total_count?: number | null; } /** * Fire an outgoing webhook event by hand for one object. * * Not a replay: the payload is built from the object's current record and its * status is then **overwritten** to agree with `event_type`. The object's own * state is neither checked nor changed, so this can tell a shop that a payment * succeeded which DeloPay records as failed. */ interface EventManualTriggerRequest { /** Payment Intent ID for a payments event, Refund ID for a refunds event. */ object_id: string; /** * The event type to fire. The payload's status is forced to match it. * * Narrowed to {@link ManualTriggerEventType}: a dispute, mandate, payout or * subscription event has no status this operation can assert, and the * endpoint rejects it. */ event_type: ManualTriggerEventType; /** Ignored for a shop-scoped caller, who is pinned to their own shop. */ profile_id?: string | null; /** Why it was fired. Required, and recorded on the payload and the timeline. */ reason: string; } interface EventManualTriggerResponse extends EventDetailResponse { /** The status the object actually held in DeloPay's records when fired. */ object_status?: string | null; /** * `true` when the forced payload status differs from `object_status` — the * merchant has been told something DeloPay's own records contradict. */ status_overridden: boolean; } interface EventDeliveryAttemptResponse { attempt_id: string; status: string; created_at: string; response_status_code?: number | null; response_headers?: Record | null; response_body?: string | null; error_message?: string | null; } interface PollStatusResponse { poll_id: string; status: PollStatus; } interface ProfileAcquirerCreateRequest { acquirer_assigned_merchant_id: string; merchant_name: string; network: string; acquirer_bin: string; acquirer_fraud_rate: number; profile_id: string; acquirer_ica?: string | null; } interface ProfileAcquirerUpdateRequest { acquirer_assigned_merchant_id?: string | null; merchant_name?: string | null; network?: string | null; acquirer_bin?: string | null; acquirer_ica?: string | null; acquirer_fraud_rate?: number | null; } interface ProfileAcquirerResponse { profile_acquirer_id: string; acquirer_assigned_merchant_id: string; merchant_name: string; network: string; acquirer_bin: string; acquirer_fraud_rate: number; profile_id: string; acquirer_ica?: string | null; } interface RelayRequest { connector_resource_id: string; connector_id: string; type: RelayType; data?: { refund?: { amount?: number | null; currency?: Currency | null; reason?: string | null; } | null; capture?: { amount?: number | null; } | null; incremental_authorization?: { amount?: number | null; reason?: string | null; } | null; void?: { cancellation_reason?: string | null; } | null; } | null; } interface RelayResponse { id: string; status: RelayStatus; connector_resource_id: string; connector_id: string; profile_id: string; type: RelayType; connector_reference_id?: string | null; error?: { code: string; message: string; } | null; } interface ThreeDsRuleExecuteRequest { routing_id: string; payment: Record; payment_method?: Record | null; customer_device?: Record | null; issuer?: Record | null; acquirer?: Record | null; } interface ThreeDsRuleResponse { decision: ThreeDSDecision; } /** Lifecycle status of a subscription. */ type SubscriptionStatus = 'active' | 'created' | 'in_active' | 'pending' | 'trial' | 'paused' | 'unpaid' | 'onetime' | 'cancelled' | 'failed'; /** Status of an invoice raised for one subscription billing cycle. */ type InvoiceStatus = 'invoice_created' | 'payment_pending' | 'payment_pending_timeout' | 'payment_succeeded' | 'payment_failed' | 'payment_canceled' | 'invoice_paid' | 'manual_review' | 'voided'; /** * Whether an invoice's `amount` is a figure at all. * * `unpriced` marks a **bootstrap placeholder**: the row exists to link the * local payment to the subscription, but nothing has priced it yet, so its * `amount` is not money. A hosted-checkout origination (Creem, PayPal) has no * order until the buyer pays, so the create response reports no invoice * figure. Render such a row as awaiting its first charge — never as `0.00`, * and never inside a total. * * `priced` means there is a real figure: what the processor charged once it * has said, and what it quoted before that (the two-step create path records * the estimate it is about to collect). It does **not** mean the cycle was * collected — `status` answers that, and the two move independently. * * **Not the API's `SubscriptionAmountSource`, and not interchangeable with * it.** That one qualifies a figure that exists (processor proration estimate * versus plan list price); this one says whether there is a figure to qualify * at all. Both can appear on the same subscription. (`SubscriptionAmountSource` * is not typed by this SDK yet.) * * Rows written before the field existed keep an unmarked zero and report * `priced`. That is deliberate: an unmarked zero cannot be told apart from a * genuinely zero-priced invoice, so history was not relabelled. Render those * as they come. */ type InvoiceAmountState = 'priced' | 'unpriced'; /** Billing interval unit for a subscription item price. */ type SubscriptionPeriodUnit = 'Day' | 'Week' | 'Month' | 'Year'; /** How the customer's saved payment method may be used for future payments. */ type FutureUsage = 'off_session' | 'on_session'; /** Item-type filter for `GET /subscriptions/items`. */ type SubscriptionItemType = 'plan' | 'addon'; /** * Payment leg attached to a subscription invoice. Present once a charge has * been attempted for the current billing cycle. */ interface SubscriptionPaymentData { payment_id: string; status: IntentStatus; amount: number; currency: Currency; profile_id?: string | null; connector?: string | null; payment_method_id?: string | null; return_url?: string | null; /** Next action to drive on the client (redirect, render QR, etc.). */ next_action?: Record | null; payment_experience?: string | null; error_code?: string | null; error_message?: string | null; payment_method_type?: PaymentMethodType | null; client_secret?: string | null; billing?: Address | null; shipping?: Address | null; payment_type?: string | null; payment_token?: string | null; } /** * Ask which of these payments were raised by a subscription. * * The linkage exists in one direction only: an invoice points at the payment it * settled, and nothing is stamped on the payment itself. So a caller holding a * page of payments — a transactions list, a reconciliation export — cannot tell * subscription charges from one-off ones without asking. * * Do **not** infer it from `off_session` or the presence of a mandate. A saved * card charged in the background looks identical there, and would be mislabelled * as a subscription. * * Batched on purpose: resolve a whole page in one call. The backend accepts at * most **200** ids per request. */ interface SubscriptionPaymentLookupRequest { payment_ids: string[]; } /** One resolved payment → subscription link. */ interface SubscriptionPaymentLink { /** The payment that was asked about. */ payment_id: string; /** The subscription that raised it. */ subscription_id: string; /** The invoice (billing cycle) the payment settles. */ invoice_id: string; invoice_status: InvoiceStatus; /** * Whether the invoice's `amount` is a figure at all — see * {@link InvoiceAmountState}. An `unpriced` cycle is awaiting its first * charge; never render its zero as money. */ amount_state: InvoiceAmountState; } interface SubscriptionPaymentLookupResponse { /** * Only the payments that resolved to a subscription, in no guaranteed order. * * An id that is absent is not an error — it means "not a subscription * payment". A page mixing the two is the normal case, so match on presence * rather than expecting one entry per id. */ links: SubscriptionPaymentLink[]; } /** A single invoice raised for one billing cycle of a subscription. */ interface SubscriptionInvoice { id: string; subscription_id: string; merchant_id: string; profile_id: string; merchant_connector_id: string; payment_intent_id?: string | null; payment_method_id?: string | null; customer_id: string; amount: number; /** * Whether `amount` is a figure at all — see {@link InvoiceAmountState}. * * Branch on this before rendering `amount` or adding it to a total: an * `unpriced` row is a bootstrap placeholder whose zero is not money. * Independent of `status`, which says whether the cycle was collected. */ amount_state: InvoiceAmountState; currency: Currency; status: InvoiceStatus; /** ID of this invoice on the billing processor (Stripe Billing / PayPal). */ billing_processor_invoice_id?: string | null; /** Connector (billing processor) this invoice was raised through, e.g. `paypal`. */ provider_name: Connector; /** * Refunded against this cycle so far, in the invoice's currency and minor units. * * `amount` stays **gross**: this is reported beside it and never subtracted * from it, because a netted figure cannot be told apart from a smaller charge. * * `null` means the billing processor reported no figure, which is not the * same as `0` — `0` says it answered and nothing was refunded. Render the * absence rather than defaulting it to zero. */ refunded_amount?: number | null; /** * Amount under dispute on this cycle, minor units. Null when no dispute * figure has been reported — which is not the same as `0`. */ disputed_amount?: number | null; /** ISO 8601 timestamp of when this invoice was recorded. */ created_at: string; } /** * One subscription's billing history, returned by * `GET /subscriptions/{id}/invoices`. */ interface SubscriptionInvoiceListResponse { /** Recorded billing cycles, newest first. */ invoices: SubscriptionInvoice[]; } /** Paging for {@link SubscriptionInvoiceListResponse}. */ interface SubscriptionInvoiceListParams { /** Maximum cycles to return. Clamped server-side to 100. */ limit?: number; /** Cycles to skip, counting from the newest. */ offset?: number; } /** * The billing processor a shop's subscriptions run on, returned by * `GET /subscriptions/billing_processor`. * * Identity only, deliberately not capabilities: how a processor originates a * subscription, whether its trials are configurable and whether a cancel takes * effect immediately are client-side concerns that belong in one place on the * client rather than half here. */ interface SubscriptionBillingProcessorResponse { /** The merchant connector account named as the shop's `billing_processor_id`. */ merchant_connector_id: string; /** The connector behind it, e.g. `creem`, `paypal` or `stripebilling`. */ connector_name: Connector; } /** A purchasable price option for a subscription item (plan or addon). */ interface SubscriptionItemPrice { price_id: string; /** Plan or addon ID this price belongs to. */ item_id?: string | null; amount: number; currency: Currency; interval: SubscriptionPeriodUnit; interval_count: number; trial_period?: number | null; trial_period_unit?: SubscriptionPeriodUnit | null; } /** A subscription item (plan or addon) with its available prices. */ interface SubscriptionItem { item_id: string; name: string; description?: string | null; /** Available prices for this item. */ price_id: SubscriptionItemPrice[]; } /** A single line of a subscription cost estimate. */ interface SubscriptionLineItem { item_id: string; item_type: string; description: string; amount: number; currency: Currency; quantity: number; } /** Payment options for `POST /subscriptions/create` (create without confirming). */ interface CreateSubscriptionPaymentDetails { /** URL the customer is returned to after completing the purchase. */ return_url: string; setup_future_usage?: FutureUsage | null; capture_method?: CaptureMethod | null; authentication_type?: AuthenticationType | null; payment_type?: string | null; } /** Adopt a subscription already running at Creem; adoption must be enabled. */ interface AdoptSubscriptionRequest { /** Only Creem is currently supported by this operation. */ billing_processor: 'creem'; merchant_connector_id?: string | null; external_subscription_id: string; /** Existing DeloPay customer, checked against the processor's buyer. */ customer_id: string; /** If supplied, checked against the processor's buyer and stored as its mapping. */ connector_customer_id?: string | null; /** If supplied, must match the processor's product or price. */ item_price_id?: string | null; plan_id?: string | null; merchant_reference_id?: string | null; } /** Result of taking an existing processor subscription into DeloPay. */ interface AdoptSubscriptionResponse { subscription_id: string; /** False when DeloPay already held the subscription. */ adopted: boolean; billing_processor: Connector; merchant_connector_id?: string | null; external_subscription_id?: string | null; status: SubscriptionStatus; customer_id: string; profile_id: string; merchant_reference_id?: string | null; plan_id?: string | null; item_price_id?: string | null; test_mode?: boolean | null; adopted_at?: string | null; /** * First post-insert confirmation persisted. Retry when false. This does not * attest that the merchant stopped its own processor event handling. */ handoff_confirmed: boolean; } /** Body for `POST /subscriptions/create`. */ interface CreateSubscriptionRequest { merchant_reference_id?: string | null; item_price_id: string; plan_id?: string | null; coupon_code?: string | null; customer_id: string; payment_details: CreateSubscriptionPaymentDetails; billing?: Address | null; shipping?: Address | null; } /** Payment options for `POST /subscriptions` (create + confirm in one call). */ interface SubscriptionPaymentDetails { payment_method?: PaymentMethod | null; payment_method_type?: PaymentMethodType | null; payment_method_data?: Record | null; setup_future_usage?: FutureUsage | null; customer_acceptance?: Record | null; /** URL the customer is returned to after completing the purchase. */ return_url?: string | null; capture_method?: CaptureMethod | null; authentication_type?: AuthenticationType | null; payment_type?: string | null; payment_method_id?: string | null; } /** Body for `POST /subscriptions` (create and immediately confirm). */ interface CreateAndConfirmSubscriptionRequest { plan_id?: string | null; item_price_id: string; coupon_code?: string | null; customer_id: string; billing?: Address | null; shipping?: Address | null; payment_details: SubscriptionPaymentDetails; merchant_reference_id?: string | null; } /** Payment options for `POST /subscriptions/{id}/confirm`. */ interface ConfirmSubscriptionPaymentDetails { shipping?: Address | null; billing?: Address | null; payment_method: PaymentMethod; payment_method_type?: PaymentMethodType | null; payment_method_data?: Record | null; customer_acceptance?: Record | null; payment_type?: string | null; payment_token?: string | null; } /** Body for `POST /subscriptions/{id}/confirm`. */ interface ConfirmSubscriptionRequest { /** Client secret minted at create time; required for client-side confirm. */ client_secret?: string | null; payment_details: ConfirmSubscriptionPaymentDetails; } /** Body for `PUT /subscriptions/{id}/update`. */ interface UpdateSubscriptionRequest { plan_id: string; item_price_id: string; } /** Body for `POST /subscriptions/{id}/pause`. */ interface PauseSubscriptionRequest { pause_option?: 'immediately' | 'end_of_term' | 'specific_date' | null; /** ISO-8601 timestamp; honoured when `pause_option` is `specific_date`. */ pause_at?: string | null; } /** Body for `POST /subscriptions/{id}/resume`. */ interface ResumeSubscriptionRequest { resume_option?: 'immediately' | 'specific_date' | null; resume_date?: string | null; charges_handling?: 'invoice_immediately' | 'add_to_unbilled_charges' | null; unpaid_invoices_handling?: 'no_action' | 'schedule_payment_collection' | null; } /** Body for `POST /subscriptions/{id}/cancel`. */ interface CancelSubscriptionRequest { cancel_option?: 'immediately' | 'end_of_term' | 'specific_date' | null; cancel_at?: string | null; unbilled_charges_option?: 'invoice' | 'delete' | null; credit_option_for_current_term_charges?: 'none' | 'prorate' | 'full' | null; account_receivables_handling?: 'no_action' | 'schedule_payment_collection' | 'write_off' | null; refundable_credits_handling?: 'no_action' | 'schedule_refund' | null; cancel_reason_code?: string | null; } /** Query for `GET /subscriptions/list`. */ interface SubscriptionListParams { limit?: number; offset?: number; /** Filter by environment: true = test only, false = live only, omit for all. */ test_mode?: boolean; } /** Query for `GET /subscriptions/items`. */ interface GetSubscriptionItemsParams { client_secret?: string; limit?: number; offset?: number; item_type: SubscriptionItemType; } /** Query for `GET /subscriptions/estimate`. */ interface SubscriptionEstimateParams { plan_id?: string; item_price_id: string; coupon_code?: string; } /** * A subscription, as returned by create / retrieve / update / list. The * confirm and create-and-confirm flows return {@link ConfirmSubscriptionResponse} * instead, which additionally carries the billing-processor subscription ID and * the approval `redirect_url`. */ interface SubscriptionResponse { id: string; merchant_reference_id?: string | null; status: SubscriptionStatus; plan_id?: string | null; item_price_id?: string | null; profile_id: string; /** Token (15-min TTL) used by the client SDK to authenticate confirm/session calls. */ client_secret?: string | null; merchant_id: string; coupon_code?: string | null; customer_id: string; payment?: SubscriptionPaymentData | null; invoice?: SubscriptionInvoice | null; /** * Whether the subscription runs against a sandbox/test billing connector. * Null for legacy subscriptions (treated as live). */ test_mode?: boolean | null; } /** * Returned by `POST /subscriptions` (create + confirm) and * `POST /subscriptions/{id}/confirm`. */ interface ConfirmSubscriptionResponse { id: string; merchant_reference_id?: string | null; status: SubscriptionStatus; plan_id?: string | null; item_price_id?: string | null; coupon?: string | null; profile_id: string; payment?: SubscriptionPaymentData | null; customer_id?: string | null; invoice?: SubscriptionInvoice | null; /** Subscription ID on the billing processor (Stripe Billing / PayPal). */ billing_processor_subscription_id?: string | null; /** * URL the customer must be redirected to in order to approve the * subscription, for billing processors that require buyer approval * (e.g. PayPal `APPROVAL_PENDING`). `null` for processors that activate the * subscription server-side (e.g. Stripe Billing with a saved payment method). */ redirect_url?: string | null; } /** Returned by `POST /subscriptions/{id}/pause`. */ interface PauseSubscriptionResponse { id: string; status: SubscriptionStatus; merchant_reference_id?: string | null; profile_id: string; merchant_id: string; customer_id: string; /** ISO-8601 timestamp the subscription was paused at. */ paused_at?: string | null; } /** Returned by `POST /subscriptions/{id}/resume`. */ interface ResumeSubscriptionResponse { id: string; status: SubscriptionStatus; merchant_reference_id?: string | null; profile_id: string; merchant_id: string; customer_id: string; /** ISO-8601 timestamp of the next billing cycle. */ next_billing_at?: string | null; } /** Returned by `POST /subscriptions/{id}/cancel`. */ /** * Whether a cancellation was performed the way it was asked for. A processor * can answer a scheduled cancel by cancelling immediately (Creem does, on a * store whose billing settings default to immediate) and name nothing about * the mode applied; the subscription really is cancelled, so the merchant is * owed a statement that the mode performed was not the mode requested. Four * states, because merging any pair misleads: "we cannot tell" (`unknown`, * render as no claim) is not "it matched", and a cancellation performed * *earlier* than asked is not the same event as one performed *later*. */ type CancelModeOutcome = 'as_requested' | 'executed_immediately' | 'executed_later' | 'unknown'; interface CancelSubscriptionResponse { id: string; status: SubscriptionStatus; merchant_reference_id?: string | null; profile_id: string; merchant_id: string; customer_id: string; /** ISO-8601 timestamp the subscription was cancelled at. */ cancelled_at?: string | null; /** Whether the mode performed was the mode requested — see {@link CancelModeOutcome}. */ cancel_mode_outcome: CancelModeOutcome; } /** A subscription item with its prices, as returned by `GET /subscriptions/items`. */ interface GetSubscriptionItemsResponse { item_id: string; name: string; description?: string | null; price_id: SubscriptionItemPrice[]; /** * Whether a subscription can be created on this item through this * response: the processor offers it and at least one price in `price_id` * could be stated. An unavailable item is still listed so the page stays * complete — its `price_id` is empty and it must not be offered. A PayPal * plan that is not active or is priced by volume or tiers, or a Stripe * product with no active recurring price with a single unit amount, is * unavailable. */ available: boolean; } /** * Provider-reported lifecycle details on a subscription snapshot. A missing * value is unknown, not false: the provider did not report it. */ interface LifecycleDetails { /** End of the current billing period, ISO 8601 in UTC. */ current_period_end?: string | null; /** Cancellation timestamp reported by the provider. */ cancelled_at?: string | null; /** Cancellation reason, when supplied. */ cancellation_reason?: string | null; /** * Whether cancellation takes effect at the end of the paid period. `true` * on a `subscription_cancelled` event whose `status` is still `active`: the * cancellation is scheduled, and the status moves when the period ends. */ cancel_at_period_end?: boolean | null; } /** * The payload of a subscription lifecycle event (`subscription_created`, * `subscription_active`, `subscription_past_due`, `subscription_paused`, * `subscription_resumed`, `subscription_cancelled`, `subscription_expired`, * `invoice_payment_failed`): an immutable snapshot of the subscription at the * transition, delivered as `content.object` under * `content.type === 'subscription_details'`. * * Neither `invoice_paid` nor `invoice_disputed` uses this shape: the first * keeps its {@link ConfirmSubscriptionResponse} payload, the second carries a * {@link SubscriptionDisputeWebhook}. `transition_id` is what marks a lifecycle * snapshot, and it is stable across delivery retries of one transition. */ interface LifecycleWebhook extends LifecycleDetails { /** The subscription id, same as `subscription_id`. */ id: string; subscription_id: string; merchant_reference_id?: string | null; customer_id: string; profile_id: string; status: SubscriptionStatus; /** Provider subscription identifier (Stripe Billing / PayPal). */ billing_processor_subscription_id?: string | null; plan_id?: string | null; item_price_id?: string | null; /** Identity of the logical transition, stable across delivery retries. */ transition_id: string; /** Environment inherited from the subscription. */ test_mode?: boolean | null; } /** * What `content.object` holds when `content.type === 'subscription_details'`. * * A paid invoice (`invoice_paid`) keeps its existing * {@link ConfirmSubscriptionResponse} serialization; every lifecycle event * carries a {@link LifecycleWebhook}; a cycle chargeback (`invoice_disputed`) * carries a {@link SubscriptionDisputeWebhook}. Use * {@link isSubscriptionLifecycleWebhook} and {@link isSubscriptionDisputeWebhook} * to narrow, or read `event_type`. */ type SubscriptionWebhookContent = ConfirmSubscriptionResponse | LifecycleWebhook | SubscriptionDisputeWebhook; /** * The payload of `invoice_disputed`: a chargeback recorded against a * subscription cycle the billing processor charged itself. * * A cycle has no payment behind it, so this names the subscription and invoice * where a {@link DisputeResponse} names a payment and attempt. The figures are * what the processor reported for this chargeback alone, never the cycle's * running total. The same chargeback is listed by `disputes.workspace()` with * `subscription_dispute_id` equal to `dispute_id`. */ interface SubscriptionDisputeWebhook { /** DeloPay's identifier for this chargeback. */ dispute_id: string; /** Subscription whose cycle was charged back. */ subscription_id: string; /** DeloPay invoice for the charged-back cycle. */ invoice_id: string; /** The billing processor's identifier for the cycle's charge. */ billing_processor_invoice_id?: string | null; customer_id: string; profile_id: string; /** Connector account the cycle was billed through. */ merchant_connector_id: string; /** Billing processor name. */ connector: string; /** The processor's identifier for this chargeback. */ connector_dispute_id: string; /** What this chargeback took, in `currency`'s minor units. */ amount: number; currency: Currency; /** Stage the processor reported; `null` when it reported none. */ dispute_stage?: DisputeStage | null; /** Status the processor reported; `null` when it reported none. */ dispute_status?: DisputeStatus | null; /** Whether this processor ever reports a chargeback's outcome. */ outcome_reporting: DisputeOutcomeReporting; /** Environment inherited from the subscription. */ test_mode?: boolean | null; /** When DeloPay first recorded this chargeback, ISO 8601 in UTC. */ created_at: string; } /** Returned by `GET /subscriptions/estimate`. */ interface SubscriptionEstimateResponse { amount: number; currency: Currency; plan_id?: string | null; item_price_id?: string | null; coupon_code?: string | null; customer_id?: string | null; line_items: SubscriptionLineItem[]; /** Billing interval unit, when the connector exposes it. */ interval?: SubscriptionPeriodUnit | null; /** Number of interval units per cycle, when known. */ interval_count?: number | null; } interface RegionCreateRequest { region_name: string; /** Short, unique code for the region (e.g. `"NORDICS"`). */ region_code: string; description?: string; } interface RegionUpdateRequest { region_name?: string; description?: string; is_active?: boolean; } interface RegionResponse { /** Region id (the API field is `id`, not `region_id`). */ id: string; profile_id: string; region_name: string; region_code: string; description?: string | null; is_active: boolean; created_at: string; modified_at: string; } /** Replace the full country membership of a custom region. */ interface RegionSetCountriesRequest { /** ISO 3166-1 alpha-2 country codes (e.g. `["SE", "NO", "DK"]`). */ countries: string[]; } interface RegionCountriesResponse { region_id: string; countries: string[]; } /** A built-in (global) region group such as EU / EEA / SEPA / LATAM / APAC. */ interface BuiltInRegionGroupResponse { /** Group code, e.g. `"EU"`. */ code: string; /** Human-readable group name. */ name: string; countries: string[]; } /** * What a placed item of the checkout's method layout stands for: a method one * of the shop's connector accounts publishes (`method`), the card form * (`card`), or a title the shop writes between methods (`heading`). */ type CheckoutMethodKind = 'method' | 'card' | 'heading'; /** Where a redirect method opens when tapped. Mirrors {@link PaneOpenTarget}. */ type CheckoutMethodOpenTarget = 'same_page' | 'tab' | 'popup'; /** Which buyer surface shows an item. Mirrors {@link PaneVisibility}. */ type CheckoutMethodVisibility = 'always' | 'embedded_only' | 'external_only'; /** * One item of a shop's payment-method layout, as stored. `connector` and * `method` identify a `method` item (`stripe` + `apple_pay`, `cryptomus` + * `crypto`); every other field is an optional override of what the router's * catalogue prints on its own. A `sublabel` of `''` means "no second line", * an absent one means "the catalogue's". `label` is the text of a `heading`. */ interface CheckoutMethodItem { kind?: CheckoutMethodKind; connector?: string | null; method?: string | null; label?: string | null; label_translations?: Record; sublabel?: string | null; sublabel_translations?: Record; icon?: string | null; icon_svg?: string | null; preset?: string | null; style?: string | null; /** Defaults to `same_page`. */ open_in?: CheckoutMethodOpenTarget; /** Defaults to `always`. */ visibility?: CheckoutMethodVisibility; /** * `method` and `card` items: every payment from this tile, or the card * form, goes to this one account instead of the shop's routing. */ merchant_connector_id?: string | null; /** * A wallet button shown even while another provider draws the card form. * Only on an item whose available entry sets * {@link AvailableCheckoutMethod.supports_always_show} — a button Stripe * draws on its own PaymentIntent, or NomuPay's Google Pay on a checkout of * its own — and beside one of the card forms that entry's * {@link AvailableCheckoutMethod.always_show_beside} names. Absent means * `false`. */ always_show?: boolean; /** `heading` only. Where the title sits on its line; absent is `left`. */ align?: CheckoutHeadingAlign | null; /** `heading` only. How the divider beside the title is drawn; absent is `solid`. */ line?: CheckoutHeadingLine | null; /** `heading` only. Absent is `small`. */ text_size?: CheckoutHeadingTextSize | null; /** `heading` only. Absent is `semibold`. */ text_weight?: CheckoutHeadingTextWeight | null; /** `heading` only. Absent is `uppercase`. */ text_case?: CheckoutHeadingTextCase | null; /** `heading` only. Absent is `muted`. */ text_tone?: CheckoutHeadingTextTone | null; /** `card` only. `false` hides the card form's divider line; absent draws it. */ divider?: boolean | null; /** * An Apple Pay or Google Pay button only: the colour of the wallet's own * button. Apple Pay takes `black`, `white`, `white_outline`; Google Pay * takes `black`, `white`, `default`. Absent leaves it to the provider. */ button_color?: CheckoutWalletButtonColor | null; /** * An Apple Pay or Google Pay button only: the word on the button. Google * Pay takes the first eight; Apple Pay every word. Absent is the provider's. */ button_type?: CheckoutWalletButtonType | null; } /** Where a heading's text sits on its line. */ type CheckoutHeadingAlign = 'left' | 'center' | 'right'; /** How a heading's divider is drawn. */ type CheckoutHeadingLine = 'solid' | 'dashed' | 'none'; /** How large a heading's text is drawn. */ type CheckoutHeadingTextSize = 'small' | 'medium' | 'large'; /** How heavy a heading's text is drawn. */ type CheckoutHeadingTextWeight = 'regular' | 'semibold'; /** A heading in capitals, or as the shop wrote it. */ type CheckoutHeadingTextCase = 'uppercase' | 'as_written'; /** A heading in the theme's secondary text colour, or its text colour. */ type CheckoutHeadingTextTone = 'muted' | 'strong'; /** The colour of a wallet's own button. */ type CheckoutWalletButtonColor = 'black' | 'white' | 'white_outline' | 'default'; /** The word on a wallet's own button; `plain` is the mark alone. */ type CheckoutWalletButtonType = 'plain' | 'buy' | 'pay' | 'checkout' | 'book' | 'order' | 'subscribe' | 'donate' | 'continue' | 'contribute' | 'support' | 'tip' | 'add_money' | 'top_up' | 'reload' | 'rent'; /** * The three tiers of a shop's checkout: the express strip (at most two rows * of two wallet buttons), the main list, and the "other payment methods" * list folded behind a disclosure. */ interface CheckoutMethodLayout { express?: CheckoutMethodItem[][]; normal?: CheckoutMethodItem[]; alternative?: CheckoutMethodItem[]; } /** Whether a layout was read from the shop's own row or derived from its connector accounts. */ type CheckoutMethodLayoutSource = 'stored' | 'derived'; /** * One item the shop may place: a method one of its accounts offers, or its * card form. Carries the router's default copy per locale, so an editor can * show what a buyer reads where the shop writes nothing. */ interface AvailableCheckoutMethod { kind: CheckoutMethodKind; connector?: string | null; connector_display_name?: string | null; method?: string | null; rail?: PaneRail | null; payment_method?: string | null; payment_method_type?: string | null; default_label?: string; default_sublabel?: string; default_label_translations?: Record; default_sublabel_translations?: Record; default_category: string; default_icon?: string | null; default_icon_svg?: string | null; /** May sit in the express strip (draws the wallet's own button). */ supports_native_element: boolean; /** Follows a redirect when tapped, so `open_in` applies. */ supports_open_target: boolean; /** * Whether an enabled account offers it. `false` when only switched-off * accounts do: the item may be placed and a save keeps it, but the * checkout skips it until one of those accounts is switched back on. * Absent from a router that predates the flag, which means `true`. */ account_enabled?: boolean; /** * Experimental and opt-in: no derived layout places it, so a shop gets it * only by placing it. Absent means `false`. */ experimental?: boolean; presets?: PanePresetCapability[]; /** * Smart Routing may choose the account for this method's tile. On the card * entry: whether this environment lets the card form's account be chosen. */ routable?: boolean; /** * The accounts of this item's connector that offer the method, enabled * first: the choices for {@link CheckoutMethodItem.merchant_connector_id}. * On the card entry, every account that can draw the card form here. */ accounts?: CheckoutMethodAccount[]; /** When `routable`: every enabled account Smart Routing chooses among for this tile. */ routing_candidates?: CheckoutMethodAccount[]; /** Candidates that cannot take this tile's payment as it is sent; while any is listed the tile is not routed. */ routing_blocked_by?: CheckoutMethodAccount[]; /** * The item may set {@link CheckoutMethodItem.always_show}: a wallet button * that can be given an object of its own beside another provider's card * form — one Stripe draws on its own PaymentIntent, or NomuPay's Google Pay * on a checkout of its own. Absent from an older router, which means * `false`. Whether this deployment draws it anywhere is * {@link AvailableCheckoutMethod.always_show_beside}. */ supports_always_show?: boolean; /** * The card forms, by connector (`paypal`, `nomupay_oppwa`, `stripe`), beside * which this deployment draws the item's button when it is set to * {@link CheckoutMethodItem.always_show}. * * A save accepts `always_show` on an item that names one here — a * smart-routed item when any provider of its wallet does — **or on one the * shop's stored layout already set to always show**. So an editor must not * reject or drop an existing flag because this list no longer names a * compatible form; a retained flag stays saveable. Empty for an item that is * not {@link AvailableCheckoutMethod.supports_always_show}, and absent from a * router that predates the field. */ always_show_beside?: string[]; /** Where buyers are offered it, order value aside; absent from an older router. */ coverage?: CheckoutMethodCoverage | null; } /** One connector account, as the router names it beside a placeable item. */ interface CheckoutMethodAccount { merchant_connector_id: string; /** `merchant_connector_account.connector_name`. */ connector: string; connector_label?: string | null; /** `false` for an account whose routing is switched off: a tile kept on it is not shown. */ enabled: boolean; } /** Why a buyer would not be offered an item, in the order the checkout render asks. */ type CheckoutMethodHiddenReason = 'shop_rule' | 'country_default' | 'provider_country' | 'provider_currency' | 'provider_order_value' | 'provider_switched_off' | 'provider_unavailable'; /** Where buyers are offered a placeable item, order value aside. */ interface CheckoutMethodCoverage { /** The countries a buyer is offered it in (ISO 3166-1 alpha-2), or absent for every country less `hidden_in`. */ countries?: string[] | null; /** With `countries` absent: the few countries it is nonetheless not offered in. */ hidden_in?: string[]; /** What limits it to fewer countries than every one; empty when nothing does. */ limited_by?: CheckoutMethodHiddenReason[]; /** A shop availability rule for the method depends on the order value. */ depends_on_order_value?: boolean; } /** `GET` / `PUT /checkout-methods/{profile_id}`. */ interface CheckoutMethodsResponse { profile_id: string; source: CheckoutMethodLayoutSource; layout: CheckoutMethodLayout; /** * Every item this shop may place, placed or not, including what only its * switched-off accounts offer (`account_enabled: false`). */ available: AvailableCheckoutMethod[]; modified_at?: string | null; } /** `PUT /checkout-methods/{profile_id}`: replaces the whole layout. */ interface CheckoutMethodsUpdateRequest { layout: CheckoutMethodLayout; } /** `DELETE /checkout-methods/{profile_id}`: the shop is back on the derived layout. */ interface CheckoutMethodsResetResponse { profile_id: string; deleted: boolean; } /** * One item of the layout as the buyer-facing checkout receives it on the * payment-link payload (`checkout_methods`): a `method` item carries its * resolved {@link PaneView}, a `heading` its localized text. */ interface CheckoutMethodView { kind: CheckoutMethodKind; pane?: PaneView | null; label?: string | null; /** `heading` only; see {@link CheckoutMethodItem}. */ align?: CheckoutHeadingAlign | null; line?: CheckoutHeadingLine | null; text_size?: CheckoutHeadingTextSize | null; text_weight?: CheckoutHeadingTextWeight | null; text_case?: CheckoutHeadingTextCase | null; text_tone?: CheckoutHeadingTextTone | null; /** `card` only; see {@link CheckoutMethodItem}. */ divider?: boolean | null; /** A wallet button's colour and word; see {@link CheckoutMethodItem}. */ button_color?: CheckoutWalletButtonColor | null; button_type?: CheckoutWalletButtonType | null; } /** The arranged, availability-filtered layout the checkout draws. */ interface CheckoutMethodsView { express?: CheckoutMethodView[][]; normal?: CheckoutMethodView[]; alternative?: CheckoutMethodView[]; /** The buyer country the layout was filtered for, when one resolved. */ country?: string | null; } /** Scope at which an availability override applies. More specific wins: shop > project > global. */ type OverrideScope = 'global' | 'project' | 'shop'; /** Whether an override force-shows or force-hides the targeted method. */ type OverrideAction = 'force_show' | 'force_hide'; /** * Create a merchant payment-method availability override. Exactly one of * `country` / `region_code` must be set; `scope_id` is required for `project` * and `shop` scope. */ interface AvailabilityOverrideCreateRequest { merchant_id: string; scope: OverrideScope; /** Project id (for `project` scope) or profile id (for `shop` scope); omit for `global`. */ scope_id?: string; payment_method: PaymentMethod; /** Omit to target every type of the payment method. */ payment_method_type?: PaymentMethodType; /** Target a single country (ISO 3166-1 alpha-2). Mutually exclusive with `region_code`. */ country?: string; /** Target a built-in group code (e.g. `"EU"`) or a custom region id. Mutually exclusive with `country`. */ region_code?: string; /** * Inclusive lower bound on the order value, in minor units. `null`/omitted is * unbounded. Requires `currency`. */ min_amount?: number; /** * Inclusive upper bound on the order value, in minor units. `null`/omitted is * unbounded. Requires `currency`. */ max_amount?: number; /** * Currency the amount bounds are expressed in. Required when either bound is * set. There is no FX conversion — the rule applies only to payments in this * currency. */ currency?: Currency; action: OverrideAction; } interface AvailabilityOverrideResponse { id: string; merchant_id: string; scope: OverrideScope; scope_id?: string | null; payment_method: PaymentMethod; payment_method_type?: PaymentMethodType | null; country?: string | null; region_code?: string | null; min_amount?: number | null; max_amount?: number | null; currency?: Currency | null; action: OverrideAction; } /** Query for {@link AvailabilityOverrides.preview}. */ interface AvailabilityPreviewParams { merchant_id: string; /** Shop / business profile id to preview. */ profile_id: string; /** ISO 3166-1 alpha-2 country to preview for; omit for no country signal. */ country?: string; /** Order value to preview for, in minor units. Requires `currency`. */ amount?: number; /** Currency of `amount`. */ currency?: Currency; } /** One payment method available in the previewed country, with its types. */ interface AvailabilityPreviewMethod { payment_method: PaymentMethod; payment_method_types: PaymentMethodType[]; } /** * The methods a customer in the previewed country would be shown — the connector * ceiling narrowed by the curated smart defaults and the merchant overrides, * including their order-value rules when `amount` + `currency` are supplied. The * connector's own amount limits are not applied, so this stays an upper bound. */ interface AvailabilityPreviewResponse { /** The country the preview resolved for (echoes the request). */ country?: string | null; /** The order value the preview resolved for (echoes the request). */ amount?: number | null; /** The currency the preview resolved for (echoes the request). */ currency?: Currency | null; payment_methods: AvailabilityPreviewMethod[]; } /** * A whole sell-to restriction policy, replaced at once: the merchant-wide one, * or one shop's override. Countries left out are no longer refused. */ interface SellToRestrictionPolicyRequest { /** * Buyer countries to refuse, as ISO 3166-1 alpha-2 codes. Duplicates are * ignored; an empty list refuses no country. */ blocked_countries: string[]; /** * Refuse a payment whose buyer IP country cannot be resolved: a private or * unresolvable address, or a server-to-server payment that states no buyer * IP. Defaults to `false`. */ block_unknown_ip_country?: boolean; } /** A stored sell-to restriction policy. */ interface SellToRestrictionPolicy { /** ISO 3166-1 alpha-2 codes, sorted. */ blocked_countries: string[]; block_unknown_ip_country: boolean; modified_at: string; } /** * Where a shop's effective policy comes from: the merchant-wide policy it * inherits, its own override (which replaces the merchant policy for that * shop), or neither. */ type SellToRestrictionSource = 'merchant' | 'shop' | 'none'; /** One shop's sell-to restriction, as a payment in that shop meets it. */ interface ShopSellToRestriction { profile_id: string; profile_name: string; source: SellToRestrictionSource; /** The policy payments in this shop are checked against. Absent when `source` is `none`. */ effective_policy?: SellToRestrictionPolicy | null; } /** Response of {@link SellToRestrictions.retrieve}. */ interface SellToRestrictionsResponse { /** * The merchant-wide policy every shop inherits. Absent for a caller scoped * to one shop, and for a merchant that has none. */ merchant_policy?: SellToRestrictionPolicy | null; /** Every shop the caller may see: all of the merchant's shops, or only the caller's own. */ shops: ShopSellToRestriction[]; } /** Where a payment was refused: the hosted checkout's render, or a confirm before any processor was called. */ type SellToRestrictionStage = 'checkout_render' | 'confirm'; /** * What refused a payment. `ip_country_unverified` means the buyer's country * arrived on a connection that could not prove it was allowed to state it, * so it was not believed. */ type SellToRestrictionSignal = 'ip_country' | 'ip_country_unknown' | 'ip_country_unverified' | 'card_issuing_country'; /** One refusal recorded against a payment. */ interface SellToRestrictionDecisionResponse { stage: SellToRestrictionStage; signal: SellToRestrictionSignal; /** The country the signal named. Absent for the unknown and unverified signals. */ country?: string | null; /** Which policy refused the payment. */ policy_source: SellToRestrictionSource; /** Absent when the checkout refused to render, before any attempt existed. */ attempt_id?: string | null; connector?: string | null; created_at: string; } /** Response of {@link SellToRestrictions.listDecisions}: a payment's refusals, oldest first. */ interface SellToRestrictionDecisionsResponse { payment_id: string; decisions: SellToRestrictionDecisionResponse[]; } /** * How a payment path checks the buyer's IP country: at the hosted checkout * and on the buyer's own confirm, at the hosted checkout only, or only as the * IP address the merchant's server states for the buyer. */ type IpCountryEnforcement = 'checkout_and_confirm' | 'checkout_only' | 'merchant_stated_ip'; /** Whether, and when, a payment path checks the card's issuing country. */ type CardIssuingCountryEnforcement = 'before_authorization' | 'not_enforced' | 'not_applicable'; /** A payment path, as far as sell-to restrictions are concerned. */ type SellToRestrictionRail = 'api_payments' | 'vault_hosted_card' | 'stripe_hosted_card' | 'stripe_wallets' | 'paypal_card_fields' | 'paypal_wallet' | 'airwallex_hosted_card' | 'nomupay_hosted_card' | 'redirect_methods'; /** What one payment path checks. */ interface SellToRestrictionRailCapability { rail: SellToRestrictionRail; ip_country: IpCountryEnforcement; card_issuing_country: CardIssuingCountryEnforcement; } /** How this deployment resolves a buyer's IP country. */ interface IpCountryResolution { /** The edge in front of the API states the visitor's country. */ edge_country_header: boolean; /** A geo-IP database is loaded, so an IP address the merchant states can be resolved. */ geoip_database: boolean; /** * The hosted checkout's server signs the buyer country it forwards. Without * it no forwarded country is believed, and a shop with a restriction refuses * every hosted checkout. */ signed_checkout_forwarding: boolean; } /** Response of {@link SellToRestrictions.capabilities}. */ interface SellToRestrictionCapabilitiesResponse { ip_country_resolution: IpCountryResolution; rails: SellToRestrictionRailCapability[]; } /** * Top-level permission group recognized by the Delopay API. The * dashboard (or any client) uses these to hide UI elements for actions * the caller's role cannot perform, via `getRolePermissions()`. */ type ParentGroup = 'Operations' | 'Refunds' /** * Connector accounts. The only group offering the `Edit` scope, for a role * that may add and maintain connectors but must not delete one — see * {@link PermissionScope}. */ | 'Connectors' | 'Workflows' | 'Analytics' | 'Users' | 'Account' | 'ReconOps' | 'ReconReports' | 'Internal' | 'Theme' | 'ShopDetails' | 'ApiKeys' | 'Impersonation' | 'Settlement' /** * Whether the caller may open a transaction in the payment provider's own * dashboard. Gates discoverability of the deep link, not access — the id it * is built from is already on every payment, and the provider authenticates * the user itself. `Read` is the only scope. */ | 'ConnectorDashboard' /** The merchant's own audit log. Read-only — an audit trail is never edited. */ | 'AuditLog' /** The hosted checkout's appearance. */ | 'CheckoutBranding' /** Carved out of `Operations`. */ | 'Transactions' /** Carved out of `Operations`. */ | 'Customers' /** Carved out of `Operations`. */ | 'Subscriptions' /** Carved out of `Operations`. Read-only: payouts define no write scope. */ | 'Payouts' /** Carved out of `Operations`. */ | 'Mandates' /** Carved out of `Operations`. */ | 'Disputes' /** Carved out of `Analytics`: the payment-performance surface. */ | 'AnalyticsPayments' /** Carved out of `Analytics`: the recurring-revenue surface. */ | 'AnalyticsSubscriptions' /** Carved out of `Analytics`: the devices & browsers surface. */ | 'AnalyticsDevices' /** Carved out of `Analytics`: the countries & cities surface. */ | 'AnalyticsGeo' /** Carved out of `Workflows`. */ | 'Routing' /** Carved out of `Workflows`. */ | 'Surcharges' /** Carved out of `Workflows`. */ | 'ThreeDsDecision' /** Carved out of `Workflows`. */ | 'MethodAvailability' /** What `Account` narrows to: the merchant's own details, shops and webhooks. */ | 'MerchantSettings' /** Carved out of `Account`. */ | 'FeeSchedule' /** Carved out of `Account`: the prepaid balance and its ledger. */ | 'BalanceLedger' /** Carved out of `Account`. */ | 'Blocklist' /** * Which sites may embed the hosted checkout (`iframe_allowed_origins`, the * checkout's `frame-ancestors` CSP). Carved out of `Account` so "may rename * a shop" and "may decide who can frame our checkout" are separately * grantable. */ | 'IframeOrigins' /** * Buyer countries the merchant refuses to sell to. `Read` sees the * restrictions and why a payment was refused, `Edit` overrides a shop's * policy, and `Write` changes the merchant-wide policy. */ | 'SellToRestrictions'; /** * What a role may do to the resources a {@link ParentGroup} names. * * A **ladder**, not a set: a scope carries every scope below it, so a role with * `Write` also satisfies `Edit` and `Read`. Order is `Read` < `Edit` < `Write`. * * - `Read` — view only. * - `Edit` — create and update, but **not** delete. * - `Write` — create, update and delete. * * `Edit` is offered where a resource defines it: `Connectors`, `Customers`, * `Subscriptions`, `Settlement` and `CheckoutBranding`. It was added beneath `Write` rather than a `Delete` * scope being added above it, so `Write` still means exactly what it always * did and no existing role's powers changed — treat `Write` as full control, * including deletion, as before. * * A role holding `Edit` may add and maintain the area's records but cannot * remove one. */ type PermissionScope = 'Read' | 'Edit' | 'Write'; /** * One entry in the response of `GET /user/role/v3`. The caller's role * has the listed `scopes` for the resources implied by `name`. */ interface ParentGroupInfo { name: ParentGroup; resources: string[]; scopes: PermissionScope[]; } /** One connector account named by a role's grant. */ interface RoleConnectorGrantEntry { /** The connector **account** id (`mca_…`), never a connector name. */ merchant_connector_id: string; /** Who added this row. */ created_by: string; /** Free-text note recorded when the row was added. */ reason?: string | null; } /** * A role's connector grant, from `GET /user/role/{roleId}/connectors`. * * **Read `restricted` before `connectors`.** An empty `connectors` list is * ambiguous on its own — "unrestricted" and "sees nothing" are opposites — so * the backend reports which one it is explicitly and callers must not infer it * from the array length. * * `restricted: false` means the role holds no grant and sees whatever its * entity and profile scope already allowed. That is every role that has not * been given a grant, so it is the normal case, not an edge case. * * A connector deleted after being granted leaves its id here, where it matches * nothing at enforcement time and is therefore invisible. It is reported so an * operator can see exactly what is stored and clear it. */ interface RoleConnectorGrant { role_id: string; restricted: boolean; connectors: RoleConnectorGrantEntry[]; } /** Body of `PUT /user/role/{roleId}/connectors`. */ interface UpdateRoleConnectorGrantParams { /** * The complete set the role may see, **replacing** whatever is stored. * * An empty array clears the grant and returns the role to unrestricted. It * does *not* mean "this role sees no connectors" — that is expressed by * removing the role's connector permission group, not here. */ merchant_connector_ids: string[]; /** Free-text note recorded on each row this edit adds. */ reason?: string; } /** * Query for the scoped analytics endpoint. The optional ids select the drill * level. For the merchant dashboard (`analytics.scope`) `merchant_id` is * ignored — the server pins the scope to the authenticated merchant — so only * `project_id` / `shop_id` (drill) and the window/section fields are honoured. */ /** Series bucket size of the payments dashboard. */ type AnalyticsGranularity = 'day' | 'hour' | '30m' | '15m' | '5m'; /** The dashboards' four outcomes; `open` is processing + awaiting action. */ type AnalyticsOutcome = 'success' | 'open' | 'failed' | 'void'; /** Checkout channel token, as the device dashboard's channel series names them. */ type AnalyticsChannel = 'hosted' | 'embedded' | 'auto_redirect' | 'api' | 'dashboard' | 'sdk' | 'unknown'; /** Sort keys of the payments drill (`GET /analytics/scope/transactions`). */ type ScopeDrillSortKey = 'created_at' | 'payment_created_at' | 'amount' | 'status' | 'connector' | 'method'; /** Sort keys of the geo and device drills: the payments keys plus `time_to_pay`. */ type ClientDrillSortKey = ScopeDrillSortKey | 'time_to_pay'; /** Sort keys of the subscription drill. */ type SubscriptionDrillSortKey = 'created_at' | 'payment_created_at' | 'amount' | 'status' | 'connector' | 'plan'; /** Every sort key any drill accepts; each request narrows it to its own endpoint's set. */ type DrillSortKey = ClientDrillSortKey | SubscriptionDrillSortKey; /** * The drawer's list controls, shared by every drill-through list. Applied * server-side over the whole match — the list is paged, so a client-side * sort or search would only ever cover the loaded page. */ interface DrillListControls { /** * Free-text search (case-insensitive substring) over the ids a reader has * in front of them: payment id, customer id, description, the processor's * transaction reference and the shop name (invoice / subscription id on * the subscription drill). */ q?: string | null; /** * Sort key; `created_at` (newest first) by default — the observation time * on geo/device, the cycle on subscriptions; `payment_created_at` is the * payment's own time everywhere. `amount` sorts on USD. Each request * narrows this to the keys its endpoint accepts — the server answers * `400` to any other. */ sort_on?: SortKey | null; /** `asc` | `desc` (default `desc`). */ sort_by?: 'asc' | 'desc' | null; /** * Row filter: an outcome token or one raw intent status (one raw invoice * status on the subscription drill). */ status?: AnalyticsOutcome | string | null; /** Row filter: processor name, `"Direct"` included. */ connector?: string | null; /** Row filter: high-level payment method; `"none"` = no attempt. */ method?: string | null; /** Row filter: checkout channel token. */ channel?: AnalyticsChannel | null; /** Row filter: ISO currency code. */ currency?: string | null; /** Page size, default 50, at most 200. */ limit?: number | null; /** Offset into the sorted list. `total` in the response is the full match count. */ offset?: number | null; } /** The window / scope / chip part of a payments drill. */ interface ScopeDrillBase extends DrillListControls { days?: number | null; start_date?: string | null; end_date?: string | null; /** Ignored (server-pinned) on `analytics.scopeTransactions`. */ merchant_id?: string | null; project_id?: string | null; shop_id?: string | null; test_mode?: boolean | null; /** * The bucket size the series was drawn in, so a sub-day `target_bucket` * spans the right interval. Defaults to `"hour"` for a timestamp bucket and * `"day"` for a date. */ granularity?: AnalyticsGranularity | null; /** Active chip filters, so the drill lists what the charts counted. */ country?: string | null; country_source?: string | null; device_class?: string | null; /** * Which clicked metric this drill is for, so the list reproduces the number * that was clicked. * * Analytics exclusions are per surface, so the conversion, success-rate and * volume cohorts can each exclude a different set of payments. Send the * cohort of the widget the user clicked; the count, the rows and the * whole-match summary are all computed under it. Defaults to * `'conversion'`, which is what the server did before the split. */ cohort?: ScopeDrillCohort | null; } /** The targets that stand on their own and combine freely. */ interface ScopeDrillFreeTargets { /** Clicked processor slice (`connectors[].name`, `"Direct"` included). */ target_connector?: string; /** Clicked outcome segment of a series bar. */ target_outcome?: AnalyticsOutcome; /** * Clicked series bucket, in the shape the series describes: `YYYY-MM-DD` * for a day, `YYYY-MM-DDTHH:MM:00Z` for a sub-day bucket. Narrows every * other target to that bucket. */ target_bucket?: string; } /** * A method type only ever qualifies a method: either both travel, or * neither. The `?: never` arm is what keeps an orphan `target_method_type` * out on every other combination of targets, not just when the method arm * is the one satisfying "at least one". */ type ScopeDrillMethodPart = { /** Clicked payment-method slice (`methods[].method`); `"none"` = the null-method slice. */ target_method: string; /** Narrows `target_method` to one `methods[].method_type`. */ target_method_type?: string; } | { target_method?: never; target_method_type?: never; }; /** * A child id without its kind cannot be resolved (the server matches * nothing rather than everything), so the pair is one unit on every arm. */ type ScopeDrillChildPart = { /** Clicked breakdown row, with the kind needed to resolve it. */ target_child: string; target_child_kind: 'merchant' | 'project' | 'shop'; } | { target_child?: never; target_child_kind?: never; }; type ScopeDrillTargets = ScopeDrillFreeTargets & ScopeDrillMethodPart & ScopeDrillChildPart; /** * The clicked target(s) of a payments drill. **At least one**, and they * combine — a connector *and* an outcome is "that connector's failed * payments" — so unlike the subscription drill this is "one or more", not * "exactly one". What the union refuses is a request with no target at all * (`{}` would otherwise list the whole window, which no click means), a * `target_method_type` with no `target_method`, and a child id with no kind * — the last two on every arm, because the pair rules are intersected into * each of them rather than living only on the arm that names the pair. */ type ScopeDrillTarget = (ScopeDrillTargets & { target_connector: string; }) | (ScopeDrillTargets & { target_method: string; }) | (ScopeDrillTargets & { target_outcome: AnalyticsOutcome; }) | (ScopeDrillTargets & { target_bucket: string; }) | (ScopeDrillTargets & { target_child: string; target_child_kind: 'merchant' | 'project' | 'shop'; }); /** Query for the payments drill-through list: `GET /analytics/scope/transactions`. */ type ScopeDrillRequest = ScopeDrillBase & ScopeDrillTarget; interface AnalyticsScopeRequest { /** Trailing days for a rolling window ending now. Default 7, capped at 90. */ days?: number | null; /** Naive ISO 8601 datetime for the window start (custom range; span ≤ 90 days). */ start_date?: string | null; /** Naive ISO 8601 datetime for the inclusive last day. Defaults to now. */ end_date?: string | null; /** Scope to a single merchant. Ignored (server-pinned) on `analytics.scope`. */ merchant_id?: string | null; /** Scope to one project's shops. */ project_id?: string | null; /** Scope to a single shop; wins over project_id. */ shop_id?: string | null; /** * Comma-separated selector for which response blocks the server computes, so * a standalone widget (e.g. a pinned card) skips the query/fold work it * doesn't need. Tokens: `series` (current daily series — KPI values + chart), * `previous` (the equal-length prior window — period-over-period deltas), * `connectors` (processor donut), `children` (breakdown table + merchant * donut) and `methods` (payment-method donut). Omit for the full dashboard * (all blocks). Unrequested blocks come back as empty arrays. */ sections?: string | null; /** * Filter by test mode: `false` returns only live payments (the usual view * for real analytics), `true` only test/sandbox payments. Omit to include * both. Applies to the payment series/connectors; refunds are always included. */ test_mode?: boolean | null; /** * Series bucket size: `"day"` (default), `"hour"`, `"30m"`, `"15m"` or * `"5m"`. Any sub-day grain over any window, as long as the series stays * within the bucket ceiling of 2208 (an hourly view of the widest 92-day * window; a 5-minute view of a week) — past it the request is rejected with * the count it would have needed. Sub-day buckets are UTC and always * aggregate live. Older backends accept only `"day"` / `"hour"` and cap * hourly at a 48-hour window. */ granularity?: AnalyticsGranularity | null; /** * Restrict to payments from one country (ISO alpha-2), matched against the * claim `country_source` names. A filtered request always aggregates live * (the rollup is unfiltered); payments without a client-context observation * don't match ip/device filters (`FilterRequiresClientContext` caveat). */ country?: string | null; /** Which country claim `country` filters on: `"ip"` (default) or `"billing"`. */ country_source?: string | null; /** * Restrict to payments of one device class: `phone` | `tablet` | `desktop` * | `unknown` (rows whose render predates classification). */ device_class?: string | null; } /** * One bucket of aggregated payment activity — a calendar day, or a single UTC * hour when the response's `granularity` is `"hour"`. Volumes are in USD major * units. */ interface AnalyticsDayBucket { tx: number; success: number; vol: number; refunds: number; refund_vol: number; failed_tx: number; processing_tx: number; action_tx: number; failed_vol: number; processing_vol: number; action_vol: number; /** Payments that timed out unpaid (absent on older backends). */ expired_tx?: number; expired_vol?: number; /** * Success-rate denominator, scoped **independently** of `tx`. * * `tx` / `success` stay the conversion cohort. An analytics exclusion can * apply to one surface and not the other, so the two pairs can legitimately * disagree — divide `success_rate_success` by `success_rate_tx` for a * success rate, never `success` by `tx`. Absent on backends that predate * the split. Neither pair is a financial or transaction-list total. */ success_rate_tx?: number; /** Success-rate numerator; see `success_rate_tx`. */ success_rate_success?: number; } /** One day of a processor's volume, split by outcome (major units). */ interface AnalyticsConnectorDay { success: number; failed: number; open: number; /** Timed-out-unpaid volume, reported separately from `failed` (absent on * older backends, where it is folded into `failed`). */ expired?: number; } /** One processor's daily, status-split volume series over the window. */ interface AnalyticsConnectorSeries { name: string; days: AnalyticsConnectorDay[]; } /** A direct child of the current scope (merchant / project / shop). */ interface AnalyticsChild { id: string; name: string; /** "merchant" | "project" | "shop". */ type: string; badge: string; /** Owning merchant name (for grouping the merchant donut). */ merchant: string; /** Aggregate of the child's activity over the current window (USD). */ totals: AnalyticsDayBucket; } /** * One payment method's status-split volume totals over the current window * (USD major units), for the "volume by payment method" donut. `method` is the * attempt's high-level `payment_method` (`card`, `wallet`, …) and * `method_type` its narrower `payment_method_type` (`credit`, `apple_pay`, * `ideal`, …); both are null for intents that never reached an attempt * (typically abandoned open volume). */ interface AnalyticsMethodSlice { /** Processor that handled the volume (`"Direct"` when the attempt carried no * connector), matching the `connectors` block's naming — so the method donut * can be filtered per processor. */ connector: string; method?: string | null; method_type?: string | null; success: number; failed: number; open: number; expired?: number; } /** * One drill level of the analytics dashboard: the scope's own daily series + * processor mix and its direct children's series. Range / metric / donut * toggles apply client-side; only a drill fetches the next level. * `end_date` is the inclusive last day as an ISO date (`YYYY-MM-DD`). */ interface AnalyticsScopeResponse { currency: string; days: number; end_date: string; /** "root" | "merchant" | "project" | "shop". */ level: string; /** * Bucket size of `series` / `previous_series` / `connectors[].days`: `"day"`, * `"hour"`, `"30m"`, `"15m"` or `"5m"` (UTC, sub-day). Mirrors the request. */ granularity: AnalyticsGranularity; /** * UTC instant of the first bucket's start for a sub-day `granularity` * (`YYYY-MM-DDTHH:MM:00Z` — the window start floored to the bucket); absent * for daily buckets, where `end_date` + `days` describe the axis. */ bucket_start?: string | null; series: AnalyticsDayBucket[]; /** * Equal-length previous window (for period-over-period deltas + comparison). * For sub-day buckets this is the same time span shifted back by the * window's length in whole days (24h for an intra-day view, 7 days for a * week), so bucket `i` compares with the same time-of-day one window earlier. */ previous_series: AnalyticsDayBucket[]; connectors: AnalyticsConnectorSeries[]; children: AnalyticsChild[]; /** * Per-payment-method window totals for the method donut. Empty unless the * `methods` section is requested (or `sections` is omitted); absent on * backends that predate the method dimension. */ methods?: AnalyticsMethodSlice[]; /** * Known ways this response is not the whole truth (populated by filtered * requests today); absent on older backends. */ caveats?: ClientAnalyticsCaveat[]; /** The country/device filters this response was computed under. */ filters?: ClientAnalyticsFilters; } /** * Which analytics projection a drill or an exclusion applies to. * * The three are scoped independently: a rule may exclude a payment from the * conversion figures and leave the volume figures alone. */ type ScopeDrillCohort = 'conversion' | 'success_rate' | 'volume'; /** * An analytics surface a rule can apply to. The five are selected * independently; omitting `surfaces` on a request means all five. */ type AnalyticsExclusionSurface = 'conversion' | 'success_rate' | 'volume' | 'device_geo' | 'theme_attribution'; /** The signal a rule matches payments on. */ type AnalyticsExclusionKind = 'ip' | 'ip_range' | 'device' | 'shop'; /** * A rule, as it is written. **Complete replacement**: an update sends the * whole rule, not a patch, so every field you omit reverts to its default * rather than keeping its stored value. Send `null` explicitly to clear * `profile_id`, `value`, `valid_from`, `valid_until` or `note`. */ interface AnalyticsExclusionRequest { /** * Limit matching to this shop. Omitted or `null` means every shop of the * merchant. A shop-scoped user must name their own shop. */ profile_id?: string | null; kind: AnalyticsExclusionKind; /** * The IP address, CIDR network or device identifier to match. Absent for a * `shop` rule, whose target is `profile_id`. */ value?: string | null; /** ISO 8601. Omitted or `null` means the rule has always applied. */ valid_from?: string | null; /** ISO 8601. Omitted or `null` means the rule does not expire. */ valid_until?: string | null; /** * The surfaces this rule affects. **Omitting it means all five** - it is not * "no surfaces", so an update that drops the field widens the rule. */ surfaces?: AnalyticsExclusionSurface[]; /** Free text for whoever reads the list later; at most 255 characters. */ note?: string | null; } /** A stored rule, as it is read back. */ interface AnalyticsExclusionResponse extends AnalyticsExclusionRequest { id: string; /** Who wrote it. */ created_by: string; /** ISO 8601. */ created_at: string; /** * Distinct payments this rule matched in the lookback window, on list * responses only. * * Counts overlap between rules and are not additive: one payment matched by * three rules is counted once by each, so adding the values reports it three * times. Absent when the server did not compute a count, which is different * from a count of zero — zero means the rule matched nothing. */ matched_payments?: number | null; } /** Every rule of the merchant, with the two facts that qualify all of them. */ interface AnalyticsExclusionsResponse { rules: AnalyticsExclusionResponse[]; /** * Matching is best effort: the client signals a rule matches on may be * absent or expired for a given payment, so a rule can be correct and still * miss payments it describes. */ best_effort: boolean; /** Historical refresh and signal matching are bounded to this many days. */ lookback_days: number; } /** * Query for the device/geo analytics endpoints. Same drill/window contract as * `AnalyticsScopeRequest`; `mode` applies to the geo endpoint only. */ interface ClientAnalyticsRequest { /** Trailing days for a rolling window ending now. Default 7, capped at 92. */ days?: number | null; /** Naive ISO 8601 datetime for the window start (custom range; span ≤ 92 days). */ start_date?: string | null; /** Naive ISO 8601 datetime for the inclusive last day. Defaults to now. */ end_date?: string | null; /** Scope to a single merchant. Ignored (server-pinned) on the merchant routes. */ merchant_id?: string | null; /** Scope to one project's shops. */ project_id?: string | null; /** Scope to a single shop; wins over project_id. */ shop_id?: string | null; /** `false` = live only, `true` = test only. Omit for both. */ test_mode?: boolean | null; /** * Series bucket size: `"day"` (default) or `"hour"` — these pages have no * sub-hour view. Any window, up to the shared ceiling of 2208 buckets * (hourly over the widest window); hourly always aggregates live. Older * backends cap hourly at a 48-hour window. */ granularity?: 'day' | 'hour' | null; /** * Comma-separated response blocks to compute; omit for all. Devices: * `totals`, `browsers`, `platforms`, `classes`, `models`, `channels`, * `timing`, `children`. Geo: `totals`, `countries`, `cities`, `heatmap`, * `languages`, `children`. */ sections?: string | null; /** * Geo endpoint only: which location claim to aggregate — `"ip"` (default; * IP-derived country + city coordinates) or `"billing"` (plaintext * billing-address country/city, no coordinates). Never coalesced. */ mode?: string | null; /** Restrict to sessions from one country (ISO alpha-2). */ country?: string | null; /** Which country claim `country` filters on: `"ip"` (default) or `"billing"`. */ country_source?: string | null; /** Restrict to one device class: `phone` | `tablet` | `desktop` | `unknown`. */ device_class?: string | null; } /** * Known ways a figure in an analytics response is not the whole truth. Codes, * not sentences — the dashboards map each to translated copy. */ type ClientAnalyticsCaveat = /** * Merchant-configured analytics exclusions apply to this surface, so the * figures are filtered. Matching is best effort over 95 days. */ 'AnalyticsExclusionsActive' | 'OptimisationUseDisabled' | 'DeviceTrackingDisabled' | 'GeoIpDisabled' | 'AutomatedTrafficExcluded' | 'RetentionWindowTruncatesWindow' | 'DeviceClassMissingInWindow' | 'BillingModeCountryLevelOnly' | 'BillingCityRedactedExcluded' | 'ShopHasNoHomeCountry' | 'NoObservationsInWindow' | 'FilterRequiresClientContext' | 'TimingDenominatorTruncated' | 'FxRateUnavailable'; /** The filters a response was computed under, echoed back verbatim. */ interface ClientAnalyticsFilters { country?: string | null; country_source?: string | null; device_class?: string | null; } /** Window totals for the device dashboard's KPI row. */ interface DeviceAnalyticsTotals { /** Canonical sessions (one per payment), automated traffic excluded. */ sessions: number; /** Sessions whose payment reached `succeeded`. */ paid: number; /** `paid / sessions` (0 when there are no sessions). */ conversion: number; /** Share of sessions on phone or tablet. */ mobile_share: number; /** Share of raw observations that were automated and therefore excluded. */ automated_share: number; /** Distinct buyer devices observed (sessions without a device id are not counted). */ unique_devices: number; /** Share of those devices with >=1 successful payment — each device counts once. Null when no session carried a device id. */ device_success_rate?: number | null; } /** One series bucket of overall session/paid counts (KPI sparklines). */ interface DeviceSessionBucket { /** Bucket start: `YYYY-MM-DD` (day grain) or `YYYY-MM-DDTHH:00:00Z` (hour grain). */ bucket: string; sessions: number; paid: number; } /** One browser family's window totals. */ interface DeviceBrowserSlice { /** `chrome` | `safari` | `edge` | `firefox` | `samsung_internet` | `in_app` | `other`. */ family: string; sessions: number; paid: number; } /** One platform family's window totals. */ interface DevicePlatformSlice { /** `android` | `ios` | `windows` | `macos` | `linux` | `other`. */ os: string; sessions: number; paid: number; } /** One device class's window totals (`unknown` = rows predating classification). */ interface DeviceClassSlice { /** `phone` | `tablet` | `desktop` | `unknown`. */ class: string; sessions: number; } /** * One identifiable device model's window totals (top ~10 by sessions). * Sessions with no identifiable model are excluded and reported via * `unidentified_model_share`, never guessed. */ interface DeviceModelSlice { model: string; sessions: number; paid: number; } /** One series bucket of session counts per checkout channel. */ interface DeviceChannelBucket { /** Bucket start: `YYYY-MM-DD` (day grain) or `YYYY-MM-DDTHH:00:00Z` (hour grain). */ bucket: string; /** Hosted checkout page (top-level). */ hosted: number; /** Hosted checkout embedded in the merchant's page. */ embedded: number; /** Hosted checkout opened through a merchant-authored auto-redirect link. * Absent on routers that do not report this channel. */ auto_redirect?: number; /** API / server-initiated. */ api: number; dashboard: number; sdk: number; /** Intents that declared no confirm source. */ unknown: number; } /** One fixed histogram bucket of the time-to-pay distribution. */ interface TimeToPayBucket { /** `lt_30s` | `s30_60s` | `m1_2` | `m2_5` | `m5_15` | `gt_15m`. */ label: string; /** Share of measured paid sessions in this bucket. */ share: number; } /** * Time from the first `checkout_open` observation to the first `succeeded` * status. Only payments with both endpoints count. */ interface TimeToPayStats { median_s?: number | null; p90_s?: number | null; buckets: TimeToPayBucket[]; paid_with_timing: number; } /** A direct child of the current scope on the device dashboard's by-shop table. */ interface DeviceAnalyticsChild { id: string; name: string; /** "merchant" | "project" | "shop". */ type: string; sessions: number; conversion: number; /** Median time-to-pay in seconds; `null` when nothing measured. */ median_ttp_s?: number | null; mobile_share: number; top_browser?: string | null; top_platform?: string | null; } /** Device-analytics response for one drill level. */ interface DevicesAnalyticsResponse { /** * `false` when the purpose gate or device tracking is off — the caveats say * which — and every data block is empty. */ enabled: boolean; /** "root" | "merchant" | "project" | "shop". */ level: string; days: number; end_date: string; /** `"day"` or `"hour"`, mirroring the request. */ granularity: string; /** First bucket's UTC start when `granularity` is `"hour"`. */ bucket_start?: string | null; totals: DeviceAnalyticsTotals; /** Per-bucket session/paid counts (the `series` section). */ series: DeviceSessionBucket[]; browsers: DeviceBrowserSlice[]; platforms: DevicePlatformSlice[]; device_classes: DeviceClassSlice[]; device_models: DeviceModelSlice[]; /** Share of sessions whose device model could not be identified. */ unidentified_model_share: number; channel_series: DeviceChannelBucket[]; time_to_pay: TimeToPayStats; children: DeviceAnalyticsChild[]; caveats: ClientAnalyticsCaveat[]; filters: ClientAnalyticsFilters; /** The previous window's totals, when `sections` includes `previous`. */ previous_totals?: DeviceAnalyticsTotals | null; /** The previous window's time-to-pay stats, for the median delta. */ previous_time_to_pay?: TimeToPayStats | null; } /** Window totals for the geo dashboard's KPI row. */ interface GeoAnalyticsTotals { /** Distinct countries observed in the selected mode. */ countries: number; /** * Share of paid sessions from outside the shop's home country. `null` when * no shop in scope has a home country (`ShopHasNoHomeCountry`). */ cross_border_share?: number | null; /** * Share of paid sessions whose IP country and billing country disagree * (both present). `null` when no session carries both claims. */ mismatch_share?: number | null; /** * Successful volume (USD major units) from outside the shops' home * countries. `null` when no shop in scope has a home country. */ intl_volume_usd?: number | null; /** Distinct buyer devices observed (sessions without a device id are not counted). */ unique_devices: number; /** Share of those devices with >=1 successful payment — each device counts once. Null when no session carried a device id. */ device_success_rate?: number | null; } /** One country's window totals in the selected mode. */ interface GeoCountrySlice { /** ISO alpha-2. */ country: string; sessions: number; paid: number; /** Successful volume, USD major units. */ volume_usd: number; success_rate: number; } /** * One city bubble for the map (IP mode only). Always a server-side aggregate, * capped at the top ~200 cities — never per-transaction coordinates. */ interface GeoCitySlice { city: string; country: string; lat: number; lon: number; sessions: number; /** Paid sessions — the map's bubble size, per the approved design. */ paid: number; } /** One buyer language's share (`accept_language` primary subtag). */ interface GeoLanguageSlice { lang: string; share: number; } /** A direct child of the current scope on the geo dashboard's by-shop table. */ interface GeoAnalyticsChild { id: string; name: string; /** "merchant" | "project" | "shop". */ type: string; countries: number; top_country?: string | null; top_country_share: number; /** `null` when no shop under the child has a home country. */ intl_share?: number | null; mismatch_share?: number | null; /** Successful volume, USD major units. */ volume_usd: number; } /** Geo-analytics response for one drill level. */ interface GeoAnalyticsResponse { /** `false` when the purpose gate or device tracking is off. */ enabled: boolean; /** "root" | "merchant" | "project" | "shop". */ level: string; /** `"ip"` or `"billing"`, mirroring the request. */ mode: string; days: number; end_date: string; granularity: string; bucket_start?: string | null; totals: GeoAnalyticsTotals; countries: GeoCountrySlice[]; /** IP mode only; empty in billing mode. */ cities: GeoCitySlice[]; /** * 7 rows (Mon..Sun) × 24 session counts on the buyer's own clock; all zeros * when no observation carries a time zone. */ local_hours: number[][]; languages: GeoLanguageSlice[]; children: GeoAnalyticsChild[]; caveats: ClientAnalyticsCaveat[]; filters: ClientAnalyticsFilters; /** The previous window's totals, when `sections` includes `previous`. */ previous_totals?: GeoAnalyticsTotals | null; /** The previous window's session share for THIS window's top country, so the * top-country delta compares one country rather than two ranks. */ previous_top_country_share?: number | null; } /** Query for the geo drill-through list: the page's window/scope/filters plus the clicked target. */ interface GeoDrillRequest extends DrillListControls { days?: number | null; start_date?: string | null; end_date?: string | null; merchant_id?: string | null; project_id?: string | null; shop_id?: string | null; test_mode?: boolean | null; /** Which claim the clicked country is matched against: 'ip' (default) or 'billing'. */ mode?: string | null; /** Active chip filters, riding along so the drill lists what the charts counted. */ country?: string | null; country_source?: string | null; device_class?: string | null; /** Clicked country (ISO alpha-2). */ target_country?: string | null; /** Clicked city name (IP claim only). */ target_city?: string | null; /** Heatmap cell: ISO day-of-week 1-7 (Mon-Sun). Requires hour. */ dow?: number | null; /** Heatmap cell: buyer-local hour 0-23. Requires dow. */ hour?: number | null; } /** Time-to-pay histogram bar labels, in histogram order. */ type TimeToPayBucketLabel = 'lt_30s' | 's30_60s' | 'm1_2' | 'm2_5' | 'm5_15' | 'gt_15m'; /** The window / scope / chip part of a device drill. */ interface DeviceDrillBase extends DrillListControls { days?: number | null; start_date?: string | null; end_date?: string | null; merchant_id?: string | null; project_id?: string | null; shop_id?: string | null; test_mode?: boolean | null; /** Active chip filters, riding along so the drill lists what the charts counted. */ country?: string | null; country_source?: string | null; device_class?: string | null; } interface DeviceBrowserTarget { /** Clicked browser family token (chrome, safari, ...). */ target_browser: string; } interface DevicePlatformTarget { /** Clicked platform family token (ios, android, macos, ...). */ target_platform: string; } interface DeviceModelTarget { /** Clicked device-model label, exactly as listed by the models card. */ target_model: string; } interface DeviceClassTarget { /** Clicked device class. */ target_device_class: 'phone' | 'tablet' | 'desktop' | 'unknown'; } interface DeviceTtpTarget { /** * Clicked time-to-pay histogram bar. Paid, timed sessions only — exactly * the bar's own population; rows carry `time_to_pay_s`. */ target_ttp_bucket: TimeToPayBucketLabel; } interface DeviceChannelTarget { /** Clicked checkout channel; rows carry `channel`. */ target_channel: AnalyticsChannel; /** * One bar of the channel series, in the shape the series emits * (`YYYY-MM-DD` or `YYYY-MM-DDTHH:00:00Z`). Only meaningful with the * channel, which is why it lives on this arm alone. */ target_bucket?: string; } type DeviceTargetKey = 'target_browser' | 'target_platform' | 'target_model' | 'target_device_class' | 'target_ttp_bucket' | 'target_channel' | 'target_bucket'; /** Every device target key this arm does not itself set, forbidden. */ type NotOtherDevice = Partial, never>>; /** * The clicked device target. **Exactly one** of six, as a discriminated * union: the endpoint refuses two targets with a `400`, and `{}` would * otherwise compile into a request that lists nothing a click meant. Each * arm marks every other target `?: never`; `target_bucket` is allowed on the * channel arm only, because a bar of the channel series is the only thing it * can name here. */ type DeviceDrillTarget = (DeviceBrowserTarget & NotOtherDevice<'target_browser'>) | (DevicePlatformTarget & NotOtherDevice<'target_platform'>) | (DeviceModelTarget & NotOtherDevice<'target_model'>) | (DeviceClassTarget & NotOtherDevice<'target_device_class'>) | (DeviceTtpTarget & NotOtherDevice<'target_ttp_bucket'>) | (DeviceChannelTarget & NotOtherDevice<'target_channel' | 'target_bucket'>); /** * Query for the device drill-through list: the page's window/scope/filters * plus exactly one clicked device target. */ type DeviceDrillRequest = DeviceDrillBase & DeviceDrillTarget; /** One payment behind a clicked geo target. */ interface DrillPayment { payment_id: string; /** Owning merchant — meaningful on the admin portal's cross-merchant scope. */ merchant_id: string; profile_id?: string | null; /** Shop name for the profile, when known to the scope. */ shop_name?: string | null; status: string; amount_minor?: number | null; currency?: string | null; /** * When the buyer was **observed** — the timestamp of the canonical * client-context row, not of the payment. This is the value the window * filters on, so it always falls inside the requested range. * * Named `created_at` since the endpoint shipped, and kept for * compatibility. Read `payment_created_at` for the payment's own age: the * two can be days apart on a long-lived payment link, and reading this one * as the payment's creation time is what makes a drill list look like it is * ignoring its own window. RFC 3339 UTC. */ created_at: string; /** * When the **payment** was created, RFC 3339 UTC. Unlike `created_at` this * need not fall inside the requested window: a payment created weeks ago * can be observed today. */ payment_created_at: string; /** IP-claimed country of the canonical observation. */ country?: string | null; /** IP-resolved city, when GeoLite2 had one. */ city?: string | null; /** * Processor of the active attempt (`"Direct"` when the attempt carried * none); null when the intent never reached an attempt. */ connector: string | null; /** High-level payment method of the active attempt (`card`, `wallet`, …). */ payment_method: string | null; /** Narrower method type of the active attempt (`credit`, `apple_pay`, …). */ payment_method_type: string | null; /** Checkout channel token; null on rows that are not payments. */ channel: AnalyticsChannel | null; /** Device class of the canonical observation, when one exists. */ device_class: string | null; /** Browser family of the canonical observation (`chrome`, `safari`, …). */ browser_family: string | null; /** Platform family of the canonical observation (`ios`, `android`, …). */ platform_family: string | null; /** * Seconds from the first checkout open to the succeeded transition; null * when either endpoint is missing. */ time_to_pay_s: number | null; /** * The amount in USD major units, FX-converted with the same rates the * dashboard sums with — so the drawer's figures agree with the chart that * was clicked. Null when the currency has no fresh rate. */ amount_usd: number | null; /** Successfully refunded amount in the payment's own minor units, when any. */ refunded_minor: number | null; /** The buyer's customer id, when the payment carried one. */ customer_id: string | null; /** * Subscription this row belongs to. Set only by the subscription drill; its * presence is what tells a client to link the row at the subscription * rather than at the payment. */ subscription_id?: string | null; /** Invoice (billing cycle) this row is. Subscription drill only. */ invoice_id?: string | null; /** * The invoice's own status, which is not the payment's: a cycle can be * `PaymentFailed` while no payment row exists at all. Subscription drill * only. */ invoice_status?: string | null; /** Plan the subscription is on. Subscription drill only. */ plan?: string | null; /** Billing processor that raised the cycle. Subscription drill only. */ billing_processor?: string | null; /** The subscription's own status. Subscription drill only. */ subscription_status?: string | null; } /** * Figures over the **whole** match of a drill — not the returned page — so a * drawer header can summarise what was clicked while the list is paged. */ interface DrillSummary { /** * Rows matched (equals the response's `total`). On the subscription drill * a row is a billing cycle, so this is the invoice count. */ count: number; /** Rows whose outcome is `success` (settled cycles on the subscription drill). */ successful_count: number; /** * USD major units, FX-converted with the dashboard's rates; rows with no * fresh rate contribute their count but not their volume. On the * subscription drill this is the billed volume of the matched cycles. */ successful_volume_usd: number; /** Successfully refunded volume behind the matched rows, USD major units. */ refund_volume_usd: number; /** `successful_count / count`, `0` when nothing matched. */ success_rate: number; /** Median successful amount, USD major units; null when none converted. */ median_amount_usd: number | null; /** Median time-to-pay over the matched paid, timed sessions (device/geo drills); null elsewhere. */ median_time_to_pay_s: number | null; /** Distinct subscriptions behind the matched cycles (subscription drill); null elsewhere. */ subscriptions: number | null; /** * Rows whose currency had no fresh USD rate, so the USD figures above are * a lower bound when this is non-zero. */ unconverted_count: number; } /** A drill-through list: the rows behind one clicked chart element. */ interface DrillResponse { enabled: boolean; caveats: ClientAnalyticsCaveat[]; /** * One page, sorted per the request's `sort_on` / `sort_by` (newest first by * default), `limit` rows (default 50). */ payments: DrillPayment[]; /** Full match count (may exceed payments.length). */ total: number; /** * Figures over the whole match. Absent on older backends, which predate * the summary — treat a missing block as "no summary", not as zeros. */ summary?: DrillSummary; } /** Query for the subscription-analytics endpoint. */ interface SubscriptionAnalyticsRequest { /** Trailing days for a rolling window ending now. Default 7, capped at 92. */ days?: number | null; /** Naive ISO 8601 datetime for the window start (custom range; span ≤ 92 days). */ start_date?: string | null; /** Naive ISO 8601 datetime for the inclusive last day. Defaults to now. */ end_date?: string | null; /** Scope to a single merchant. Ignored (server-pinned) on the merchant route. */ merchant_id?: string | null; /** Scope to one project's shops. */ project_id?: string | null; /** Scope to a single shop; wins over project_id. */ shop_id?: string | null; /** `false` = live only, `true` = test only. Omit for both. */ test_mode?: boolean | null; /** * Day only. Any other value is rejected rather than downgraded: a billing * cycle carries day resolution at best, so an hourly series over `invoice` * would chart when a scheduler ran. */ granularity?: 'day' | null; /** * Comma-separated blocks to compute; omit for all. Tokens: `totals`, * `series`, `movement`, `processors`, `plans`, `outcomes`, `children`, * `previous`. A block you did not ask for comes back empty, zeroed or * `null` — never stale — so a single card placed on another page can load * itself without paying for the whole dashboard. */ sections?: string | null; /** * Restrict to subscriptions charged from one country (ISO alpha-2). Narrows * the population to subscriptions with at least one matching invoice in the * window — see `FilterNarrowsToChargedSubscriptions`. */ country?: string | null; /** Which country claim the filter matches: `"ip"` (default) or `"billing"`. */ country_source?: string | null; /** `phone` | `tablet` | `desktop` | `unknown`. */ device_class?: string | null; } /** Known ways a figure in the subscription response is not the whole truth. */ type SubscriptionCaveat = /** An active subscription has one invoice, so no cadence could be read from * its own history. Excluded from every estimate rather than guessed at. */ 'CadenceUnknownExcluded' /** * At least one cycle was recorded at zero and left out of volume, of * revenue-per-subscription and of cadence inference. * * Named for what was observed, not why: a zero is written both by a * processor-hosted origination and by a genuinely free renewal, and nothing * stored tells them apart. */ | 'ZeroAmountCyclesExcluded' /** An invoice had no usable reporting rate for its own day and was left out, * so the converted totals are a lower bound. */ | 'FxIncomplete' /** There is no subscription status history, so churn is timed from the last * accepted processor status event. */ | 'ChurnTimingFromConnectorEvent' /** A country/device filter can only match cycles that reached a payment * carrying a client-context observation. */ | 'FilterRequiresClientContext' /** Under a filter the subscription counts describe subscriptions with a * matching invoice in this window — a narrower population than "active". */ | 'FilterNarrowsToChargedSubscriptions' /** The filter reads fraud-collected signals, which the purpose gate forbids. * It was refused and the response is unfiltered. */ | 'FilterRefusedOptimisationUseDisabled' /** The window contains no subscription activity at all. */ | 'NoSubscriptionsInWindow'; /** Echo of the filters actually applied. */ interface SubscriptionFilters { country?: string | null; country_source?: string | null; device_class?: string | null; } /** * Headline figures. Volumes are USD major units; counts are exact. * * **Stocks** (`est_*`, `active`, `trial`, `paused`, `unpaid_subscriptions`, * `arpa_usd`) are as at the end of the window. **Flows** are sums over it. */ interface SubscriptionTotals { /** Stock. Estimated recurring volume per month for the scope. */ est_monthly_volume_usd: number; /** Stock. `est_monthly_volume_usd × 12`, carried so every client agrees. */ est_annual_volume_usd: number; /** Stock. */ active: number; /** Stock. */ trial: number; /** Stock. */ paused: number; /** Stock. */ unpaid_subscriptions: number; /** Flow. Gross settled invoice volume in the window. */ billed_volume_usd: number; /** Flow. Reported beside the gross, never subtracted from it. */ refunded_usd: number; /** Flow. */ invoices_raised: number; /** Flow. Settled, first attempt or later. */ invoices_paid: number; /** Flow. Entered retry, settled or not. */ invoices_retried: number; /** Flow. Failed at least once and settled afterwards. */ invoices_recovered: number; /** Flow. Still unpaid at window end. */ invoices_unpaid: number; /** 0–100. `null` when nothing was raised — a rate over no invoices is not zero. */ renewal_success_rate?: number | null; /** Flow. */ new_subscriptions: number; /** Flow. */ cancelled_subscriptions: number; /** 0–100. `null` when nothing was active to churn. */ churn_rate?: number | null; /** Stock. `est_monthly_volume_usd / active`. */ arpa_usd?: number | null; } /** One bucket of the series — always a calendar day. */ interface SubscriptionBucket { /** `YYYY-MM-DD`. */ bucket: string; /** Stock as at the end of this bucket. */ est_monthly_volume_usd: number; /** Stock as at the end of this bucket. */ active: number; /** Flow within this bucket. */ billed_volume_usd: number; /** Flow. Cycles that settled for a subscription that already had one. */ renewed: number; /** Flow. */ new_subscriptions: number; /** Flow. */ failed: number; /** Flow. */ cancelled: number; } /** * What moved recurring volume between the previous window and this one. * `opening + new + expansion − contraction − churn = closing`. */ interface SubscriptionMovement { opening_usd: number; closing_usd: number; net_usd: number; /** A subscription's first ever invoice. */ new_usd: number; new_count: number; /** A renewal billed above the same subscription's previous cycle. */ expansion_usd: number; expansion_count: number; /** A renewal billed below the same subscription's previous cycle. */ contraction_usd: number; contraction_count: number; churn_usd: number; churn_count: number; /** 0–100. `null` when opening was zero. */ nrr?: number | null; /** 0–100. `null` when opening was zero. */ grr?: number | null; } /** One processor's share, on either axis. */ interface ProcessorSlice { /** Connector token as stored (`stripebilling`, `creem`, `paypal`, …). */ key: string; /** Stock. */ est_monthly_volume_usd: number; /** Flow. */ billed_volume_usd: number; /** Stock. */ subscriptions: number; } /** * The two processor axes. They diverge exactly where the billing connector * cannot take money itself: a Recurly subscription charged through Stripe * appears under Recurly in `billing` and Stripe in `charging`. */ interface SubscriptionProcessors { /** Grouped by `subscription.billing_processor`. */ billing: ProcessorSlice[]; /** Grouped by the connector on the payment attempt behind each invoice. */ charging: ProcessorSlice[]; } /** One plan's share. Annual cadences are divided down to a monthly figure. */ interface PlanSlice { key: string; /** Stock. */ est_monthly_volume_usd: number; /** Stock. */ subscriptions: number; /** Stock. */ arpa_usd?: number | null; } /** The invoice funnel. `paid_first_attempt + recovered + unpaid = raised`. */ interface InvoiceOutcomes { raised: number; paid_first_attempt: number; retried: number; recovered: number; unpaid: number; /** `recovered / retried`, 0–100. `null` when nothing was retried. */ recovery_rate?: number | null; } /** One breakdown row, one drill level below the current scope. */ interface SubscriptionChild { /** `merchant` | `project` | `shop`. */ kind: string; id: string; /** Display name, when resolvable; falls back to the id. */ name?: string | null; /** Shops directly under this row (projects only). */ shop_count?: number | null; /** Stock. */ est_monthly_volume_usd: number; /** Stock. */ active: number; /** Flow. */ billed_volume_usd: number; renewal_success_rate?: number | null; churn_rate?: number | null; } /** One drill level of the subscription-analytics dashboard. */ interface SubscriptionAnalyticsResponse { /** Always `true` today; kept so the shape matches the device/geo responses. */ enabled: boolean; /** `root` | `merchant` | `project` | `shop`. */ level: string; days: number; /** `YYYY-MM-DD` of the window's last day. */ end_date: string; /** Always `"day"`. */ granularity: string; /** Always `null`; the field exists for shape parity with the other pages. */ bucket_start?: string | null; totals: SubscriptionTotals; series: SubscriptionBucket[]; movement?: SubscriptionMovement | null; processors: SubscriptionProcessors; plans: PlanSlice[]; outcomes?: InvoiceOutcomes | null; children: SubscriptionChild[]; caveats: SubscriptionCaveat[]; filters: SubscriptionFilters; /** The previous window's totals, when `sections` includes `previous`. */ previous_totals?: SubscriptionTotals | null; } /** The window, scope and chip filters every drill call carries. */ interface SubscriptionDrillBase extends Omit, 'method' | 'channel' | 'status' | 'connector'> { /** Row filter: one invoice status, verbatim. */ status?: string | null; /** Row filter: the charging connector. */ connector?: string | null; days?: number | null; start_date?: string | null; end_date?: string | null; merchant_id?: string | null; project_id?: string | null; shop_id?: string | null; test_mode?: boolean | null; /** Active chip filters, so the drill lists what the charts counted. */ country?: string | null; country_source?: string | null; device_class?: string | null; /** * Clicked series bucket (`YYYY-MM-DD`). Narrows whichever target is set to * that one day, so it combines with any of them rather than replacing them. */ target_bucket?: string | null; } /** * The clicked target. **Exactly one**, encoded as a discriminated union rather * than a bag of optional fields. * * The union is the only thing enforcing that, which is exactly why it is one. * The endpoint applies every target it is given as a further AND, so * `{ target_outcome: 'unpaid', target_plan: 'x' }` is not refused — it answers * a narrower question than the click meant, and returns a shorter list with no * indication that it did. A drawer quietly missing rows is worse than a 400, * so `{}` and a two-target request are compile errors here. * * Each member marks the other targets `?: never`. Without that TypeScript * accepts any property present in *some* member of the union, so a second * target compiles happily — the exclusions are what make "exactly one" real. * * `target_child` and `target_child_kind` travel together as one member: a * child id with no kind cannot be resolved to anything, and the server matches * nothing rather than everything when the kind is missing. */ type SubscriptionDrillTarget = (SubscriptionOutcomeTarget & NotOther<'target_outcome'>) | (SubscriptionBillingProcessorTarget & NotOther<'target_billing_processor'>) | (SubscriptionChargingConnectorTarget & NotOther<'target_charging_connector'>) | (SubscriptionPlanTarget & NotOther<'target_plan'>) | (SubscriptionStatusTarget & NotOther<'target_status'>) | (SubscriptionMovementTarget & NotOther<'target_movement'>) | (SubscriptionChildTarget & NotOther<'target_child' | 'target_child_kind'>) | (SubscriptionBucketOnlyTarget & NotOther); interface SubscriptionOutcomeTarget { /** Clicked slice of the invoice funnel. */ target_outcome: 'paid_first' | 'retried' | 'recovered' | 'unpaid'; } interface SubscriptionBillingProcessorTarget { /** Clicked slice of the "bills it" ring. */ target_billing_processor: string; } interface SubscriptionChargingConnectorTarget { /** Clicked slice of the "charges the card" ring. */ target_charging_connector: string; } interface SubscriptionPlanTarget { /** Clicked plan row. */ target_plan: string; } interface SubscriptionStatusTarget { /** Clicked subscription status. */ target_status: string; } interface SubscriptionMovementTarget { /** Clicked movement component. */ target_movement: 'new' | 'expansion' | 'contraction' | 'churn'; } interface SubscriptionChildTarget { /** Clicked breakdown row, with the kind needed to resolve it. */ target_child: string; target_child_kind: 'merchant' | 'project' | 'shop'; } /** * A clicked day with no further target: every cycle raised that day. The one * member whose key lives on the base, so it is `required` here — without that * `{}` would satisfy the union and an untargeted drill would list the whole * window, which no click means. */ interface SubscriptionBucketOnlyTarget { target_bucket: string; } /** Every target key this member does not itself set, forbidden. */ type NotOther = Partial, never>>; type TargetKey = 'target_outcome' | 'target_billing_processor' | 'target_charging_connector' | 'target_plan' | 'target_status' | 'target_movement' | 'target_child' | 'target_child_kind'; type SubscriptionDrillRequest = SubscriptionDrillBase & SubscriptionDrillTarget; /** Payout progress of a settlement statement. */ type SettlementPayoutStatus = 'unpaid' | 'partial' | 'paid'; /** * Who owns the connector a settlement line ran through. `unknown` marks * lines frozen before ownership tracking existed. */ type ConnectorOwnership = 'host' | 'shop' | 'unknown'; /** * Per-connector/currency rollup inside a statement or current-period view. * Amounts are native minor units of `currency`. */ interface SettlementBucket { /** Non-negative principal magnitudes in native minor units. Already included * in net_to_shop as -dispute_debit_amount + dispute_recovery_amount. * Absent on older servers; absence is unreported, never a zero adjustment. */ dispute_debit_amount?: number; dispute_recovery_amount?: number; /** Movement identities captured by this statement snapshot. */ dispute_movement_ids?: string[]; /** Reversals and passed-through processor costs already deducted from net_to_shop. * Absence is unreported, never a zero amount or complete cost coverage. */ reversal_amount?: number; reversal_count?: number; processor_cost_passthrough_amount?: number; processor_cost_passthrough_count?: number; processor_cost_passthrough_incomplete?: boolean; connector?: string | null; ownership: ConnectorOwnership; currency: string; gross_amount: number; platform_fee_amount: number; /** * `true` when at least one line in this bucket is still awaiting its * platform-fee figure. Absent platform-fee fields on shop-owner reads are * a server-side permission boundary, not a gap — never re-derive them. */ platform_fee_incomplete: boolean; merchant_fee_amount: number; refund_amount: number; net_to_shop: number; line_count: number; refund_count: number; /** Fee schedule applied, e.g. `"2.9"` percent. Absent when mixed. */ fee_percentage?: string | null; fee_flat_amount?: number | null; } /** One generated monthly settlement statement. All `*_usd` amounts are USD minor units. */ interface FeeStatementSummary { /** Non-negative principal magnitudes in USD minor units. Already included * in net_to_shop_usd as -dispute_debit_usd + dispute_recovery_usd. * Absent on older servers; absence is unreported, never a zero adjustment. */ dispute_debit_usd?: number; dispute_recovery_usd?: number; id: string; merchant_id: string; profile_id: string; /** ISO-8601 period start (UTC). */ period_start: string; /** ISO-8601 period end (UTC). */ period_end: string; test_mode: boolean; gross_usd: number; platform_fee_usd: number; merchant_fee_usd: number; refund_usd: number; net_to_shop_usd: number; line_count: number; refund_count: number; fx_incomplete: boolean; platform_fee_incomplete: boolean; includes_backfill: boolean; payout_status: SettlementPayoutStatus; paid_amount_usd: number; paid_at?: string | null; payout_note?: string | null; generated_at: string; /** * Signed sum of manual adjustments, USD minor units. Positive means the * shop owner owes more (their payout shrinks). */ adjustments_usd: number; /** * `net_to_shop_usd - adjustments_usd`: what the shop owner is actually * paid once the host's positions are applied. **This is the payable * figure** — `net_to_shop_usd` is the computed sub-total before them. */ net_after_adjustments_usd: number; /** * Payments taken back by a reversal that produced no refund row (a void * after a charge, a rail-side refund), USD minor, already subtracted from * `net_to_shop_usd` and kept apart from `refund_usd`. Zero on statements * generated before reversals were tracked. */ reversal_usd: number; /** How many payments `reversal_usd` stands for. */ reversal_count: number; /** * What the rail charged and the shop owner was made to carry, USD minor, * already subtracted from `net_to_shop_usd`. Zero unless the host switched * pass-through on for this shop. Never redacted from the shop owner. */ processor_cost_passthrough_usd: number; /** * True when at least one passed-through payment had no known rail fee, so * `processor_cost_passthrough_usd` is a lower bound and the host carried the * rest. `false` on statements generated before pass-through existed. */ processor_cost_passthrough_incomplete: boolean; } /** A statement with its per-connector/currency breakdown. */ interface FeeStatementDetail extends FeeStatementSummary { breakdown: SettlementBucket[]; } interface SettlementStatementListParams { /** * Statement environment. Required so test and live figures can never * blend by accident: `false` is transmitted, not dropped. */ test_mode: boolean; /** Restrict to one shop. Enforced from the token for shop-owner callers. */ profile_id?: string; limit?: number; offset?: number; } interface SettlementStatementListResponse { statements: FeeStatementSummary[]; total_count: number; } interface SettlementOverviewParams { /** Environment switch — see {@link SettlementStatementListParams.test_mode}. */ test_mode: boolean; } /** Per-shop settlement rollup for the host merchant's overview. */ interface ShopSettlementOverview { profile_id: string; profile_name?: string | null; unpaid_net_usd: number; unpaid_statement_count: number; current_period_net_usd: number; current_period_merchant_fee_usd: number; current_period_gross_usd: number; fx_incomplete: boolean; has_fee_config: boolean; visible_to_shop: boolean; /** Already paid out on account of the running month, USD minor. */ current_period_advances_usd: number; /** * `current_period_net_usd` less what has already been paid on account, * floored at zero. The figure a payout panel should show beside a shop: the * gross running total on its own would offer a host a payment they have * already made. */ current_period_still_accruing_usd: number; /** Whether this shop's owner currently carries the payment rail's own fee. The setting in force now; past payouts keep what was stamped on their lines. */ processor_cost_passthrough: boolean; } interface SettlementOverviewResponse { shops: ShopSettlementOverview[]; } interface SettlementCurrentParams { /** Environment switch — see {@link SettlementStatementListParams.test_mode}. */ test_mode: boolean; profile_id?: string; } /** Live rollup of the current (not yet statemented) period. */ interface SettlementCurrentResponse { /** Non-negative principal magnitudes in USD minor units. Already included * in net_to_shop_usd as -dispute_debit_usd + dispute_recovery_usd. * Absent on older servers; absence is unreported, never a zero adjustment. */ dispute_debit_usd?: number; dispute_recovery_usd?: number; period_start: string; period_end: string; test_mode: boolean; gross_usd: number; platform_fee_usd: number; merchant_fee_usd: number; refund_usd: number; net_to_shop_usd: number; line_count: number; refund_count: number; fx_incomplete: boolean; platform_fee_incomplete: boolean; breakdown: SettlementBucket[]; /** Payments taken back so far this period by a reversal with no refund row, USD minor, already subtracted from `net_to_shop_usd`. */ reversal_usd: number; reversal_count: number; /** What the rail charged and the shop owner carried so far this period, USD minor, already subtracted from `net_to_shop_usd`. */ processor_cost_passthrough_usd: number; /** True when a passed-through payment had no known rail fee, so the figure above is a lower bound. */ processor_cost_passthrough_incomplete: boolean; /** * Already paid out on account of this period, USD minor — the signed total * of the advances recorded against it. Statements exist only for closed * months, so until this month closes these payments live nowhere else. */ advances_usd: number; /** `net_to_shop_usd` less what has been paid out on account, floored at zero: what is left to pay if the month ended now. */ still_accruing_usd: number; /** * Paid out beyond the period's net so far, USD minor — zero in the ordinary * case. Provisional while the month is open: the net is still moving, so a * figure here today can be gone tomorrow because the shop took more * payments. Reported rather than hidden so a host who has overpaid can see it. */ advance_excess_usd: number; } interface StatementGenerateRequest { profile_id: string; /** Calendar year (UTC). */ year: number; /** Calendar month 1..=12 (UTC). */ month: number; /** Environment switch — see {@link SettlementStatementListParams.test_mode}. */ test_mode: boolean; } /** @deprecated Use StatementPayoutRecordRequest to append individual transfer records. */ interface StatementPayoutUpdateRequest { payout_status: SettlementPayoutStatus; /** * Amount handed over so far, USD minor units — makes `partial` a real * figure. Omitted it defaults to the full net for `paid` and zero for * `unpaid`. */ paid_amount_usd?: number; /** ISO-8601. Defaults to now (UTC) when marking `paid` without a date. */ paid_at?: string; note?: string; } /** Append one transfer or reversal to a monthly statement. No transfer is initiated. */ interface StatementPayoutRecordRequest { /** Signed USD minor units. Omit to record the current outstanding balance. */ amount_usd?: number | null; /** Original transfer amount and currency are reference data, not a second deduction. */ original_amount?: number | null; original_currency?: Currency | null; value_date?: string | null; method?: string | null; reference?: string | null; note?: string | null; /** Reuse the same key when retrying the same record. */ idempotency_key?: string | null; payout_status?: never; paid_amount_usd?: never; paid_at?: never; } interface StatementPayout { id: string; amount_usd: number; original_amount?: number | null; original_currency?: string | null; value_date: string; method?: string | null; reference?: string | null; note?: string | null; source: string; created_at: string; } interface StatementPayoutListResponse { payouts: StatementPayout[]; total_usd: number; overpaid_usd: number; } interface StatementAdvanceRecordRequest { profile_id: string; year: number; month: number; test_mode?: boolean | null; amount_usd: number; original_amount?: number | null; original_currency?: Currency | null; value_date?: string | null; method?: string | null; reference?: string | null; note?: string | null; idempotency_key?: string | null; } interface StatementAdvance { id: string; profile_id: string; period_start: string; period_end: string; test_mode: boolean; amount_usd: number; original_amount?: number | null; original_currency?: string | null; value_date: string; method?: string | null; reference?: string | null; note?: string | null; source: string; statement_id: string | null; attached_at: string | null; created_at: string; } interface SettlementAdvanceListRequest { profile_id?: string | null; year: number; month: number; test_mode?: boolean | null; } interface SettlementAdvanceListResponse { profile_id: string; period_start: string; period_end: string; test_mode: boolean; advances: StatementAdvance[]; total_usd: number; } interface StatementPdfParams { /** ISO currency the PDF totals are converted to. Default USD. */ currency?: string; /** Append a per-transaction table. Off by default. */ include_transactions?: boolean; } /** One manual statement adjustment. Positive charges the shop, negative credits them. */ interface StatementAdjustment { id: string; label: string; /** Signed USD minor units. */ amount_usd: number; created_at: string; } interface StatementAdjustmentListResponse { adjustments: StatementAdjustment[]; } interface StatementAdjustmentCreateRequest { label: string; /** Signed USD minor units. Positive charges the shop; negative credits them. */ amount_usd: number; } interface SettlementLineListParams { profile_id: string; /** Calendar year (UTC) of the period to list. */ year: number; /** Calendar month 1..=12 (UTC). */ month: number; /** Environment switch — see {@link SettlementStatementListParams.test_mode}. */ test_mode: boolean; limit?: number; offset?: number; } /** * How a settlement line's processor cost was obtained. `reported` is the * rail's own figure; `estimated` comes from a manually configured schedule * and must never be presented as an observation; `unavailable` means the * rail was asked and reports nothing — an answer, not a zero. */ type ProcessorCostSource = 'reported' | 'estimated' | 'unavailable'; /** * The schedule and rates that produced an `estimated` processor cost, as * stamped on the settlement line when the cost was priced. * * A copy, not a reference: it keeps explaining the figure after the schedule * it came from was replaced or deleted, which is the whole point — a rate * that priced last month's transactions is not the rate configured today. * Present only where a cost was estimated; a `reported` cost is the rail's * own figure and rests on no schedule. */ interface ProcessorCostEstimateBasis { /** The schedule that priced the cost. It may no longer exist. */ schedule_id: string; /** Whether the schedule was the merchant's own rather than DeloPay's. */ merchant_scoped: boolean; /** The processor's own commission, in percent. */ percentage?: number | null; /** The underlying provider's fee, in percent. */ provider_fee_percentage?: number | null; /** * Minor units of `flat_currency`, or of the transaction currency when * `flat_currency` is absent. `min_amount` and `max_amount` likewise. */ flat_amount?: number | null; flat_currency?: string | null; min_amount?: number | null; max_amount?: number | null; /** ISO 8601. Start of the schedule's window. */ effective_from: string; payment_method_type?: string | null; connector_method_code?: string | null; } /** Fields common to every settlement line. Amounts are native minor units of `currency`. */ interface SettlementLineBase { payment_id: string; attempt_id: string; connector?: string | null; connector_ownership: ConnectorOwnership; payment_method?: string | null; payment_method_type?: string | null; /** * The processor's own method identifier the payment ran with, where the * payment method type cannot tell two methods apart: the e-Payouts vendor * code. Absent for other connectors, on lines written before it was * recorded, and where it could not be established. */ connector_method_code?: string | null; /** * What produced the line's `estimated` processor cost. Absent on a * `reported` cost, on a line with no figure, and on lines written before * the basis was stamped — absence is never a statement that no schedule * applied. */ processor_cost_basis?: ProcessorCostEstimateBasis | null; /** * The same, for the cost the shop owner was made to carry when * pass-through is on. Read it rather than {@link * SettlementLineBase.processor_cost_basis} when explaining a shop owner's * deduction: the two can rest on different schedules. */ processor_cost_passthrough_basis?: ProcessorCostEstimateBasis | null; currency: string; gross_amount: number; platform_fee_amount: number; merchant_fee_amount: number; net_to_shop: number; merchant_fee_source: string; /** Absent on shop-owner reads — server-side redaction, never re-derive. */ platform_fee_source?: string | null; merchant_ledger_outcome: string; test_mode: boolean; backfilled: boolean; transaction_at: string; } /** * A line whose processor cost is known — the rail reported it, or a * configured schedule estimated it. * * `processor_cost_amount` is minor units of `processor_cost_currency`, which * is the processor's **own** settlement currency and not necessarily the * line's `currency` — a crypto rail quotes its commission in the asset the * buyer sent. Never convert it client-side, and never sum lines across * different `processor_cost_currency` values: a converted view has to come * from the backend with the rate it used. * * The cost is informational, never a subtrahend: the rail deducts before the * money reaches anyone downstream, so `net_to_shop` already reflects reality * and must not have this subtracted from it. */ interface SettlementLineWithProcessorCost extends SettlementLineBase { processor_cost_source: 'reported' | 'estimated'; processor_cost_amount: number; processor_cost_currency: string; /** * Decimal places `processor_cost_amount` is expressed in: * `processor_cost_amount / 10 ** processor_cost_exponent` units of * `processor_cost_currency`. * * Required alongside the amount, not optional, because the figure does not * decode without it and the currency does not imply it — a rail can report * in an asset whose precision is not a property of its ticker (TRX is 10^6, * and the same ticker on another network can differ). Reading a crypto * rail's 10^8 figure through a card rail's 10^2 overstates it by a * millionfold, so the type refuses to hand over an amount without it. * * The server derives all four cost fields from one source and redacts them * as one, so an amount never arrives without its exponent. */ processor_cost_exponent: number; } /** * A line with no processor-cost figure — which is not a zero. * * Two distinct absences: a present `'unavailable'` says the rail was asked * and does not report a cost; an absent source says either the row pre-dates * cost recording, or the reader is a shop owner (all three fields are * redacted together server-side, same rule as `platform_fee_source` — never * re-derive them). Either way there is no number here, and a total over a * period containing such lines is a total over partial data. */ interface SettlementLineWithoutProcessorCost extends SettlementLineBase { processor_cost_source?: 'unavailable' | null; processor_cost_amount?: never; processor_cost_currency?: never; processor_cost_exponent?: never; } /** * One settled payment attempt. Amounts are native minor units of `currency`. * * A discriminated union on `processor_cost_source`: `processor_cost_amount` * is a `number` only after narrowing to a line whose source is `'reported'` * or `'estimated'`, so an unreported cost can never be read as a plain * number — and never as 0. Pinned by `tests/settlement.processorCost.test-d.ts`. */ type SettlementLine = SettlementLineWithProcessorCost | SettlementLineWithoutProcessorCost; interface SettlementLineListResponse { lines: SettlementLine[]; total_count: number; } /** * How much weight {@link SettlementCostResponse.margin_usd} can carry. * * Gaps can pull in either direction. Missing positive costs can establish an * upper bound; missing revenue, signed refund/dispute fees or their FX can * leave the direction unknown. Use this qualifier instead of deriving a * margin bound from processor-cost flags alone. * * - `exact` — the relevant revenue and merchant-borne costs are accounted for. * - `upper_bound` — cost-side gaps understate cost and so overstate what is * left. The real figure is `margin_usd` **or lower**; never present it as * exact. * - `unknown` — no direction can be established, so no number is published. * {@link SettlementCostResponse.margin_usd} is absent. Render the reason, * never a zero. */ type MarginQuality = 'exact' | 'upper_bound' | 'unknown'; /** * What every figure in a cost rollup rests on. * * - `reported` — observations from the rails. * - `estimated` — configured contract rates. Never present these as observed. * - `mixed` — both. Say so wherever the total might be reconciled against a * processor invoice. * - `none` — nothing priced the period at all. */ type ProcessorCostBasis = 'reported' | 'estimated' | 'mixed' | 'none'; /** * One bucket of processor cost, keyed by connector, ownership, cost currency * and cost exponent together. * * The currency is the **rail's**, not the transaction's — a EUR payment on a * rail that settles in USD reports a USD cost — which is why this breakdown * is separate from the settlement buckets rather than a column on them. * * Never sum `cost_amount` across different currencies or exponents. Method * buckets retain native amounts only, so finer grouping cannot manufacture * independently rounded USD amounts that disagree with the headline total. */ interface ProcessorCostNativeBucket { /** Whether the original payment's processor cost was passed to the shop. * Absence is unreported, not a statement that the host retained the cost. */ passed_through?: boolean; connector?: string | null; ownership: ConnectorOwnership; /** * Currency the rail reported its cut in. Absent on the bucket holding the * lines that carry no figure. */ cost_currency?: string | null; /** * Decimal places `cost_amount` is expressed in: `cost_amount / 10 ** * cost_exponent` units of `cost_currency`. Reported beside the figure * because it cannot be derived from the ticker. */ cost_exponent?: number | null; /** * Summed cost in `cost_currency` at `cost_exponent`. Absent — never zero — * when no line in the bucket carries a figure. */ cost_amount?: number | null; line_count: number; reported_count: number; estimated_count: number; /** The rail was asked and reports no cost — an answer, not a zero. */ unavailable_count: number; /** Lines that pre-date cost recording, so nothing ever asked. */ unrecorded_count: number; } /** Connector-level native costs with a backend-converted USD amount. */ interface ProcessorCostBucket extends ProcessorCostNativeBucket { /** Absent when no reporting rate covers the currency; unconverted is not free. */ cost_usd?: number | null; } /** * The period a cost rollup covers: one explicit UTC calendar month, or the * running month. * * A union rather than two optional fields, because half a period is not a * period — the server answers `400 InvalidRequestData` ("year and month must * be given together") rather than guessing the other half, so the shape that * would earn that error does not typecheck. */ type SettlementCostPeriod = { /** UTC calendar year to report. */ year: number; /** UTC calendar month, 1..=12. */ month: number; } | { /** Omit both for the running month so far. */ year?: never; month?: never; }; /** Query for the period cost-and-margin rollup. */ type SettlementCostParams = { /** Restrict to one shop. Omitted spans every shop of the merchant. */ profile_id?: string; /** * Environment switch — test and live figures never blend. Omitted = live. */ test_mode?: boolean; } & SettlementCostPeriod; /** * Query for the period processor-cost rollup * (`settlement.processorCost()`, `GET /settlement/processor-cost`). * * Carries {@link SettlementCostPeriod} for the same reason * {@link SettlementCostParams} does: half a period is not a period. `year` * without `month`, or the reverse, earns a `422` from the server, so the * shape that would earn it does not typecheck. */ type ProcessorCostPeriodRequest = { /** * Restrict to one shop (business profile). Omitted spans every shop of the * merchant. A `profile_id` that is not one of this merchant's shops is * refused `404` rather than filtered away — a period that cost nothing must * stay distinguishable from a month with no payments. */ profile_id?: string; /** * Environment switch — test and live figures never blend. Omitted = live. */ test_mode?: boolean; } & SettlementCostPeriod; /** * One `(shop, connector, account, method, unit)` bucket of a period's rail * costs. * * The unit is part of the key rather than converted, so nothing here adds two * figures in different currencies or at different scales. A reader that wants * one number per period converts, and says which rates it used. */ interface ProcessorCostPeriodBucket { /** * The shop these payments ran under. Always present, and part of the key * even when the request spans every shop: two shops on the same rail * otherwise produce two buckets a reader cannot tell apart. */ profile_id: string; connector?: string | null; merchant_connector_id?: string | null; payment_method?: string | null; payment_method_type?: string | null; /** * The processor's own method identifier, where the payment method type * cannot tell two vendors apart. */ connector_method_code?: string | null; /** The unit the rail reported its fee in — routinely not the transaction's. */ cost_currency?: string | null; /** * Decimal places {@link ProcessorCostPeriodBucket.cost_amount} is expressed * in: `cost_amount / 10 ** cost_exponent` units of `cost_currency`. */ cost_exponent?: number | null; /** * The rail's fee summed over the attempts in this bucket that carried one. * * `null` means no attempt here carried a figure — not a zero. **Never read * a present value as a period total**: it covers * `reported_count + estimated_count` attempts out of `attempt_count`, and * is a lower bound whenever those disagree. */ cost_amount?: number | null; /** The transaction currency of {@link ProcessorCostPeriodBucket.gross_amount}. */ gross_currency?: string | null; /** Decimal places `gross_amount` is expressed in. */ gross_exponent?: number | null; /** * What the attempts in this bucket took from buyers, as the cost was priced * against it. The denominator of * {@link ProcessorCostPeriodBucket.effective_rate_pct}. */ gross_amount?: number | null; /** * Established captures in this bucket. The denominator every count below is * read against. */ attempt_count: number; /** The rail said what it took. */ reported_count: number; /** * A configured cost schedule derived the figure. An estimate, never an * observation. */ estimated_count: number; /** Asked, and this rail has no answer. A real answer about the rail. */ unavailable_count: number; /** * Nothing was ever written for the attempt — it predates the column, or a * replica that did not know about it recorded the payment. **Not** * {@link ProcessorCostPeriodBucket.unavailable_count}: one is a gap in our * reporting, the other is the rail's own answer, and merging them would * report the first as a rail that charges nothing. */ unwritten_count: number; /** * The stored source is a value this release cannot name, which is what a * rolling deploy looks like from the older side. Counted separately because * the four counts above are claims this release can stand behind. */ unreadable_count: number; /** * The rail's fee as a percentage of the gross it was taken from. * * `null` unless the bucket can support one: every attempt in it priced, a * positive gross, and a cost in the **same currency** as that gross. A rail * that settles its fee in another currency is not a rate until somebody * states a conversion, and stating one here would bury the assumption * inside a number that reads as arithmetic. */ effective_rate_pct?: number | null; } /** * Response of `GET /settlement/processor-cost` * (`settlement.processorCost()`): what a period's payments cost at the rails, * per connector and method. * * Answerable for **every** merchant, including one that hosts nobody: it is * read from the per-attempt settlement record rather than from the settlement * lines, which exist only where hosting fees are configured. * * **There is no period total, and none may be derived here.** The buckets are * denominated in whatever the rails reported, and adding two currencies is * not addition. A caller that needs one number converts, and says which rates * it used. The counts below are totalled because a count has no unit. */ interface ProcessorCostPeriodResponse { /** ISO 8601. Start of the reported period (UTC). */ period_start: string; /** ISO 8601. End of the reported period (UTC). */ period_end: string; test_mode: boolean; /** The shop this was narrowed to, echoed back. `null` spans every shop. */ profile_id?: string | null; /** * ISO 8601. When this was computed. Nothing here is frozen — a rail * reporting late changes a past period's figures, which is the point of the * sweep. */ generated_at: string; /** Established captures in the period, across every bucket. */ attempt_count: number; reported_count: number; estimated_count: number; unavailable_count: number; unwritten_count: number; unreadable_count: number; /** * Sorted by connector, then account, then method, then unit, so two reads * of an unchanged period return the same document. */ buckets: ProcessorCostPeriodBucket[]; } /** * Which revenue the margin is a margin *on*: `hosting_fee` for a host (what it * charged its shops), `merchant_sales` for a merchant that hosts nobody (its * own captured sales). Read `revenue_usd` and this beside it rather than * either source directly — the two are never added, since a hosted shop's * payments are also payments. */ type RevenueBasis = 'hosting_fee' | 'merchant_sales'; /** Whose money a term of the cost model comes out of. */ type TermBearer = 'merchant' | 'shop_owner'; /** * How a period expense that only partly overlaps the report window is * counted: pro rata by days, or not applied at all. */ type ExpenseAllocation = 'pro_rata_days' | 'not_applied'; /** * What a host charged the shops it runs, as a term of the cost model. The same * money as `SettlementCostResponse.merchant_fee_usd`, carrying the evidence * that says how far it can be trusted. */ interface HostingFeeTerm { bearer: TermBearer; /** USD minor units. Identical to `merchant_fee_usd` by construction. */ amount_usd: number; /** Settlement lines in the period — the denominator the counts below are read against. */ line_count: number; /** A bucket carrying a non-zero hosting fee had no USD reporting rate, so `amount_usd` understates. Never blended 1:1, never counted as zero. */ fx_incomplete: boolean; /** Attempts where the rail took money and no settlement line exists, so their hosting fee is in no figure here. */ uncovered_captured_attempt_count: number; /** Unlined attempts whose outcome never resolved. Informational: they establish no capture, so no missing fee. */ uncovered_unresolved_attempt_count: number; } /** * One merchant-side cost term of the profit statement (cost of goods, partner * shares): a total plus the evidence that says how far it can be trusted. * `amount_usd` is a **lower bound** whenever `incomplete` or `fx_incomplete` * is set; a gap is never a zero. */ interface ProfitCostTerm { bearer: TermBearer; /** USD minor units. Zero with `row_count == 0` means nothing was recorded; zero with `fx_incomplete` means nothing could be converted. */ amount_usd: number; /** Rows in the period, priced or not. */ row_count: number; /** Rows carrying no figure at all. Never counted as zero. */ unavailable_count: number; /** Rows that carry a figure this term deliberately does not sum. */ excluded_count: number; /** Rows whose stored source matched none of the values this build knows — * a third state, weaker than either count above: not a figure nobody has, * not a figure belonging to another term, but a row that cannot be placed * at all. Published on its own because the remedy is its own. */ unclassified_count: number; /** Any count above is non-zero: the total is a floor. */ incomplete: boolean; /** A figure exists in a currency no USD reporting rate covered, so it is missing from `amount_usd` entirely. */ fx_incomplete: boolean; /** Attempts where the rail took money and no row of this kind exists. Makes the margin an upper bound. */ uncovered_captured_attempt_count: number; /** The same for attempts whose outcome never resolved. Informational. */ uncovered_unresolved_attempt_count: number; } /** * What the period cost the merchant outside the payment rails — advertising, * stock, anything booked to a period rather than to a payment. */ interface PeriodExpenseTerm { bearer: TermBearer; /** USD minor units, after allocation. A lower bound whenever `incomplete` or `fx_incomplete` is set. */ amount_usd: number; /** Rows that contributed a figure. */ row_count: number; /** Rows selected by the window that contributed nothing, for a reason that is not "they cost nothing". */ excluded_count: number; /** `excluded_count > 0`: `amount_usd` is a floor. */ incomplete: boolean; /** At least one selected row's currency had no USD reporting rate. */ fx_incomplete: boolean; allocation: ExpenseAllocation; /** * The exclusive end of the sub-window expenses were actually allocated to * (ISO date). Equal to `period_end` for a closed month, the current date for * the running one; absent when nothing was allocated. */ allocated_through?: string | null; } /** * What the merchant itself sold in the period — the revenue term for a * merchant who hosts nobody. Present only when `revenue_basis` is * `merchant_sales`. */ interface SalesRevenueTerm { /** `captured_usd - refunded_usd`, USD minor. Negative in a period that refunded more than it sold. */ amount_usd: number; /** What the rails captured in the period, USD minor. */ captured_usd: number; /** Successful refunds created in the period, USD minor. */ refunded_usd: number; /** Attempts contributing to `captured_usd`. */ attempt_count: number; /** Refunds contributing to `refunded_usd`. */ refund_count: number; /** Captures whose money went back to the buyer outside the refund flow. They earn nothing and bound nothing. */ reversed_attempt_count: number; /** The rail captured and no usable figure says how much. */ unpriced_captured_attempt_count: number; /** Attempts establishing neither a capture nor its absence. Informational. */ unresolved_attempt_count: number; /** Refunds left out of `refunded_usd` because their attempt's gross was never counted as revenue. */ excluded_refund_count: number; /** Either count above is non-zero: `amount_usd` cannot be reconciled and no margin is published. */ incomplete: boolean; /** A bucket's currency had no USD reporting rate, so that money is missing from every figure here. */ fx_incomplete: boolean; } /** Payment fees grouped by the method recorded when each payment settled. */ interface ProcessorCostMethodBucket { connector?: string | null; ownership: ConnectorOwnership; payment_method?: string | null; payment_method_type?: string | null; /** * The processor's own method identifier (the e-Payouts vendor code). Absent * for connectors without one and on lines that did not record it. */ connector_method_code?: string | null; /** Transaction currency; gross_amount uses its standard minor units. */ currency: string; gross_amount: number; cost_buckets: ProcessorCostNativeBucket[]; line_count: number; reported_count: number; estimated_count: number; unavailable_count: number; unrecorded_count: number; unclassified_count: number; /** Absent unless coverage is complete, currencies match and gross is positive. */ effective_rate_pct?: number; } /** A processor fee charge or credit; never refund or dispute principal. */ interface ProcessorFeeMovement { provider_event_id: string; /** Signed native minor units: positive expense, negative fee credit. */ amount: number; currency: string; exponent: number; event_at: string; } /** Fee-report availability and evidence for one refund or dispute. */ interface ProcessorCostEventSource { source_kind: 'refund' | 'dispute'; source_id: string; payment_id: string; attempt_id: string; connector: string; merchant_connector_id?: string | null; payment_method?: string | null; payment_method_type?: string | null; source_status: string; source_created_at: string; /** Reporting partition. Imported snapshots use the destination test partition. */ test_mode: boolean; cost_source: 'reported' | 'unavailable' | 'unrecorded'; unavailable_reason?: string | null; /** Empty events confirm zero only when complete and cost_source is reported. */ complete: boolean; /** False for imported history, whose source references cannot be refreshed * using the destination account's credentials. */ refreshable: boolean; checked_at?: string | null; events: ProcessorFeeMovement[]; } type ProcessorCostEventsRequest = { profile_id?: string; /** Explicitly filter the reporting environment. Omit for all partitions of * one payment; ordinary period queries default to the live partition. */ test_mode?: boolean; limit?: number; offset?: number; } & SettlementCostPeriod & ({ /** Omit the month to read this payment's complete cost-event history. */ payment_id: string; /** Read provider reports first; requires limit <= 20. Never moves money. */ force_sync?: boolean; } | { payment_id?: string; /** Refreshing requires the payment-specific branch of this request. */ force_sync?: false; }); interface ProcessorCostEventsResponse { data: ProcessorCostEventSource[]; total_count: number; has_more: boolean; } /** Signed native fees grouped without combining currencies. */ interface ProcessorCostEventBucket { connector: string; ownership: ConnectorOwnership; payment_method?: string | null; payment_method_type?: string | null; currency: string; exponent: number; amount: number; } /** Refund or dispute processor fees in one reporting period. */ interface ProcessorCostEventTerm { /** Absent when no usable USD amount is known. Partial amounts carry gap flags. */ amount_usd?: number; host_amount_usd?: number; source_count: number; reported_count: number; unavailable_count: number; unrecorded_count: number; incomplete: boolean; fx_incomplete: boolean; /** Missing signed credits make the host's margin unknown, not an upper bound. */ host_incomplete: boolean; host_fx_incomplete: boolean; breakdown: ProcessorCostEventBucket[]; } /** * What a period's payments cost, and what was left over. * * # Who this is for * * The merchant account holder — the host. **Never a shop owner**: gross minus * what the rail charged is the host's cost base, and publishing it to a third * party hands over the margin `platform_fee_amount` is redacted to protect. * The endpoint refuses a profile-scoped caller with a 403 rather than * returning a redacted shell, so a shop-owner caller should never be offered * this surface at all rather than shown one that fails. * * # The processor cost is not a subtrahend * * The rail deducts before the money reaches anyone here. Nothing in this * response moves `net_to_shop`, and no statement figure changes because of * it. This is margin reporting laid over settlement, not part of it. * * All `*_usd` figures are USD minor units (cents). */ interface SettlementCostResponse { /** Absent on servers predating method-cost reporting; absence is not an empty period. */ method_breakdown?: ProcessorCostMethodBucket[]; /** Signed known amounts are already included in processor_cost_usd; the * signed host_amount_usd is already included in host_processor_cost_usd. * Absent on servers predating fee events; this does not imply no fees. */ refund_processor_cost?: ProcessorCostEventTerm; /** Same inclusion rule as refund_processor_cost; these are fees, not principal. */ dispute_processor_cost?: ProcessorCostEventTerm; /** ISO-8601 inclusive start of the reported period (UTC). */ period_start: string; /** ISO-8601 exclusive end of the reported period (UTC). */ period_end: string; test_mode: boolean; /** Succeeded volume in the period, USD minor. */ gross_usd: number; /** What DeloPay charged the merchant for these attempts, USD minor. */ platform_fee_usd: number; /** * What the merchant charged its shop owners (hosting fees), USD minor. * Zero for a merchant that hosts nobody. */ merchant_fee_usd: number; /** Original payment fees plus known refund_processor_cost.amount_usd and * dispute_processor_cost.amount_usd, across all ownerships, USD minor. * Missing signed movements or their FX can make this recorded total lower * or higher than the final cost; incomplete does not imply a lower bound. */ processor_cost_usd: number; /** Original host-borne payment costs net of frozen pass-through deductions, * plus known signed host_amount_usd from both event terms. Negative fee * credits reduce this amount and payment_cost_usd/total_cost_usd; they * increase margin by the same magnitude when margin can be established. * Missing signed host fees make margin unknown; absent amounts are not zero. * Only explicitly shop-owned costs are excluded; unlabelled accounts follow * the same host-borne default as original payment costs. */ host_processor_cost_usd: number; /** Total cost in the period, USD minor. */ total_cost_usd: number; /** * What was left over, USD minor — **absent when it cannot be established**. * * Genuinely optional: the field is omitted from the wire, not sent as 0 and * not sent as null, whenever {@link margin_quality} is `'unknown'`. Treat * its absence as a state to render, never as a zero — a 0 here would claim * the period broke even, which is a different statement from *nobody knows*. * * When {@link margin_quality} is `'upper_bound'` the number is real but is * a ceiling: the true figure is this or lower. */ margin_usd?: number; /** How much weight {@link margin_usd} carries. */ margin_quality: MarginQuality; /** * Attempts the rail definitely charged for that carry no settlement line — * cost that is definitely missing. This is what drives * {@link margin_quality} to `'unknown'`. * * Distinct from {@link unlined_unresolved_attempt_count}, and not to be * added to it: one is missing money, the other is mostly ordinary * abandonment. Read both against {@link line_count} — "37 unaccounted out of * 4102" is a different statement from "37 out of 40". */ unlined_captured_attempt_count: number; /** * Attempts whose outcome never resolved — mostly ordinary abandonment and * payments still settling. Qualifies the figure rather than withholding it. */ unlined_unresolved_attempt_count: number; /** True exactly when both unlined counts are zero. */ coverage_complete: boolean; /** Whether the totals rest on observations, configured rates, or both. */ cost_basis: ProcessorCostBasis; /** Settlement lines in the period. */ line_count: number; /** Lines carrying a cost figure of any kind. */ priced_line_count: number; /** Lines whose cost the rail itself reported. */ reported_line_count: number; /** Lines whose cost came from a configured rate. */ estimated_line_count: number; /** Lines where the rail was asked and reports nothing. */ unavailable_line_count: number; /** Lines that pre-date cost recording, so nothing ever asked. */ unrecorded_line_count: number; /** Some payment cost or refund/dispute fee report is missing, stale or partial. * Missing signed charges or credits can understate or overstate the cost. * Read the event-term flags and margin_quality for their specific effects. */ processor_cost_incomplete: boolean; /** Some original or later fee cannot be converted to USD. Omitted signed * movements can raise or lower the total; this is not a universal lower bound. */ processor_cost_fx_incomplete: boolean; /** Some revenue currency had no USD rate, so its bucket is not in gross. */ fx_incomplete: boolean; /** Some platform-fee figure could not be resolved. */ platform_fee_incomplete: boolean; /** Per-connector cost breakdown. */ breakdown: ProcessorCostBucket[]; /** * The part of `processor_cost_usd` a shop owner carried because the host * switched pass-through on, USD minor. Reported so the drop in * `host_processor_cost_usd` has a name rather than looking like cost that * went missing. */ passed_through_cost_usd: number; /** * The revenue every cost below is subtracted from, USD minor: * `merchant_fee_usd` when `revenue_basis` is `hosting_fee`, * `sales_revenue.amount_usd` when it is `merchant_sales`. Never both. */ revenue_usd: number; /** Which revenue `revenue_usd` is. */ revenue_basis: RevenueBasis; /** The merchant's own sales. Present only when `revenue_basis` is `merchant_sales`. */ sales_revenue?: SalesRevenueTerm | null; /** * `platform_fee_usd + host_processor_cost_usd` — what the period's payments * cost the merchant, before anything the merchant spent on its own account. * This was the whole of `total_cost_usd` before the operating costs joined it. */ payment_cost_usd: number; /** What the host charged its shops, with the evidence behind the figure. */ hosting_fee: HostingFeeTerm; cost_of_goods: ProfitCostTerm; partner_shares: ProfitCostTerm; period_expenses: PeriodExpenseTerm; /** `cost_of_goods + partner_shares + period_expenses` — what the merchant spent on its own account, as distinct from what the payments cost. */ operating_cost_usd: number; } interface ShopFeeConfigParams { profile_id: string; } /** One fee schedule that applies to a shop. */ interface ShopFeeConfigEntry { schedule_id: string; connector?: string | null; profile_specific: boolean; fee_type: string; percentage_fee?: number | null; flat_fee_amount?: number | null; flat_fee_currency?: string | null; min_fee_amount?: number | null; max_fee_amount?: number | null; } interface ShopFeeConfigResponse { profile_id: string; schedules: ShopFeeConfigEntry[]; /** * Whether this shop currently carries the payment rail's own fee on top of * the hosting fee. Shown to the shop owner deliberately: this endpoint * exists so they can see the terms they are charged under. */ processor_cost_passthrough: boolean; } interface SettlementBackfillRequest { /** Restrict to one shop; omitted = all shops of the merchant. */ profile_id?: string; /** ISO-8601 inclusive start (UTC). Omitted = beginning of time. */ from?: string; /** ISO-8601 exclusive end (UTC). Omitted = now. */ to?: string; } interface SettlementBackfillResponse { enqueued: boolean; job_id: string; } interface ShopVisibilityUpdateRequest { profile_id: string; visible_to_shop: boolean; } interface ShopVisibilityResponse { profile_id: string; visible_to_shop: boolean; } /** * Operations that can carry limit rules. * * Which *dimensions* a rule may then set is per-operation, and the server * rejects an upsert that names one the operation cannot honour rather than * saving a rule that renders as configured and enforces nothing: * * - `refund` — every dimension. * - `settlement_adjustment` — amount dimensions only. There is no payment * behind an adjustment, so `max_payment_age_days` means nothing, and an * adjustment is hard-deleted with no tombstone, so a count read back from * live rows counts positions standing rather than adds performed. * - `settlement_payout` — everything but `max_payment_age_days`, there being * no payment behind a payout whose age a rule could ask about. Both windows * count the individual payments recorded against a statement, each of which * is attributed and server-timestamped. */ type LimitedOperation = 'refund' | 'settlement_adjustment' | 'settlement_payout'; /** Rule target: the merchant default, one role, or one user. */ type OperationLimitScope = 'merchant' | 'role' | 'user'; /** * What happens when an operation would exceed its limit. * * `block` refuses it outright (`DE_01`). `require_approval` parks it as a * request a second person decides on: the call fails with HTTP 409 `DE_06` * carrying `PendingApprovalErrorDetails`, and the operation executes only * once somebody approves it. Nothing was created either way — the difference * is that `require_approval` names a request that can still succeed. * * `require_approval` is accepted on refund rules alone; the settlement * members of `UpsertOperationLimitRuleRequest` take `block` only. */ type OperationLimitOnExceeded = 'block' | 'require_approval'; /** How the usage window is anchored. Rolling is the default. */ type OperationLimitWindowMode = 'rolling' | 'calendar'; /** * One persisted operation-limit rule. Enforcement resolves the most * specific rule: user > role > merchant. Amounts are minor units in * `currency`. */ interface OperationLimitRule { id: string; merchant_id: string; operation: LimitedOperation; scope: OperationLimitScope; /** * Always present on the wire (nullable, never omitted) — as are the four * limit fields below: the backend serializes every `Option` key. */ scope_id: string | null; max_amount_per_operation: number | null; max_total_amount_per_window: number | null; max_count_per_window: number | null; max_payment_age_days: number | null; window_hours: number; /** ISO currency code of the amount fields. */ currency: string; on_exceeded: OperationLimitOnExceeded; created_at: string; modified_at: string; } /** What every limit rule names, whatever it constrains. */ interface UpsertOperationLimitRuleBase { scope: OperationLimitScope; /** Required for `role`/`user` scopes; must be absent for `merchant`. */ scope_id?: string; /** Window length in hours (rolling mode). Defaults to 24; 1–720. */ window_hours?: number; /** Currency of the amount fields. Defaults to USD. */ currency?: Currency; } /** * A refund rule — the only operation that takes every dimension, and the only * one that can be sent for approval. */ interface UpsertRefundLimitRuleRequest extends UpsertOperationLimitRuleBase { operation: 'refund'; max_amount_per_operation?: number; max_total_amount_per_window?: number; max_count_per_window?: number; /** How old the payment being refunded may be. */ max_payment_age_days?: number; /** * What an over-limit refund does. Defaults to `block`, so a rule written by * a client that predates four-eyes keeps refusing rather than silently * becoming approvable. */ on_exceeded?: OperationLimitOnExceeded; } /** * A settlement-adjustment rule: amount dimensions only. * * There is no payment behind an adjustment, so an age limit means nothing, * and an adjustment is hard-deleted with no tombstone, so a count read back * from live rows counts positions standing rather than adds performed. The * server refuses either field rather than saving a rule nothing enforces. */ interface UpsertSettlementAdjustmentLimitRuleRequest extends UpsertOperationLimitRuleBase { operation: 'settlement_adjustment'; max_amount_per_operation?: number; max_total_amount_per_window?: number; max_count_per_window?: never; max_payment_age_days?: never; /** * `block` only. Approval needs an executor that can run the operation once * somebody says yes, and only refunds have one — the server refuses * `require_approval` here rather than storing it and quietly blocking. */ on_exceeded?: 'block'; } /** * A payout-recording rule: the per-operation ceiling and both windows. * * The windows were previously refused. Recording a payout used to overwrite * a single running figure on the statement, so it named only whoever acted * last, and the only date it carried was one the caller supplied — a * backdated request fell straight out of every window. Each payment is now * recorded as its own entry, attributed and server-timestamped, and that is * what the windows count. * * An age limit is still refused: there is no payment behind a payout whose * age the rule could ask about. */ interface UpsertSettlementPayoutLimitRuleRequest extends UpsertOperationLimitRuleBase { operation: 'settlement_payout'; max_amount_per_operation?: number; max_total_amount_per_window?: number; max_count_per_window?: number; max_payment_age_days?: never; /** `block` only — see `UpsertSettlementAdjustmentLimitRuleRequest`. */ on_exceeded?: 'block'; } /** * Body for `PUT /operation-limits/rules` — a full-replace upsert for one * target. Absent limit fields mean "this rule does not constrain that * dimension"; a request with no limit at all is rejected (delete the rule * instead). Amounts are minor units in `currency`. * * Which dimensions a rule may set depends on the operation, and the union * says so: the server rejects a rule naming one its operation cannot honour, * because a rule that saves, lists and renders while no hook can read it is * worse than a refused one. */ type UpsertOperationLimitRuleRequest = UpsertRefundLimitRuleRequest | UpsertSettlementAdjustmentLimitRuleRequest | UpsertSettlementPayoutLimitRuleRequest; interface OperationLimitRuleListParams { /** Without `operation`, every rule of the caller's merchant is returned. */ operation?: LimitedOperation; } interface OperationLimitRuleDeleteResponse { id: string; deleted: boolean; } /** * Merchant-level enforcement settings. Both fields have safe defaults, so * an untouched merchant behaves as: rolling window, admins not exempt. */ interface OperationLimitSettings { window_mode: OperationLimitWindowMode; /** Whether merchant/organization admins bypass limit rules. */ admins_exempt: boolean; } /** Body for `PUT /operation-limits/settings`. Only provided fields change. */ interface UpdateOperationLimitSettingsRequest { window_mode?: OperationLimitWindowMode; admins_exempt?: boolean; } /** * Lifecycle of an over-limit request. * * There is no `executed` state: approving and executing are two facts, so an * approved request that failed to execute stays `approved` and carries * `execution_error`. Read `executed_at` and `result_entity_id` to tell an * approval that ran from one that has not. */ type PendingOperationStatus = 'pending' | 'approved' | 'rejected' | 'expired'; /** Which limit the operation ran into, and by how much. */ interface PendingOperationLimitContext { /** The dimension that tripped, e.g. `max_amount_per_operation`. */ limit_type: string; /** The configured ceiling, in minor units of `currency` where it is an amount. */ limit?: number | null; /** What the operation asked for, on the same scale as `limit`. */ attempted?: number | null; /** For `max_payment_age_days`: how old the payment being refunded is. */ attempted_age_seconds?: number | null; currency?: string | null; } /** What the request is about, in the words of the operation that parked it. */ interface PendingOperationSummary { payment_id: string; /** Minor units of `currency`. */ amount?: number | null; currency?: string | null; reason?: string | null; } /** One over-limit request in the approvals inbox. */ interface PendingOperation { id: string; merchant_id: string; profile_id?: string | null; operation: LimitedOperation; status: PendingOperationStatus; summary?: PendingOperationSummary | null; idempotency_key?: string | null; /** The rule that diverted it, when it is still around. */ rule_id?: string | null; limit_context?: PendingOperationLimitContext | null; /** The user who asked. The server refuses to let them decide their own request. */ requested_by: string; requested_by_role_id?: string | null; request_reason?: string | null; decided_by?: string | null; decided_by_role_id?: string | null; decision_note?: string | null; /** RFC 3339 UTC; absent while `pending`. */ decided_at?: string | null; /** RFC 3339 UTC. Past this, the request expires and can no longer be approved. */ expires_at: string; /** RFC 3339 UTC. Set when the approved operation actually ran. */ executed_at?: string | null; /** The id the executed operation produced — a refund id, for a refund. */ result_entity_id?: string | null; /** Why an approved operation failed to execute. `approved` with this set is a real state. */ execution_error?: string | null; created_at: string; } interface PendingOperationListParams { /** * Which operation's requests to list. **Defaults to `refund`** — the list * is one operation at a time, not all of them, so a view that covers more * than refunds must ask per operation. */ operation?: LimitedOperation; /** * **Defaults to `pending`.** Omitting this filters to what is still * awaiting a decision, not to everything: approved, rejected and expired * requests are reachable only by asking for that status explicitly. */ status?: PendingOperationStatus; /** Defaults to 100, clamped to 1–500. */ limit?: number; } interface PendingOperationListResponse { requests: PendingOperation[]; } /** Body for approve/reject. The note is recorded on the request. */ interface DecidePendingOperationRequest { note?: string; } /** * `DelopayError.data` on the 409 `DE_06` a refund gets when a * `require_approval` rule parks it. Refunds are the only operation that can * be parked, so this is the only call that raises `DE_06`. * * A non-2xx deliberately: nothing was created, so a 2xx envelope would be * decoded as a refund that does not exist. Distinct from `DE_01`, which says * the operation will not happen at all — this one names a request a second * person can still approve, and until when. */ interface PendingApprovalErrorDetails { pending_operation_id: string; /** RFC 3339 UTC. */ expires_at: string; limit_context?: PendingOperationLimitContext | null; } /** Half-open amount range filter; minor units. `null` bounds are open. */ interface AmountFilter { start_amount?: number | null; end_amount?: number | null; } /** Sort order for filtered payment lists. */ interface PaymentListOrder { /** Column to sort on, e.g. `"created"` or `"amount"`. */ on: string; by: 'asc' | 'desc'; } /** * Body for `POST /payments/list` and `POST /payments/profile/list`. * `start_time` / `end_time` are top-level (the backend flattens the time * range), not nested. `test_mode`: `true` = test only, `false` = live only, * omitted = both. */ interface PaymentListFilterConstraints { /** General case-insensitive search across plaintext transaction fields. */ search?: string | null; /** Literal ID prefix (3+ characters), otherwise exact; supported across connectors. */ connector_transaction_id?: string | null; /** Literal ID prefix (3+ characters), otherwise exact; supported across connectors. */ connector_capture_id?: string | null; /** Literal ID prefix (3+ characters), otherwise exact; supported across connectors. */ connector_response_reference_id?: string | null; /** Literal ID prefix (3+ characters), otherwise exact; supported across connectors. */ connector_request_reference_id?: string | null; payment_id?: string | null; profile_id?: string | null; /** Filters to the business profiles assigned to this project. */ project_id?: string | null; customer_id?: string | null; customer_email?: string | null; /** Default 10, max 20 (server-enforced defaults; dashboards send their own). */ limit?: number; offset?: number | null; amount_filter?: AmountFilter | null; connector?: string[] | null; currency?: Currency[] | null; status?: IntentStatus[] | null; payment_method?: PaymentMethod[] | null; payment_method_type?: string[] | null; authentication_type?: AuthenticationType[] | null; merchant_connector_id?: string[] | null; card_network?: string[] | null; merchant_order_reference_id?: string | null; card_discovery?: string[] | null; /** ISO-8601 range start (flattened, top-level on the wire). */ start_time?: string | null; /** ISO-8601 range end. */ end_time?: string | null; /** `true` = test only, `false` = live only, omitted = both environments. */ test_mode?: boolean | null; /** * Filter by what the rail says about settling the payment. Values combine * as OR, and each is the same word * the row's `connector_settlement_status` carries, so a status read off * one payment can be searched for. * * There is deliberately no "not paid out" value: it would have to fold in * the rails that never report settlement (`unavailable`), the rows nothing * has asked yet (`unknown`) and the attempts with no settlement row * (`not_recorded`), and that would claim a rail is holding money it never * said it held. "Still outstanding" is `pending` + `available` + * `payout_failed`. */ connector_settlement_status?: ConnectorSettlementStatus[] | null; /** * Filter by the mechanism that chose the payment's connector — the word * the row's `routing_approach` carries. Values combine as OR. * * `not_recorded` selects payments that carry no approach, and `other` * payments that carry one the server has no name for. Neither is * `default_fallback`, which is a routing decision that was recorded. * Accepted on the profile-scoped list too: the approach is shown on every * row there, so filtering on it reveals nothing new. */ routing_approach?: RoutingApproachFilter[] | null; /** Omitted = backend default (`created`, descending). */ order?: PaymentListOrder | null; } /** * The filter body `POST /payments/profile/list` accepts * (`payments.listByProfileFilter()`): the merchant-wide constraints minus the * keys the route resolves from the caller's own profile (`profile_id`, * `project_id`) and minus `connector_settlement_status`, which the backend * refuses on this route (403). A profile-scoped viewer is not shown the * settlement state, and a filter it could probe one value at a time would * hand it back through `total_count`. */ type ProfilePaymentListFilterConstraints = Omit & { profile_id?: never; project_id?: never; connector_settlement_status?: never; }; /** One connected account a payments list can be narrowed to. */ interface MerchantConnectorInfo { connector_label: string; merchant_connector_id: string; } /** * Which options a payments list can be narrowed by: the body of * `GET /payments/filter` (`payments.getFilters()`) and * `GET /payments/profile/filter` (`payments.getFiltersByProfile()`). * * Every list except `connector` and `payment_method` is enumerated from the * vocabulary rather than from the merchant's data, so a value appears here * even when no payment carries it yet — including the filter values that * describe an absence, such as `not_recorded`. * * The index signature is load-bearing: an interface has no implicit one, so * without it a caller that stored the previous `Record` * return in a variable of that type would stop compiling. */ interface PaymentListFiltersExt { [key: string]: unknown; /** Connected accounts, keyed by connector name. */ connector: Record; currency: Currency[]; status: IntentStatus[]; /** Payment method types, keyed by payment method. */ payment_method: Record; authentication_type: AuthenticationType[]; card_network: string[]; card_discovery: string[]; connector_settlement_status: ConnectorSettlementStatus[]; /** Every value {@link PaymentListFilterConstraints.routing_approach} accepts. */ routing_approach: RoutingApproachFilter[]; } /** Response of the filtered payment list endpoints (`count` = page size). */ interface PaymentListFilteredResponse { count: number; total_count: number; data: PaymentResponse[]; } /** * Where one attempt's money has got to, according to its rail. **Not** the * hosted-shop `SettlementPayoutStatus`, * which is a host ticking a monthly statement off by hand; this is * acquirer → merchant bank, reported by the rail, per payment. * * Total on purpose: every attempt carries exactly one of these, including * the three that describe an absence, and a client must never default one. * * - `pending` — the rail holds the money and says it has not released it yet. * - `available` — released into the balance the rail holds for this account. * **Not paid out**: the bank transfer is a separate object on a separate * schedule, and an account on a manual payout schedule can sit here * indefinitely. Never render it as "paid out". * - `paid_out` — the rail says it sent this money to a bank account. The only * value entitled to a payout date; `payout_failed` may also carry a reference. * - `payout_failed` — a payout that failed or was reversed, returning the * funds to the rail's balance. **Possibly permanent**: nothing shows the * value later moves to `paid_out` even when the money is paid again, so do * not build "stuck payout" alerting on it clearing itself. * - `unavailable` — this rail does not report settlement at all (Creem, * e-Payouts, Cryptomus, and any rail nobody has wired up). A fact about our * reach, never about the money — it is not "not yet paid out". * - `unknown` — a settlement record exists and this release can read no state * out of it (nothing asked of the rail yet, or a value written by a newer * release). * - `not_recorded` — no settlement record exists for the attempt. Ordinarily * because no capture was ever established, and never proof that no money * moved. */ type ConnectorSettlementStatus = 'pending' | 'available' | 'paid_out' | 'payout_failed' | 'unavailable' | 'unknown' | 'not_recorded'; /** One attempt of a payment, and what its rail says about settling it. */ interface ConnectorSettlementAttempt { /** `{payment_id}_{n}` — unique per merchant, not globally. */ attempt_id: string; /** * The attempt's own payment status, so a `not_recorded` can be read: absent * on a `failure` attempt is the ordinary case; absent on a `charged` one is * a gap worth chasing. */ attempt_status: AttemptStatus; /** The rail this attempt ran on. */ connector?: string | null; /** The connector account it ran on. */ merchant_connector_id?: string | null; /** Where the money has got to. Always present. */ status: ConnectorSettlementStatus; /** * When the rail says the funds became (or become) available in its own * balance — a date, not a promise, and nothing about a bank transfer. * Withheld only beside an `unknown` of the unreadable-value kind. */ available_on?: string | null; /** * The rail's identifier for the batch that carried **or attempted** this * payment to a bank. Present beside `paid_out` and `payout_failed`, and * beside nothing else. */ payout_reference?: string | null; /** * When the rail said the batch carrying this payment would reach a bank — * the payout's own arrival date, **not** `available_on`. Present beside * `paid_out` and nothing else: a date beside `payout_failed` would assert * that money landed. `null` is never a statement that the payment is * unpaid; `status` answers that. */ paid_out_at?: string | null; /** * When this record was last written by Delopay — how fresh the answer is, * not when the rail acted. `null` where there is no record. */ last_recorded_at?: string | null; /** * What the rail itself took for this attempt, in * {@link ConnectorSettlementAttempt.processor_cost_currency} — **not** * necessarily the payment's currency, and never a subtrahend of anything * else here: the rail deducts before the money reaches anyone downstream. * * Recorded for **every** merchant, which is the point of the field. The * same figure lives on a settlement line, and that line exists only for a * merchant with hosting-fee configuration. * * `null` is no figure and **never** a zero — a zero would say the rail is * free. {@link ConnectorSettlementAttempt.processor_cost_source} says which * kind of absence it is. */ processor_cost_amount?: number | null; /** * The unit `processor_cost_amount` is denominated in: the **rail's** * currency, routinely not the payment's. */ processor_cost_currency?: string | null; /** * Decimal places `processor_cost_amount` is expressed in: * `processor_cost_amount / 10 ** processor_cost_exponent` units of * `processor_cost_currency`. * * Present whenever the amount is, because the amount is not readable * without it — a rail can report in an asset whose precision is not a * property of its ticker (TRX is 10^6 where most of its neighbours are * 10^8), so amount and currency alone need per-connector knowledge to read. */ processor_cost_exponent?: number | null; /** * How the figure was obtained: `reported` (the rail said so), `estimated` * (derived from a configured cost schedule) or `unavailable` (asked, and * this rail has no answer). **An estimate must never be presented as an * observation.** * * Absent or `null` is a fourth state and is **not** `unavailable`: nothing * was ever written for this attempt — the row predates the column, or a * replica that did not know about it recorded the payment. Keep the two * apart. `unavailable` is the rail's own answer; an absent source is a gap * in our recording, and merging them reports the gap as a rail that charges * nothing. * * Widened with `(string & {})` deliberately: the backend serves the stored * value verbatim rather than mapping it, so a value written by a newer * release arrives here unrecognised. Render such a value raw rather than * folding it into a neighbour. */ processor_cost_source?: ProcessorCostSource | (string & {}) | null; /** * The schedule and rates an `estimated` cost was priced from, as they stood * when it was priced. Absent beside a `reported` or `unavailable` cost, and * beside an estimate priced from DeloPay's own acquirer contract rather * than the merchant's own schedule — absence is never a statement that no * schedule applied. */ processor_cost_basis?: ProcessorCostEstimateBasis | null; /** * The processor's own method identifier the cost was priced under, where * the payment method type cannot tell two vendors apart (the e-Payouts * vendor code). `null` for a rail that carries none and for one whose code * could not be established — the response does not distinguish those two, * because nothing downstream prices on the difference. */ connector_method_code?: string | null; } /** * Response of `GET /payments/{payment_id}/settlement` * (`payments.settlement()`): what the payment's rail says about settling * each attempt the caller may see, oldest first. Attempts with no settlement * record are listed too, as `not_recorded`. * * A caller narrowed to specific connector accounts receives only the * attempts on connectors it may see, with nothing marking what was omitted — * so `count` is the number returned, never the number that exist. */ interface PaymentConnectorSettlementResponse { payment_id: string; /** How many attempts are in `attempts` — the number returned, never a denominator. */ count: number; attempts: ConnectorSettlementAttempt[]; } /** Response of `DELETE /payments/{payment_id}` (soft delete). */ interface PaymentsDeleteResponse { payment_id: string; merchant_id: string; /** Always `true` on success — the endpoint errors otherwise. */ deleted: boolean; } /** * The effective deletable-status set for the calling merchant — lets a * dashboard show the delete action only where allowed. */ interface PaymentsDeletePolicyResponse { statuses: IntentStatus[]; } /** * One client/device observation captured while the buyer interacted with a * payment: a checkout open, a confirm, a redirect leg or buyer-reported * client signals. */ interface PaymentClientContextEntry { /** Capture point, e.g. `checkout_open`, `confirm`, `redirect_return`, `client_signals`. */ source: string; device_id?: string | null; /** Partitioned (CHIPS) twin of `device_id`, scoped to the embedding site. */ partitioned_device_id?: string | null; ip_address?: string | null; ip_country?: string | null; user_agent?: string | null; accept_language?: string | null; /** * Best-effort hardware-model guess derived server-side. Display/analytics * only; never make decisions on it. */ device_model?: string | null; /** Capture-point-specific extras (client hints, screen size, referrer …). */ extra?: Record | null; /** * The session-replay session this observation was captured during, when the * buyer consented to recording and the checkout reported it. `null` is the * ordinary case — no consent, no hosted checkout, or a payment older than * the feature — and means "no recording", never "not loaded yet". * */ replay_session_id?: string | null; created_at: string; } interface PaymentClientContextListResponse { payment_id: string; count: number; /** All captured observations, oldest first. */ data: PaymentClientContextEntry[]; } /** * One e-Payouts rail the buyer can pick. `payment_method`, * `payment_method_type` and `payment_method_data` are echoed verbatim on the * confirm call — never derive them client-side from `category`, the mapping * is not 1:1. */ interface EpayoutsMethod { /** e-Payouts vendor code (wire name `type`), passed back verbatim. */ type: string; display_name: string; category: string; payment_method: string; payment_method_type: string; payment_method_data: Record; pmin?: number; pmax?: number; pinned_country?: string; /** Sanitised inline SVG for the tile icon, when the merchant set one. */ icon_svg?: string; } interface EpayoutsMethodsResponse { country: string; currency: string; methods: EpayoutsMethod[]; /** * Lowercase ISO 3166-1 alpha-2 codes of every country the catalog can * mint some method for — drives the pane's country picker. */ supported_countries: string[]; /** * e-Payouts' public PayPal client id, for the experimental PayPal SDK panes * (`ppal:paypal_express`, `ppal:paypal_direct_form`). Present only when the * catalogue was asked with `{ paypalSdk: true }` and `ppal` is among * `methods`. Load the PayPal JS SDK with it, never anything secret. */ paypal_client_id?: string; /** The currency e-Payouts loads the PayPal SDK with (`USD`), beside `paypal_client_id`. */ paypal_currency?: string; } /** Options for `CheckoutSession.epayoutsMethods`. */ interface EpayoutsMethodsOptions { /** * Also ask for e-Payouts' PayPal client id (`paypal_client_id`). Only a * checkout drawing a PayPal SDK pane should: resolving it can cost e-Payouts * an access code, and the router answers it only for a shop that placed one. */ paypalSdk?: boolean; /** * The e-Payouts account the PayPal SDK pane confirms on * (`merchant_connector_id`), so the client id is that account's. Read only * with `paypalSdk`. */ merchantConnectorId?: string; } /** * The PayPal order e-Payouts expects for the payment's access code, for the * PayPal SDK's `createOrder` on an experimental PayPal SDK pane. Build * `actions.order.create` from it verbatim: `purchase_units: [{ amount: { value: * amount, currency_code: currency }, custom_id: code, soft_descriptor, * description }]`, `application_context: { brand_name, shipping_preference, * user_action: 'PAY_NOW' }`. */ interface EpayoutsPaypalOrderResponse { /** The e-Payouts access code — the order's `custom_id`. */ code: string; /** `purchase_units[0].amount.value`, verbatim. */ amount: string; /** `purchase_units[0].amount.currency_code`. */ currency: string; /** `purchase_units[0].soft_descriptor`. */ soft_descriptor: string; description?: string; brand_name?: string; shipping_preference?: string; /** * The PayPal client id on e-Payouts' page for this code. Create the order * only with a PayPal SDK loaded with this id. */ client_id?: string; /** * PayPal may already have taken money for this code: a capture was reported, * or an approval whose capture never reported back. Do not create another * order: the payment waits for e-Payouts' confirmation. */ captured: boolean; } /** Which PayPal SDK button the buyer paid with. */ type EpayoutsPaypalFundingSource = 'paypal' | 'card'; /** * A PayPal order's progress on an experimental PayPal SDK pane, reported from * `onApprove`: first an approval, before asking PayPal to capture, then the * capture PayPal answered — or a decline when PayPal refused the buyer's * funding. An approval that no capture, decline or `NOT_CAPTURED` followed * blocks any further order on the payment, because its capture may have gone * through unseen. `NOT_CAPTURED` is the way out when the approval's report went * unacknowledged and no capture was asked for: once the router records it, a * new order can be created. */ type EpayoutsPaypalCaptureRequest = EpayoutsPaypalProgressReport | EpayoutsPaypalCaptureReport; /** * An approval (`APPROVED`, `data.orderID`, before capturing), a decline * (`INSTRUMENT_DECLINED`, before `actions.restart()`), or `NOT_CAPTURED` — the * checkout stopped before capturing because its approval report was not * acknowledged, which releases an approval the router may hold. Carries no * capture. */ interface EpayoutsPaypalProgressReport { order_id: string; status: 'APPROVED' | 'INSTRUMENT_DECLINED' | 'NOT_CAPTURED'; funding_source: EpayoutsPaypalFundingSource; capture_id?: never; amount?: never; currency?: never; } /** A capture's status as PayPal answers it. Only `COMPLETED` and `PENDING` took money. */ type EpayoutsPaypalCaptureStatus = 'COMPLETED' | 'PENDING' | 'DECLINED' | 'FAILED' | 'PARTIALLY_REFUNDED' | 'REFUNDED'; /** The capture PayPal answered to `actions.order.capture()`. Every field is required. */ interface EpayoutsPaypalCaptureReport { /** `orderData.id`. */ order_id: string; status: EpayoutsPaypalCaptureStatus; /** `orderData.purchase_units[0].payments.captures[0].id`. */ capture_id: string; /** The capture's `amount.value`. */ amount: string; /** The capture's `amount.currency_code`. */ currency: string; funding_source: EpayoutsPaypalFundingSource; } /** The router's answer to a reported PayPal capture. */ interface EpayoutsPaypalCaptureResponse { /** * The payment's status now. It stays `requires_customer_action` until * e-Payouts' notification lands; poll the payment for the outcome. */ payment_status: IntentStatus; /** Whether the reported capture is the order's amount and currency. Absent for an approval or a decline. */ matches_order?: boolean; } /** The closed set of buyer-side checkout events. */ type CheckoutEventKind$1 = 'native_pane_selected' | 'native_pane_tab_opened' | 'native_pane_tab_blocked' | 'native_pane_abandoned' | 'native_pane_returned' | 'checkout_cancelled'; interface RecordCheckoutEventRequest { event: CheckoutEventKind$1; /** Native-pane catalog key the event is about (`apple_pay`, `klarna`, …). */ method: string; } interface RecordCheckoutEventResponse { /** * `false` when the event was deduplicated or the per-payment cap was * reached. Informational — checkouts do not branch on it. */ recorded: boolean; } /** What a checkout page needs to stand up a VGS Collect form. */ interface VaultCollectSessionResponse { /** VGS tenant id (`tnt...`), first argument to `VGSCollect.create`. */ vault_id: string; /** `sandbox` or `live` — second argument to `VGSCollect.create`. */ environment: string; /** Inbound route the Collect form posts through, when one is configured. */ route_id?: string | null; /** Write-only bearer token for `form.createAliases({ access_token })`. */ access_token: string; /** Remaining lifetime in seconds. */ expires_in: number; } /** * A card the processor already holds for this attempt, named by the * processor's own reference. * * The buyer entered the card in the processor's fields in their browser — * PayPal Card Fields — so no card number ever reached Delopay. `reference` * is the order the card was attached to and `merchant_connector_id` the * account that order lives on; the confirm is pinned to that account and * refused for any other, because an order on account A cannot be captured * on account B. Sent as `payment_method_data: { card_reference }` with * `payment_method: 'card'`. */ interface CardReference { /** For PayPal, the order id the Card Fields submit attached the card to. */ reference: string; merchant_connector_id: string; } /** * `POST /payments/{payment_id}/post-session-tokens` — mint the processor-side * session a browser SDK completes: the PayPal order the SDK buttons or Card * Fields (`payment_method: 'card'`) attach the buyer's choice to. */ interface PaymentsPostSessionTokensRequest { payment_method: PaymentMethod; payment_method_type: PaymentMethodType; /** * The merchant connector account the session must be created on. Required * when the checkout's card surface already committed the attempt to one * account: the confirm that follows is pinned to the same account, and a * session minted on a sibling could not be completed there. Re-validated by * the router and refused rather than re-routed. */ merchant_connector_id?: string | null; } /** The call a browser SDK is to make next, as the spec's `NextActionCall`. */ type NextActionCall = 'post_session_tokens' | 'confirm' | 'sync' | 'complete_authorize' | 'await_merchant_callback' | 'eligibility_check' | { deny: { message: string; }; }; /** What the browser SDK is to do next with the session the router minted. */ interface SdkNextActionData { next_action: NextActionCall; /** The processor's order id — for PayPal, what `createOrder` returns. */ order_id?: string | null; } /** Every `type` the spec's `NextActionData` union can carry. */ type NextActionDataType = 'redirect_to_url' | 'redirect_inside_popup' | 'display_bank_transfer_information' | 'third_party_sdk_session_token' | 'qr_code_information' | 'fetch_qr_code_information' | 'invoke_upi_intent_sdk' | 'invoke_upi_qr_flow' | 'display_voucher_information' | 'wait_screen_information' | 'three_ds_invoke' | 'invoke_sdk_client' | 'collect_otp' | 'invoke_hidden_iframe'; /** The one next action that carries a session for a browser SDK to complete. */ interface InvokeSdkClientNextAction { type: 'invoke_sdk_client'; next_action_data: SdkNextActionData; } /** * Any other member of the spec's `NextActionData` union. Only the * discriminator is typed: narrowing on `type === 'invoke_sdk_client'` is what * makes `order_id` reachable, and every other action keeps its fields as the * spec declares them. */ interface OtherNextAction { type: Exclude; [key: string]: unknown; } type PaymentsPostSessionTokensNextAction = InvokeSdkClientNextAction | OtherNextAction; interface PaymentsPostSessionTokensResponse { payment_id: string; next_action?: PaymentsPostSessionTokensNextAction | null; status: IntentStatus; } interface VaultPaymentMethodRequest { /** The card-number alias from `createAliases` (must be card-shaped). */ card_number_alias: string; /** Expiry in the clear by necessity — VGS never hands raw values back. */ card_exp_month: string; card_exp_year: string; card_holder_name?: string; nick_name?: string; card_network?: string; } interface VaultPaymentMethodResponse { /** Absent for a guest checkout, which stores no payment method. */ payment_method_id?: string; /** Last four of the format-preserving alias — the real card's last four. */ last4: string; card_network?: string | null; /** * One-shot spendable token: put on the confirm call as `payment_token` * alongside `payment_method: "card"`. */ payment_token: string; } /** * Body of `POST /payment-link/{merchant_id}/{payment_id}/stripe-confirm`: the * Stripe card form's PaymentIntent, confirmed by the router once it has judged * the card's issuing country against the shop's sell-to restrictions (a shop * without restrictions is never refused). Used when the checkout payload * carries `card_connector_server_confirm: true`. */ interface StripeHostedConfirmRequest { /** * The PaymentMethod `stripe.createPaymentMethod` created from the card * form's Elements (`paymentMethodCreation: 'manual'`). Omit it to finalize an * intent Stripe returned to `requires_confirmation` after its required * action (3-D Secure). */ payment_method?: string | null; /** * Refused with `400`. Stripe will not confirm a manually confirmed * PaymentIntent with a ConfirmationToken collected through Elements, so the * router takes a PaymentMethod instead; a checkout that still sends a token * is told to reload. * * @deprecated Send `payment_method`. */ confirmation_token?: string | null; /** * The acquisition channel the checkout reports on the Stripe return URL * (`hosted_checkout`, `hosted_checkout_iframe`, or * `hosted_checkout_auto_redirect`). */ pcs?: string | null; } /** Stripe's reason for declining the card. */ /** * What the checkout sends from COPYandPAY's pre-submit hook before the widget * submits a card on a checkout stamped `submit_grant`, beside always-shown * wallet buttons of another account. */ interface NomupayCardAuthorizationRequest { /** The card form's checkout (`nomupay_oppwa.checkout_id`). */ checkout_id: string; } /** The router's answer to a NomuPay card authorization request. */ interface NomupayCardAuthorizationResponse { /** * Whether the widget may submit the card. `false` when the payment no longer * waits for a payment method, or the checkout is not this attempt's live * card-form checkout stamped `submit_grant`. The wallet buttons beside the * form are closed before `true` is answered; when one of them is already * being paid, the route answers an error instead. */ authorized: boolean; } interface StripeHostedConfirmDecline { /** Stripe's error code, e.g. `card_declined`. */ code?: string | null; /** The issuer's reason, e.g. `insufficient_funds`. */ decline_code?: string | null; /** Stripe's buyer-facing explanation. */ message?: string | null; } /** Where a server-side confirm left the Stripe PaymentIntent. */ interface StripeHostedConfirmResponse { /** The Stripe PaymentIntent id. */ payment_intent_id: string; /** * Stripe's status of the intent. `requires_action` asks the checkout to run * `stripe.handleNextAction` with `client_secret` and confirm again without a * payment method; `requires_payment_method` follows a decline. */ status: string; /** The intent's Stripe client secret, for `stripe.handleNextAction`. */ client_secret: string; /** Why Stripe declined the card, when it did. */ decline?: StripeHostedConfirmDecline | null; } /** Narrow payload of `POST /shops/{merchant_id}/{shop_id}/checkout-branding`. */ interface CheckoutBrandingUpdate { /** Applied as a whole-object replace of `payment_link_config`. */ payment_link_config?: BusinessPaymentLinkConfig | null; } /** * Response of both checkout-branding routes: the `GET` on * `/shops/{merchant_id}/{shop_id}/checkout-branding`, and the `POST` echoing * back what it persisted. * * Deliberately not a `ProfileResponse`: that carries `payment_response_hash_key` * — the webhook signing secret — plus the card-vault and authentication * configuration, and a role that may only restyle a checkout has no business * reading any of it, whether it asked to load the checkout or has just saved * it. This carries what the branding editor renders and nothing else. */ interface CheckoutBrandingResponse { /** The shop (business profile) this branding belongs to. */ profile_id: string; /** Name of the shop, for labelling the editor. */ profile_name: string; /** * `null` means the shop has never been styled — the untouched default, not * an error. Keep the `null`: it is what tells the editor to seed its own * defaults rather than to render a stored, empty style. */ payment_link_config: BusinessPaymentLinkConfig | null; } /** A VGS vault environment. */ type VaultEnvironment = 'sandbox' | 'live'; /** What a VGS route is for: inbound card capture or outbound reveal. */ type VaultRoutePurpose = 'collect' | 'reveal'; type VaultRouteChangeKind = 'create' | 'update' | 'unchanged'; interface VaultRouteFieldChange { path: string; from?: string | null; to?: string | null; } type VaultRouteWarningCode = 'templated_connector_base_url' | 'applier_reported'; /** A non-fatal finding from a vault-route preview or apply. */ interface VaultRouteWarning { code: VaultRouteWarningCode; connector?: string | null; detail?: string | null; } interface VaultRouteChange { route_id: string; purpose: VaultRoutePurpose; change: VaultRouteChangeKind; field_changes: VaultRouteFieldChange[]; } /** * What the *processor* says about a connector account. * * Distinct from `disabled` and `status`, which are values DeloPay stores: this * is the processor's own answer, fetched when asked. It exists because a * processor can revoke an account's ability to take payments while the * credentials stay valid and the API stays up — which produces no failed * payments and no errors, only checkouts that quietly never complete. */ type ConnectorHealthState = /** The processor accepts charges and every capability asked about is active. */ 'healthy' /** Charges work, but a capability is inactive or pending, or the account has * outstanding requirements. The shop is taking money; somebody should look. */ | 'degraded' /** The processor will not accept charges. Payments stall at the checkout * without producing declines, so this will NOT show up as failed payments. */ | 'cannot_accept_payments' /** The question could not be asked, or this connector has no probe. * Establishes nothing either way — never render it as good or bad news. */ | 'unknown'; /** * Why the router could not establish an answer. Present exactly when `state` is * `'unknown'`. * * A stable code rather than a sentence: the router compiles no merchant-facing * prose, so render your own words per code. Branch on it when the action * differs — "this build has no probe for that connector" is a different message * from "the processor did not answer". */ type ConnectorHealthUnknownReason = /** No probe is implemented for this connector. */ 'connector_not_supported' /** The account's stored credentials could not be read. */ | 'credentials_unreadable' /** The stored credentials hold no API key for this processor. */ | 'credentials_missing_api_key' /** The account runs in test mode with no sandbox credentials, so there is * nothing to probe that a payment would actually use. */ | 'sandbox_credentials_missing' /** The processor could not be reached at all — DNS, TLS, timeout, a proxy. * Says nothing about the credentials or the account. */ | 'processor_unreachable' /** The processor answered and refused the request: the key is wrong, * revoked, or lacks the scope. A credential to fix, not an outage to wait * out — which is why it is a separate code. */ | 'processor_rejected_credentials' /** The processor answered with something this build could not read. Neither * an outage nor a credential fault. */ | 'processor_response_unreadable' /** The processor answered without saying whether it will accept charges. */ | 'processor_did_not_report_chargeability'; /** * Where one capability stands with the processor. `unknown` covers a value the * router did not recognise, and must not be shown as active. */ type ConnectorCapabilityState = 'active' | 'inactive' | 'pending' | 'unknown'; /** * One capability row. `id` is the processor's own key (`card_payments`, * `link_payments`, …), deliberately a string rather than a union: processors * add capabilities on their own schedule, and a union would mean a new one is * dropped rather than displayed. Translate the ones you recognise, render the * rest verbatim. */ interface ConnectorCapability { id: string; state: ConnectorCapabilityState; } /** Outstanding processor requirements, when the processor publishes any. */ interface ConnectorHealthRequirements { disabled_reason?: string | null; currently_due?: string[]; past_due?: string[]; errors?: string[]; } type ConnectorHealthCheckStatus = 'healthy' | 'unhealthy' | 'unknown' | 'unsupported'; interface ConnectorHealthCheck { id: string; status: ConnectorHealthCheckStatus; reason: string; } /** * Result of `GET .../connectors/{connectorId}/health`. * * The nullable fields — `can_accept_charges`, `requirements`, * `processor_account_id` and `unknown_reason` — arrive either omitted or as an explicit * `null`, and the two mean the same thing: the router established nothing about * that value. Neither is exceptional and neither should be special-cased. * * `capabilities` is optional but never `null`: the router omits it when it has * no capabilities to report. */ interface ConnectorHealthResponse { credential_source?: string | null; checks?: ConnectorHealthCheck[]; stale?: boolean; merchant_connector_id: string; connector: string; /** Which credential slot was probed, so "healthy" is attributable to an * environment. */ test_mode: boolean; state: ConnectorHealthState; /** * Whether the processor will accept charges. * * `null` and absent both mean unknown — the question could not be asked, or * the processor did not answer it. Never read either as `false`: a * restriction that was never observed is not a restriction. `false` appears * only when the processor said so. */ can_accept_charges?: boolean | null; capabilities?: ConnectorCapability[]; requirements?: ConnectorHealthRequirements | null; /** The processor's own account id, when it names one. Worth surfacing: one * processor account commonly backs several connector accounts, so a * restriction on it takes down every shop that shares it at once. */ processor_account_id?: string | null; /** * The account's own name at the processor — its dashboard display name, or * its business name when there is no dashboard. Never a label typed into * DeloPay. Absent when the processor did not report one; render that as * "not reported", never as blank and never as your own connector label. */ processor_account_name?: string | null; /** The account's country as the processor reports it (ISO 3166-1 alpha-2 * for Stripe). Absent when unreported. */ processor_account_country?: string | null; /** The account's default currency as an upper-case ISO 4217 code. The * account default only — not a promise about which currency a given payout * settles in. Absent when unreported. */ processor_default_currency?: string | null; /** The email address the processor associates with the account. Not a * login: Stripe documents it as unused for authentication. Absent when * unreported. */ processor_account_email?: string | null; /** The processor's account type, verbatim — for Stripe the legacy * `standard` / `express` / `custom` configuration, or `none`. Describes * how the account is configured, not whether it is a platform or a * connected account. A string rather than a union so a value this SDK does * not know still passes through. Absent when unreported. */ processor_account_type?: string | null; /** Whether the processor will pay out to the merchant. Informational * beside `can_accept_charges` and does not move `state`. `null` and absent * both mean unreported — read neither as `false`. */ payouts_enabled?: boolean | null; /** Why nothing could be established. Always present when `state` is * `'unknown'`, never present otherwise. A stable code, not copy — render * your own words for it. */ unknown_reason?: ConnectorHealthUnknownReason | null; /** ISO-8601. When the probe ran — show it rather than implying the answer is * live. */ probed_at: string; } type VaultCheckId = 'collect_credentials_valid' | 'collect_write_only' | 'management_scopes' | 'vault_reachable' | 'environment_coherent' | 'reveal_route_covers_processors' /** Whether the vault's outbound route admits only DeloPay's egress. VGS * enforces a route's `source_endpoint`, so one left open lets any holder of * the proxy credentials reveal a stored alias. Advisory: it never blocks * cloaking being switched on. */ | 'reveal_route_restricted_to_our_egress' | 'collect_route_exists' | 'ca_certificate_configured'; type VaultCheckStatus = 'pass' | 'fail' | 'unknown'; interface VaultCheck { id: VaultCheckId; status: VaultCheckStatus; /** Always present on the wire (nullable, never omitted). */ detail: string | null; } interface VaultVerifyRequest { profile_id: string; } /** Result of `POST .../vault/verify` — configuration checks for a vault MCA. */ interface VaultVerificationResponse { passed: boolean; checks: VaultCheck[]; /** Vault egress IPs the merchant's processors may need to allowlist. */ egress_ips_to_allowlist: string[]; } interface VaultRoutesPreviewRequest { profile_id: string; /** `sandbox` or `live`; omitted = derived from the credentials. */ environment?: VaultEnvironment; } /** * Fingerprint of the vault's current routes. `null` is a real value meaning * "no routes exist" (the wire is an untagged enum), and must be sent back * as `null` on apply rather than omitted. */ type VaultRoutesFingerprint = string | null; interface VaultRoutesPreviewResponse { vault_id: string; environment: VaultEnvironment; /** Opaque token covering the desired route document; echo on apply. */ desired_fingerprint: string; desired_upstream_hosts: string[]; /** Opaque token covering what exists now; echo on apply. See {@link VaultRoutesFingerprint}. */ current_fingerprint: VaultRoutesFingerprint; changes: VaultRouteChange[]; warnings: VaultRouteWarning[]; } interface VaultRoutesApplyRequest { profile_id: string; environment?: VaultEnvironment; /** * From the preview, byte for byte. `null` means the preview found no * routes and is a real value — an absent key is refused by the router. */ expected_current_fingerprint: VaultRoutesFingerprint; /** From the same preview, byte for byte. */ expected_desired_fingerprint: string; } interface VaultRouteIds { collect: string; reveal: string; } interface VaultRouteApplyVerification { established: string[]; not_established: string[]; routes_appeared: string[]; } interface VaultRoutesApplyResponse { applied: boolean; route_ids: VaultRouteIds; collect_route_id_stored: boolean; warnings: VaultRouteWarning[]; verification?: VaultRouteApplyVerification | null; } /** * Body of `PATCH /user/metadata` and `PATCH /user/merchant/metadata` — an * RFC 7396 merge patch over the metadata bucket. An object merges key by * key (a `null` value removes that key); a root-level `null` clears the * whole bucket. Anything else is rejected with a 400. */ interface UpdateMetadataRequest { patch: Record | null; } /** Body for PUT /user/preferences/{key}. Opaque object, at most 16 KiB of compact UTF-8 JSON. */ interface SetUserPreferenceRequest { value: Record; /** Preserve an existing object (including a reset tombstone) and return the winner. */ if_absent?: boolean; } /** Own per-page preference. A null value means no saved preference: use defaults. */ interface UserPreferenceResponse { key: string; value: Record | null; } /** How a catalog entry's country coverage is interpreted. */ type EpayoutsLocality = 'country_locked' | 'regional' | 'universal'; /** Which processing rail a catalog entry mints codes for. */ type EpayoutsRail = { kind: 'local_bank_redirect'; } | { kind: 'credit_card_redirect'; } | { kind: 'bank_redirect'; pmt: string; data_variant: string; } | { kind: 'wallet'; pmt: string; data_variant: string; } | { kind: 'bank_transfer'; pmt: string; data_variant: string; } | { kind: 'voucher'; pmt: string; }; interface EpayoutsCatalogEntry { vendor_code: string; family: string; display_name?: string | null; category?: string | null; /** Sanitised inline SVG for the tile icon, when set. */ icon_svg?: string | null; /** ISO 3166-1 alpha-2 codes (lowercase) the entry covers. */ coverage: string[]; pmin?: number | null; pmax?: number | null; enabled: boolean; locality: EpayoutsLocality; rail: EpayoutsRail; } interface EpayoutsCatalogResponse { entries: EpayoutsCatalogEntry[]; /** Set by a sync sweep: how many countries were probed / answered. */ countries_probed?: number | null; countries_ok?: number | null; } /** * One connector's stored risk index for a shop. * * Read-only snapshots — neither endpoint scores anything on demand, so * `computed_at` is the age of the answer and can be older than the request. */ interface ConnectorRisk { connector: string; /** * Coarse bucket the index falls in: `healthy` | `watch` | `at_risk` | * `critical` | `insufficient_data`. Open string on purpose — a new band * must reach dashboards without an SDK release, so render unknown values * through the same neutral path as `insufficient_data`, never as healthy. */ band: string; /** The index itself, when the snapshot carries one. */ index?: number | null; /** Direction against the previous snapshot, when there is one to compare. */ trend?: string | null; /** Which scoring model produced it. Bands are not comparable across versions. */ model_version: number; /** RFC 3339 UTC — when the snapshot was computed, not when it was read. */ computed_at: string; /** * Per-signal contributions behind the index. * * Any JSON value: the backend passes the stored blob through verbatim and * its shape is the scoring model's business, so it is versioned by * `model_version` rather than by this type — an object today, an array in * the backend's own fixture. Narrow it against the model you support; * do not assume it is keyed. */ components: unknown; } /** One shop's stored risk, per connector. */ interface ShopRisk { shop_id: string; /** Newest `computed_at` across `connectors`; absent when the shop has none. */ computed_at?: string | null; connectors: ConnectorRisk[]; } /** * The merchant-wide roll-up. * * A shop-scoped caller — a JWT with a profile-scoped role, or an API key * pinned to one shop — gets its own shop and no sibling's, from this endpoint * as much as from the per-shop one. */ interface MerchantRisk { merchant_id: string; /** Worst band across every shop; absent when nothing has been scored. */ worst_band?: string | null; shops: ShopRisk[]; } /** How far along a connector's integration is. */ type ConnectorIntegrationStatus = 'live' | 'sandbox' | 'beta' | 'alpha'; /** What kind of processor a connector is. */ type DelopayConnectorCategory = 'payment_gateway' | 'alternative_payment_method' | 'bank_acquirer' | 'payout_processor' | 'authentication_provider' | 'fraud_and_risk_management_provider' | 'tax_calculation_provider' | 'revenue_growth_management_platform' | 'vault_provider'; /** Whether one feature is available on a connector's payment method. */ type FeatureStatus = 'supported' | 'not_supported'; /** Card-only additions to a supported payment method. */ interface CardSpecificFeatures { three_ds: FeatureStatus; no_three_ds: FeatureStatus; /** Card network names, as the connector reports them. */ supported_card_networks: string[]; } /** One payment method type a connector supports, and what it supports on it. */ interface SupportedPaymentMethod extends Partial { payment_method: PaymentMethod; payment_method_type: PaymentMethodType; payment_method_type_display_name: string; mandates: FeatureStatus; refunds: FeatureStatus; supported_capture_methods: CaptureMethod[]; /** ISO 3166-1 alpha-3 codes. */ supported_countries?: string[] | null; supported_currencies?: Currency[] | null; } /** One connector's entry in the feature matrix. */ interface ConnectorFeatureMatrixEntry { name: string; display_name: string; description: string; base_url?: string | null; category: DelopayConnectorCategory; integration_status: ConnectorIntegrationStatus; supported_payment_methods?: SupportedPaymentMethod[] | null; supported_webhook_flows?: EventClass[] | null; /** * Whether the connector rejects an incoming webhook it cannot verify, * rather than processing it unverified. * * A statement about failure, not about setup. `true` means an event that * does not verify is dropped at the door; `false` means a failed * verification is not by itself a reason to discard the event. * * It does **not** say the merchant must supply a credential, and must not * be read that way: some connectors verify a signature against a value * stored on the connector account, others check the request's source IP and * fall back to the vendor's documented egress address — fail-closed either * way, with nothing to enter. Read it as "an unverified event will not be * acted on". Which field, if any, the setup flow must then collect is a * separate question the connector's own config answers. */ webhook_source_verification_mandatory: boolean; /** * How the connector reaches its sandbox, if it has one. The same value every * account of this connector reports on `ConnectorResponse.sandbox_mechanism`, * available here before any account exists. * * Absent from a router that predates the field. Treat absence as "not * reported" rather than as `none`. */ sandbox_mechanism?: ConnectorSandboxMechanism; /** * Whether a processor cost schedule for this connector may scope on a * `connector_method_code` — the processor's own identifier for one method, * on a rail where one payment method type multiplexes many (e-Payouts * routes every vendor through `local_bank_transfer`). * * A schedule's code is matched against the code the connector stamped on * the attempt, so on a connector that stamps none such a schedule could * never price a payment, and the router refuses to create one. Offer the * field only where this is `true`. * * Absent from a router that predates the field. Treat absence as `false`: * such a router accepts no method code at all. */ accepts_processor_cost_method_code?: boolean; } interface FeatureMatrixResponse { connector_count: number; connectors: ConnectorFeatureMatrixEntry[]; } /** * Rail a pane is rendered and confirmed on. * * `wallet` panes are collected in-page by the connector's own SDK; * `redirect` panes hand the buyer to a connector-hosted page; * `hosted_element` panes are mounted by the connector's own SDK against the * intent the render already minted and reconciled by that connector's return * leg — Airwallex's whole catalogue. */ type PaneRail = 'wallet' | 'redirect' | 'hosted_element'; /** * Where the checkout draws a wallet's **own** button in place of the pane's * tile — Stripe's Express Checkout Element on the `wallet` rail, Airwallex's * wallet button on the `hosted_element` rail — for the two brands that have * one (Apple Pay, Google Pay). * * The three contexts are {@link PaneVisibility}'s two names plus `everywhere`, * and the two fields **compose** in that order: `visibility` decides whether * the pane is offered in a render at all, and only then does this decide which * of the two surfaces it is offered as. There is no "never": wherever the * processor reports the wallet unavailable the checkout falls back to the tile * by itself, so this can never take the method off a checkout. * * Only meaningful where {@link PaneCapability.supports_native_element} is * `true`; the router omits {@link PaneView.native_element} everywhere else. */ type PaneNativeElement = 'everywhere' | 'embedded_only' | 'external_only'; /** * One method a connector can publish as a pane. * * Capability facts only. There is deliberately no `label`, `sublabel` or * `category` here — presentational copy for a pane lives in the dashboard's * `en.json` / `de.json`, keyed by `key`, and never in the router. */ interface PaneCapability { /** Stable catalogue key (`apple_pay`, `klarna`, …). The copy lookup key. */ key: string; rail: PaneRail; /** Routing key sent on confirm. Pass through verbatim; never derive it. */ payment_method: PaymentMethod; /** Routing key sent on confirm. Pass through verbatim; never derive it. */ payment_method_type: PaymentMethodType; /** Whether a configuration for this method must carry a billing country. */ requires_billing_country: boolean; /** * Whether this method can be drawn as the wallet's own button instead of a * DeloPay tile — i.e. whether {@link Pane.nativeElement} means anything * here. The router's answer, not the dashboard's: Apple Pay and Google Pay * on Stripe's wallet rail and on Airwallex's hosted-element rail today. * * Optional because a router predating the field omits it; read absent as * `false`, so the control is offered nowhere rather than on a method the * router would ignore. */ supports_native_element?: boolean; /** * The shipped presets this method may draw with, each with its styles in * picker order (the first is the default). Empty when the checkout has no * preset for the method. Absent on an older router. */ presets?: PanePresetCapability[]; } /** One preset a pane method may draw with. */ interface PanePresetCapability { key: string; styles: string[]; } /** What one connector can publish as panes. */ interface PanesConnectorCatalog { /** Connector name (`stripe`, `creem`, …). */ connector: string; /** * Rails this connector supports at all. A pane declaring a rail absent from * this list is refused when the connector account is saved, rather than * accepted and silently dropped. */ allowed_rails: PaneRail[]; methods: PaneCapability[]; } interface PanesCatalogResponse { connectors: PanesConnectorCatalog[]; } /** @deprecated Renamed to {@link PaneCapability}. Removed in 0.112.0. */ interface NativePaneCapability extends PaneCapability { } /** @deprecated Renamed to {@link PanesConnectorCatalog}. Removed in 0.112.0. */ interface NativePanesConnectorCatalog extends PanesConnectorCatalog { } /** @deprecated Renamed to {@link PanesCatalogResponse}. Removed in 0.112.0. */ interface NativePanesCatalogResponse extends PanesCatalogResponse { } /** @deprecated Renamed to {@link PaneRail}. Removed in 0.112.0. */ type NativePaneRail = PaneRail; /** * Query parameters for a merchant's own audit log. * * Note what is **absent**: `merchant_id`. The scope comes from the * authenticated token, so there is no field to set and no way to point this at * another merchant. */ interface MerchantAuditLogListParams { /** One of this merchant's own users. */ user_id?: string | null; /** One of this merchant's shops. */ profile_id?: string | null; /** One sign-in — the changes made in a single session. */ session_id?: string | null; action?: string | null; entity_type?: string | null; /** Inclusive lower bound, ISO 8601 (`2026-03-12T00:00:00Z`). */ start_date?: string | null; /** Inclusive upper bound, ISO 8601. */ end_date?: string | null; offset?: number | null; limit?: number | null; } /** * The parties a merchant can see in their own log. * * `delopay` covers support acting on the account and the platform itself, * deliberately as one value: which of DeloPay's mechanisms it was is not the * merchant's question, and splitting it would leak internal structure. */ type MerchantAuditActorKind = 'team_member' | 'api_key' | 'delopay' | 'system'; /** Who acted, as a merchant is allowed to see it. */ interface MerchantAuditActorInfo { /** * Absent for a DeloPay actor: the merchant learns that support acted, not * which employee it was. */ id?: string | null; kind: MerchantAuditActorKind; /** The team member's email, or the label for a non-human actor. */ name?: string | null; } /** * Which relationship an impersonated change was made under. Absent means * **unknown** — a row written before this was recorded — which is not the same * as `self_acted`. */ type MerchantAuditImpersonationKind = 'self_acted' | 'admin_for_merchant' | 'merchant_for_team_member'; /** * The sign-in a change was made in. * * Only ever present for the merchant's **own** people. A DeloPay support * session carries none: the employee's device, address and city are not the * merchant's to read back. */ interface MerchantAuditSessionInfo { id: string; ip_address?: string | null; user_agent?: string | null; country_code?: string | null; city?: string | null; /** Approximate — derived from the IP, so a neighbourhood, not an address. */ latitude?: number | null; longitude?: number | null; auth_method?: string | null; started_at?: string | null; last_seen_at?: string | null; revoked_at?: string | null; } interface MerchantAuditLogEntry { id: string; actor: MerchantAuditActorInfo; /** * The person really acting, when it was one of the merchant's own — an admin * signed in as a team member. Absent when nobody was impersonating, and also * when it was DeloPay: `impersonation_kind` says an admin acted, and `actor` * already says it was DeloPay. */ real_actor?: MerchantAuditActorInfo | null; impersonation_kind?: MerchantAuditImpersonationKind | null; session?: MerchantAuditSessionInfo | null; profile_id?: string | null; action: string; entity_type: string; entity_id?: string | null; /** What the entity was called. Absent when it has since been deleted. */ entity_name?: string | null; /** * Which fields the change touched. Names only, never values — the raw * payload can carry connector credentials, so it is never forwarded. */ changed_fields: string[]; created_at: string; } interface MerchantAuditLogListResponse { entries: MerchantAuditLogEntry[]; total_count: number; offset: number; limit: number; } /** * Output format of an export. * * Selects an `Accept` header — `text/csv`, `application/json` or * `application/pdf` — rather than a request field, because an export reuses its * list's filter model as literally the same type and several of those are * `deny_unknown_fields`. * * `csv` and `pdf` come back as a `Blob`; `json` comes back as an * {@link ExportEnvelope}. The API's own default is `csv`, but the SDK always * asks explicitly and defaults to `json`, because a method that returns parsed * records is the useful default in a typed client. * * `pdf` is bounded far tighter than the other two — 500 rows against 25,000 — * because a PDF is a document someone reads, not a way to move a spreadsheet. * Over either bound the request is **refused** with `DE_07`, never silently * truncated. * * `csv` and `json` are **streamed**: the body has no `Content-Length`, and the * `X-Delopay-Record-Count` header announces, before the first byte, how many * records it will hold. `pdf` is laid out from every row at once, so it stays * a buffered body with a `Content-Length` and no record count. */ type ExportFormat = 'csv' | 'json' | 'pdf'; /** * Where an export download is. See {@link ExportProgress}. * * - `preparing` — the request is out; the server is counting the match set and * has not answered yet. * - `receiving` — the headers are in and the body is arriving. * - `done` — the body is complete and has been checked against the record * count the server announced. Only ever reported once, and last. */ type ExportPhase = 'preparing' | 'receiving' | 'done'; /** * A snapshot of an export download, reported through * {@link ExportTransferOptions.onProgress}. * * `receivedRecords` of `expectedRecords` is the progress to show for `csv` and * `json`, whose bodies are streamed with no byte total. `receivedBytes` of * `totalBytes` is the one for `pdf`, which has a byte total and no record count. * * A retried request starts its body again from the beginning, so the counts * can go back to zero within one download. */ interface ExportProgress { phase: ExportPhase; /** Body bytes received so far. */ receivedBytes: number; /** `Content-Length` — a `pdf`. `null` for a streamed body. */ totalBytes: number | null; /** * Records complete in the body so far, counted as it arrives. Always `0` * for `pdf`, which is not counted. */ receivedRecords: number; /** * The record count the server announced in `X-Delopay-Record-Count` before * the first byte. `null` when it announced none — a `pdf`, or a server that * predates the header — and then the finished file is not checked against it. */ expectedRecords: number | null; } /** * How an export download runs — the part of an export's options that is not * the format. * * Every export is held to its own announced size: once the body has arrived, * a record count that differs from `X-Delopay-Record-Count` — or, for `json`, * an envelope whose own `record_count` / `total_count` differs from the rows * it carries — rejects with a `DelopayError` whose `code` is * `'EXPORT_INCOMPLETE'` (`status` `0`, `data` `{ expected_records, * received_records }`) instead of resolving to a file that is missing rows. */ interface ExportTransferOptions { /** * Cancel the download. Rejects with the client's usual `ABORTED` error, at * any point — before the headers or part-way through the body. */ signal?: AbortSignal; /** * Milliseconds without progress after which the download fails with * `TIMEOUT`. An **idle** timeout: it restarts when the headers arrive and on * every chunk of the body, so a large export that keeps arriving is never cut * off, and one that stalls still is. Defaults to the client's timeout. */ timeout?: number; /** * Called with the download's progress: once with `phase: 'preparing'` * synchronously before the request is sent, then as the headers and each * chunk of the body arrive, and last with `phase: 'done'` — only after the * file has passed its record-count check. A listener that throws rejects the * export with that error. */ onProgress?: (progress: ExportProgress) => void; /** * The words a PDF export prints, in the reader's language. Sent * only with `format: 'pdf'`, in the `X-Delopay-Export-Presentation` header. * Without it the PDF lists the server's own filter echo, in English. * * The words only describe the file: which rows it holds is decided by the * export's filters, never by anything here. */ presentation?: ExportPresentation; } /** * The words of an exported PDF report: its title, the filters * exactly as the caller's export dialog lists them, column and value labels, * the report's own words, and how numbers, dates and times read. Strings are * printed as given. A report is an overview — records, period, mode, a status * breakdown, the merchants — followed by the rows as a table grouped by * merchant. * * Bounded by the server: 200 characters for a heading, label, word or * template, 1,000 for a filter value, 60 filters, 80 column labels, 80 words, * 20 value columns of 200 codes each — and 6 KiB once encoded, which the SDK * checks before sending (`EXPORT_PRESENTATION_TOO_LARGE`). * * `words`, `values`, `date_format`, `time_format`, `month_names` and * `time_zone` are read by routers that support report presentation, which also * ignore any field they do not know. An earlier router refuses a presentation * carrying them (`IR_06`), so send them only to a router that reads them. */ interface ExportPresentation { /** The document's heading, e.g. `Transaktionen`. Must not be empty. */ title: string; /** A line under the title. */ subtitle?: string; /** The heading over the filter list, e.g. `Applied filters`. */ filters_heading: string; /** The filters, in the order and words the export dialog shows them. */ filters: ExportPresentationFilter[]; /** Printed in place of the list when there are no filters. */ no_filters: string; /** Printed in place of the table when no row matched. */ no_rows: string; /** The heading over the table; `{count}` becomes the number of rows. */ row_count: string; /** The page footer after the brand; `{page}` and `{pages}` are filled in. */ page: string; /** * Labels for the export's columns, keyed by column name (`payment_id`). A * column with none prints its name, readably spaced. */ columns?: Record; /** Default `.`; `,` for a German reader. */ decimal_separator?: string; /** Default `,`; `.` for a German reader. */ thousands_separator?: string; /** How a `true` cell reads. Default `yes`. */ yes?: string; /** How a `false` cell reads. Default `no`. */ no?: string; /** * The report's own words, keyed as {@link ExportReportWord} lists them. A * word left out prints in English. */ words?: Partial>; /** * Labels for coded values, per report column: * `{ status: { succeeded: 'Erfolgreich' } }`. A code with none prints * spaced out and capitalised (`requires_payment_method` → `Requires payment * method`). */ values?: Record>; /** * How a date reads: literal text and `{d}`/`{dd}` day, `{m}`/`{mm}` month, * `{mmm}` month name, `{yyyy}`/`{yy}` year — e.g. `{dd}.{mm}.{yyyy}`. * Default `{d} {mmm} {yyyy}`. */ date_format?: string; /** * How a time of day reads: `{H}`/`{HH}` hour of 24, `{h}`/`{hh}` hour of 12, * `{MM}` minute, `{a}` the `am`/`pm` word — e.g. `{HH}:{MM}`. Default * `{HH}:{MM}`. */ time_format?: string; /** Twelve month names, January first, for `{mmm}`. English when absent. */ month_names?: string[]; /** * The IANA zone times print in, e.g. `Europe/Zurich`. UTC when absent or * unknown to the server, and the report says which it used. */ time_zone?: string; } /** * The words a report prints that are not a title, a label, a filter or a * value. `money` is a template: `{amount}` is the number in the reader's * separators, `{currency}` the ISO code (default `{amount} {currency}`). */ type ExportReportWord = 'overview' | 'details' | 'records' | 'period' | 'generated' | 'time_zone' | 'mode' | 'live' | 'test' | 'live_and_test' | 'test_banner' | 'by_status' | 'status' | 'count' | 'amount' | 'by_merchant' | 'merchant' | 'merchants' | 'page_column' | 'subtotal' | 'continued' | 'no_merchant' | 'total' | 'fx_incomplete' | 'passthrough_incomplete' | 'am' | 'pm' | 'money'; /** One filter as an export dialog lists it. */ interface ExportPresentationFilter { label: string; value: string; } /** * Options for the JSON path: `format` omitted, or explicitly `'json'`. * * The three option types below are separate rather than one generic with an * optional field, and that is load-bearing. A generic `{ format?: F }` is * satisfied by `{}` for **every** `F`, so `ExportOptions<'csv' | 'pdf'>` was * satisfied by an empty object — which selected the `Blob` overload while the * client, seeing no format, asked for and returned JSON. The promise and the * value disagreed, and nothing said so. */ interface JsonExportOptions extends ExportTransferOptions { format?: 'json'; } /** * Options for a binary path. `format` is **required**: `csv` and `pdf` are the * formats you have to ask for, and an absent one can only mean JSON. */ interface BinaryExportOptions extends ExportTransferOptions { format: 'csv' | 'pdf'; } /** * Options for an export that offers PDF but not CSV — the disputes * workspace. As with {@link BinaryExportOptions}, `format` is required. */ interface PdfExportOptions extends ExportTransferOptions { format: 'pdf'; } /** * Options for the disputes workspace export when its format is chosen at * runtime — JSON or PDF, never CSV. Resolves to `DisputeWorkspaceExport | Blob` * for the caller to narrow, as {@link DynamicExportOptions} does elsewhere. */ interface DynamicWorkspaceExportOptions extends ExportTransferOptions { format: 'json' | 'pdf'; } /** * Options whose format is chosen at runtime. * * A caller holding an `ExportFormat` variable cannot know which of the two * paths it will take, so this resolves to `ExportEnvelope | Blob` and makes * them narrow it. Previously such a caller matched no overload at all. */ interface DynamicExportOptions extends ExportTransferOptions { format: ExportFormat; } /** Any of the three shapes an export method accepts. */ type ExportOptions = JsonExportOptions | BinaryExportOptions | DynamicExportOptions; /** * The `json` shape of every export. * * `filters` echoes what the file was produced under, so the limitation travels * inside the data rather than living only in the call site that made it. */ interface ExportEnvelope { records: T[]; record_count: number; filters: Record; } /** * One row of the transactions export — a payment `payments.listByFilter` would * list for the same body. */ interface TransactionExportRecord { payment_id: string; merchant_id: string; /** The shop the payment belongs to. */ shop_id: string | null; status: string; amount: number; currency: string | null; /** ISO 8601, UTC. */ created_at: string; /** * Where the active attempt's money has got to, as the rail reports it — the * word `connector_settlement_status` carries on a payment. Present only on * the merchant-level export: a shop-scoped viewer is not shown the * settlement state, so `transactionsForProfile` has no such column at all. */ connector_settlement_status?: string | null; /** * The payment's environment. `null` when it never recorded one — not the * same as `false`, although the list's `test_mode` filter counts it as live. * An export is no longer cut to one environment, so a file that spans both * says which each row is. */ test_mode: boolean | null; } /** One row of the refunds export. */ interface RefundExportRecord { refund_id: string; payment_id: string; profile_id: string | null; status: string; amount: number; currency: string; reason: string | null; connector: string; error_code: string | null; error_message: string | null; created_at: string | null; updated_at: string | null; } /** One row of the disputes export. */ interface DisputeExportRecord { dispute_id: string; payment_id: string; attempt_id: string; profile_id: string | null; dispute_stage: string; dispute_status: string; amount: string; currency: string; connector: string; connector_status: string; connector_dispute_id: string; connector_reason: string | null; connector_reason_code: string | null; challenge_required_by: string | null; created_at: string; } /** One row of the payouts export. */ interface PayoutExportRecord { payout_id: string; profile_id: string; customer_id: string | null; status: string; amount: number; currency: string; payout_type: string | null; connector: string | null; error_code: string | null; error_message: string | null; merchant_order_reference_id: string | null; created_at: string; } /** One row of the subscriptions export. */ interface SubscriptionExportRecord { id: string; merchant_reference_id: string | null; profile_id: string; customer_id: string; status: string; plan_id: string | null; item_price_id: string | null; coupon_code: string | null; test_mode: boolean | null; } /** * One row of a merchant's audit-log export. * * The columns answer the question an audit asks rather than mirroring the * dashboard table: who acted, on whose behalf if they were impersonating, what * they did, to what, when, and from where. `real_actor_*`, `session_id` and * `ip_address` are not visible columns anywhere and are the reason the log * exists. `details` is deliberately absent — an unbounded per-action blob is * unreadable in a spreadsheet cell. */ interface MerchantAuditLogExportRecord { created_at: string; actor_kind: string; actor_id: string | null; actor_name: string | null; real_actor_id: string | null; real_actor_name: string | null; impersonation_kind: string | null; session_id: string | null; ip_address: string | null; user_agent: string | null; profile_id: string | null; action: string; entity_type: string; entity_id: string | null; entity_name: string | null; /** Space-separated, so one cell holds the whole set. */ changed_fields: string; } /** PayPal's provider case, including transactions not originated by DeloPay. */ interface PaypalDisputeCase { case_id: string; connector_dispute_id: string; merchant_connector_id: string; profile_id: string | null; payment_id: string | null; attempt_id: string | null; invoice_id: string | null; seller_id: string | null; transaction_ids: string[]; test_mode: boolean; status: string; stage: string; reason: string | null; reason_code: string | null; /** PayPal major-unit decimal string. */ amount: string; currency: string; seller_response_due_date: string | null; buyer_response_due_date: string | null; created_at: string | null; updated_at: string | null; outcome: string | null; available_actions: PaypalDisputeAction[]; webhook_delivery_count?: number; provider_event_id?: string | null; details?: Record | null; } type PaypalDisputeAction = 'send_message' | 'make_offer' | 'accept_claim' | 'provide_evidence' | 'appeal' | 'escalate' | 'acknowledge_return_item' | 'provide_supporting_info' | 'update_communication'; interface PaypalDisputeDocument { name: string; content_type: string; data_base64: string; } interface PaypalDisputeDownloadRequest { /** Must be a document URL returned in this case's current PayPal details. */ document_url: string; } interface PaypalDisputeActionRequest { action: PaypalDisputeAction; /** Reuse for retries of the same action. Never change it automatically on an unknown result. */ request_id: string; data: Record; documents?: PaypalDisputeDocument[]; } interface PaypalDisputeActionResponse { request_id: string; /** submitted, rejected or unknown; unknown must be reconciled before another action. */ outcome: string; case: PaypalDisputeCase; } interface PaypalDisputeListRequest { merchant_connector_id?: string; profile_id?: string; project_id?: string; test_mode?: boolean; status?: string; stage?: string; search?: string; limit?: number; offset?: number; } interface PaypalDisputeImportRequest { merchant_connector_id: string; test_mode: boolean; start_time?: string; next_page_token?: string; } interface PaypalDisputeImportResponse { cases: PaypalDisputeCase[]; next_page_token: string | null; } interface PaypalDisputeActionRecord { request_id: string; action: string; outcome: string; actor: string | null; created_at: string; request: Record | null; } interface NativeSettlementPreviewRequest { profile_id: string; currency: Currency; from: string; to: string; test_mode: boolean; hosting_rate_pct: string; reserve_rate_pct?: string | null; include_open_disputes?: boolean | null; fee_basis?: NativeSettlementFeeBasis; } type NativeSettlementFeeBasis = 'reported_only' | 'reported_or_contract'; type NativeSettlementQuality = 'complete' | 'provisional'; interface NativeSettlementFeeEvidence { provider_event_id: string; basis: string; amount_minor: string; currency: string; exponent: number; category: string; native_amount_minor: string | null; provider_fx_rate: string | null; fx_provenance: string | null; processor_amount_minor: string | null; application_amount_minor: string | null; classification_complete: boolean; overlap_uncertain: boolean; components: NativeSettlementFeeComponent[]; event_at: string; } interface NativeSettlementFeeComponent { kind: string; description: string | null; amount_minor: string; currency: string; exponent: number; included_in_total: boolean; } interface NativeSettlementSource { source_kind: string; source_id: string; payment_id: string; attempt_id: string; connector: string; connector_label: string | null; merchant_connector_id: string | null; provider_source_id: string | null; payment_method: string | null; status: string; /** False while further capture changes can still affect principal or fees. */ capture_final: boolean; amount_minor: string; applied_principal_minor: string; known_fee_minor: string | null; estimated_fee_minor: string | null; applied_fee_minor: string | null; cost_source: string; fee_complete: boolean; unavailable_reason: string | null; created_at: string; modified_at: string; fee_checked_at: string | null; fee_evidence: NativeSettlementFeeEvidence[]; } interface NativeSettlementFeeTerm { known_amount_minor: string | null; estimated_amount_minor: string | null; applied_amount_minor: string | null; observed_source_count: number; estimated_source_count: number; incomplete_source_count: number; } interface NativeSettlementTotals { gross_minor: string; hosting_fee_minor: string; payment_fees: NativeSettlementFeeTerm; refund_minor: string; refund_fees: NativeSettlementFeeTerm; /** * Money returned to the buyer by a post-charge reversal that wrote no refund: * an attempt moved to `auto_refunded` or `voided_post_charge`. Reported * separately from `refund_minor` because a shop owner did not issue it, and * subtracted inside `owed_minor` rather than reduced from `gross_minor` — the * hosting fee stays with the host, exactly as it does on a refund. */ reversal_minor: string; settled_dispute_minor: string; open_dispute_minor: string; applied_dispute_minor: string; dispute_fees: NativeSettlementFeeTerm; owed_minor: string; reserve_minor: string; payable_minor: string; paid_minor: string; outstanding_minor: string; } interface NativeSettlementPayout { id: string; profile_id: string; currency: string; test_mode: boolean; amount_minor: string; value_date: string; method: string | null; reference: string | null; note: string | null; reversal_of: string | null; created_at: string; } interface NativeSettlementPreviewResponse { calculation_version: number; render_version: number; calculated_at: string; terms: NativeSettlementPreviewRequest; profile_name: string | null; currency_exponent: number; quality: NativeSettlementQuality; totals: NativeSettlementTotals; sources: NativeSettlementSource[]; payouts_in_window: NativeSettlementPayout[]; payouts_outside_window: NativeSettlementPayout[]; /** Host costs, omitted when empty and from shareable exports. */ account_fees?: NativeSettlementAccountFees[]; } interface NativeSettlementAccountFees { account_namespace: string; account_reference: string | null; connector: string; merchant_connector_id: string | null; complete: boolean; /** Previously reported zero can remain known after a failed refresh. Omission does not establish zero. */ reported_zero?: boolean; unavailable_reason: string | null; checked_at: string | null; evidence: NativeSettlementFeeEvidence[]; } interface NativeSettlementReportCreateRequest { calculation: NativeSettlementPreviewRequest; idempotency_key: string; allow_provisional?: boolean; } interface NativeSettlementReport { id: string; created_at: string; snapshot: NativeSettlementPreviewResponse; } /** Shareable export omits the host's account-level costs. */ interface NativeSettlementReportExport { id: string; created_at: string; snapshot: Omit; } interface NativeSettlementListRequest { profile_id: string; currency: Currency; test_mode: boolean; limit?: number | null; offset?: number | null; } interface NativeSettlementReportListResponse { data: NativeSettlementReportSummary[]; has_more: boolean; } interface NativeSettlementReportSummary { id: string; created_at: string; terms: NativeSettlementPreviewRequest; profile_name: string | null; currency_exponent: number; calculation_version: number; render_version: number; quality: NativeSettlementQuality; totals: NativeSettlementTotals; source_count: number; } interface NativeSettlementPayoutCreateRequest { profile_id: string; currency: Currency; test_mode: boolean; amount_minor: string; value_date: string; method?: string | null; reference?: string | null; note?: string | null; reversal_of?: string | null; idempotency_key: string; } interface NativeSettlementPayoutListResponse { data: NativeSettlementPayout[]; has_more: boolean; } interface NativeSettlementFeeRefreshRequest { calculation: NativeSettlementPreviewRequest; limit?: number | null; offset?: number | null; } interface NativeSettlementFeeRefreshResponse { source_count: number; complete_count: number; has_more: boolean; next_offset: number | null; } interface NativeSettlementPdfRequest { include_transactions?: boolean | null; } /** Create and manage API keys for a merchant account. */ declare class ApiKeys { private readonly request; constructor(request: RequestFn); /** * Create a new API key for a merchant. * * @param merchantId - The merchant account ID. * @param params - Key creation parameters (name, expiry, etc.). * @returns The newly created key including the plaintext secret (shown once only). * * @example * ```typescript * const { key_value } = await delopay.apiKeys.create('merch_123', { name: 'Production key' }); * ``` */ create(merchantId: string, params: ApiKeyCreateRequest): Promise; /** * Retrieve metadata about an API key (does not return the plaintext secret). * * @param merchantId - The merchant account ID. * @param keyId - The API key ID. * @returns The API key metadata. */ retrieve(merchantId: string, keyId: string): Promise; /** * Update an API key's name or expiry. * * @param merchantId - The merchant account ID. * @param keyId - The API key ID to update. * @param params - Fields to update. * @returns The updated API key metadata. */ update(merchantId: string, keyId: string, params: ApiKeyUpdateRequest): Promise; /** * Revoke an API key, immediately invalidating it. * * @param merchantId - The merchant account ID. * @param keyId - The API key ID to revoke. * @returns Revocation confirmation. */ revoke(merchantId: string, keyId: string): Promise; /** * List all API keys for a merchant. * * @param merchantId - The merchant account ID. * @returns Array of API key metadata objects. */ list(merchantId: string): Promise; /** * Create a new API key pinned to the caller's shop (business profile). * `POST /account/{merchantId}/profile/api-keys` * * @param merchantId - The merchant account ID. * @param params - Key creation parameters (name, expiry, etc.). * @returns The newly created key including the plaintext secret (shown once * only) and the `profile_id` it is pinned to. * * @example * ```typescript * const { api_key } = await delopay.apiKeys.createByProfile('merch_123', { * name: 'Shop key', * expiration: 'never', * }); * ``` */ createByProfile(merchantId: string, params: ApiKeyCreateRequest): Promise; /** * List API keys pinned to the caller's shop (business profile) only. * `GET /account/{merchantId}/profile/api-keys` * * @param merchantId - The merchant account ID. * @param params - Optional pagination constraints (`limit`, `skip`). * @returns Array of API key metadata objects belonging to the caller's shop. */ listByProfile(merchantId: string, params?: ApiKeyListConstraints): Promise; /** * Retrieve metadata about a shop-pinned API key (does not return the * plaintext secret). `GET /account/{merchantId}/profile/api-keys/{keyId}` * * @param merchantId - The merchant account ID. * @param keyId - The API key ID. * @returns The API key metadata. */ retrieveByProfile(merchantId: string, keyId: string): Promise; /** * Update a shop-pinned API key's name, description, or expiry. * `POST /account/{merchantId}/profile/api-keys/{keyId}` * * @param merchantId - The merchant account ID. * @param keyId - The API key ID to update. * @param params - Fields to update. * @returns The updated API key metadata. */ updateByProfile(merchantId: string, keyId: string, params: ApiKeyUpdateRequest): Promise; /** * Revoke a shop-pinned API key, immediately invalidating it. * `DELETE /account/{merchantId}/profile/api-keys/{keyId}` * * @param merchantId - The merchant account ID. * @param keyId - The API key ID to revoke. * @returns Revocation confirmation. */ revokeByProfile(merchantId: string, keyId: string): Promise; } declare class Authentication { private readonly request; constructor(request: RequestFn); create(params: AuthenticationCreateRequest): Promise; checkEligibility(authId: string): Promise; authenticate(authId: string, params: Record): Promise; /** Sync authentication status. `POST /authentication/{merchantId}/{authId}/sync` */ sync(merchantId: string, authId: string, params?: Record): Promise; /** Redirect after authentication. `POST /authentication/{merchantId}/{authId}/redirect` */ redirect(merchantId: string, authId: string, params: Record): Promise>; /** Enable authn methods token. `POST /authentication/{authId}/enabled-authn-methods-token` */ enabledAuthnMethodsToken(authId: string, params: Record): Promise>; /** Submit eligibility check. `POST /authentication/{authId}/eligibility-check` */ eligibilityCheck(authId: string, params: Record): Promise>; } /** Manage per-shop prepaid balance allocations transferred from the host merchant treasury. */ declare class BillingAllocations { private readonly request; constructor(request: RequestFn); /** * Transfer funds from the host merchant treasury into a shop's allocation. * * @param merchantId - The host merchant account ID. * @param params - Transfer details (amount, target profile/shop ID). * @returns The allocation transfer result. */ transferIn(merchantId: string, params: AllocationTransferRequest): Promise; /** * Transfer funds from a shop's allocation back to the host merchant treasury. * * @param merchantId - The host merchant account ID. * @param params - Transfer details (amount, source profile/shop ID). * @returns The allocation transfer result. */ transferOut(merchantId: string, params: AllocationTransferRequest): Promise; /** * List all shop balance allocations for a merchant. * * @param merchantId - The merchant account ID. * @returns List of shop allocations. */ list(merchantId: string): Promise; /** * Get the balance allocation for a specific shop. * * @param merchantId - The merchant account ID. * @param profileId - The shop (business profile) ID. * @returns The shop's balance allocation. */ get(merchantId: string, profileId: string): Promise; } /** * Manage prepaid billing balances — top-ups, card setup, auto-recharge, and the balance ledger. * * Delopay deducts a platform fee from the merchant's prepaid balance on every successful payment. * Use these endpoints to fund and monitor that balance. */ declare class Billing { private readonly request; /** Per-shop balance allocation management for host merchants. */ readonly allocations: BillingAllocations; constructor(request: RequestFn); /** * Retrieve a merchant's billing profile (balance, status, auto-recharge config). * * @param merchantId - The merchant account ID. * @returns The billing profile. * * @example * ```typescript * const profile = await delopay.billing.getProfile('merch_123'); * console.log(profile.balance, profile.status); * ``` */ getProfile(merchantId: string): Promise; /** * Start a Stripe SetupIntent flow to collect a payment card for auto-recharge. * * @param merchantId - The merchant account ID. * @param params - Optional setup parameters. * @returns The Stripe client secret needed to render the card element. */ setup(merchantId: string, params?: BillingSetupRequest): Promise; /** * Confirm card setup after the Stripe SetupIntent completes on the frontend. * * @param merchantId - The merchant account ID. * @param params - The Stripe SetupIntent ID to confirm. * @returns The updated billing profile. */ completeSetup(merchantId: string, params: BillingCompleteSetupRequest): Promise; /** * Manually top up a merchant's prepaid balance by charging their saved card. * * @param merchantId - The merchant account ID. * @param params - Top-up amount and currency. * @returns The top-up result. */ topup(merchantId: string, params: TopupRequest): Promise; /** * List the balance ledger (credits and debits) for a merchant. * * @param merchantId - The merchant account ID. * @param params - Optional pagination parameters. * @returns The ledger entries. */ listLedger(merchantId: string, params?: LedgerListParams): Promise; /** * List payment attempts that were blocked by the billing suspension gate * (account suspended, setup incomplete, or shop allocation suspended). * * These attempts never created a payment, so they do not appear in the * payments list — this is the only way to retrieve them. * * @param merchantId - The merchant account ID. * @param params - Optional filters (profile, reason, date range) and pagination. * @returns The blocked-attempt entries with a total count. */ listBlockedAttempts(merchantId: string, params?: BlockedAttemptListParams): Promise; /** * Update auto-recharge configuration (threshold, top-up amount, enabled flag). * * @param merchantId - The merchant account ID. * @param params - Auto-recharge settings to update. * @returns The updated billing profile. */ updateAutoRecharge(merchantId: string, params: AutoRechargeUpdateRequest): Promise; } interface BlocklistListParams { data_kind?: BlocklistDataKind | null; limit?: number | null; offset?: number | null; } interface BlocklistToggleParams { status: boolean; } declare class Blocklist { private readonly request; constructor(request: RequestFn); add(params: BlocklistAddRequest): Promise; remove(params: BlocklistAddRequest): Promise; list(params?: BlocklistListParams): Promise; toggle(params: BlocklistToggleParams): Promise>; } declare class Connectors { private readonly request; constructor(request: RequestFn); create(accountId: string, params: ConnectorCreateRequest): Promise; /** * One connector account. * * The credential-bearing fields come back `null` here, whatever is stored: * `connector_webhook_details`, `connector_wallets_details`, * `pm_auth_config` and `additional_merchant_data`. They are dropped rather * than masked, because an editor that prefills from this response and * PATCHes the field back would otherwise save a mask over a live signing * secret. Send those fields only when the operator has typed a new value, * and omit them entirely otherwise — an omitted field leaves the stored one * alone. * * This is the retrieve path alone. `create` and `update` echo back what the * caller sent, and `clone` returns the *copied* secrets — see that method. * * Because the value is gone, `has_live_webhook_secret` and * `has_sandbox_webhook_secret` are what tell a stored webhook signing secret * from an unconfigured one. Render those; never infer configuration from the * `null` block. * * `GET /account/{accountId}/connectors/{connectorId}` */ retrieve(accountId: string, connectorId: string): Promise; /** * The merchant's connector accounts. * * Never wider than the caller: an API key pinned to one shop lists that * shop's connectors only, not every sibling shop's. * * `GET /account/{accountId}/connectors` */ list(accountId: string): Promise; /** * The profile-scoped connector list. The merchant-wide `list()` is * merchant-gated and 403s for a profile-entity (shop user) JWT; this * variant is scoped server-side to the caller's own profile. * * `GET /account/{accountId}/profile/connectors` */ listByProfile(accountId: string): Promise; /** * Every connector's pane capabilities, as the router itself knows them — * which rails a connector supports, and which methods it can publish as a * pane with the confirm routing keys each one carries. * * Capability facts only: no label, sublabel or category comes back. A * designer renders a method's copy from its own locale files, keyed by * `key`, and takes `payment_method` / `payment_method_type` from here * verbatim rather than deriving them client-side. * * This is what the pane helpers in `src/panes.ts` should be driven from — * `paneMethodInfo`, `defaultPane` and `validatePanes` all take a * {@link PanesConnectorCatalog} out of this response, so a connector the * router starts publishing panes for needs no SDK release. * * `GET /account/{accountId}/connectors/native-panes/catalog` * * The path keeps the `native-panes` spelling deliberately. The identifiers * were renamed off "native pane"; the route was not, and pointing at the * backend's new name before it is deployed everywhere would both 404 against * un-upgraded replicas mid-rollout and fail `scripts/parity-check.mjs`, * which blocks publishing. */ getPanesCatalog(accountId: string): Promise; /** @deprecated Renamed to {@link getPanesCatalog}. Removed in 0.112.0. */ getNativePanesCatalog(accountId: string): Promise; /** * Sweep the merchant's own e-Payouts module and return the rails it * actually has enabled. Server-side this makes many upstream calls, so it * can take several seconds — show progress. * * `POST /account/{accountId}/connectors/{connectorId}/epayouts/catalog/sync` */ syncEpayoutsCatalog(accountId: string, connectorId: string): Promise; /** * Update a connector account. * * `connector_webhook_details` **merges** into the stored block key by key: * an absent key keeps its stored secret, an explicit value is written, and * an explicit empty string clears that secret. Send only the * keys the operator typed — padding the other environment's keys with `''` * clears a live signing secret and inbound webhooks stop verifying. * * The other credential-bearing fields — `connector_wallets_details`, * `pm_auth_config`, `additional_merchant_data` — are still whole-value * replacements: omit them unless you are writing a complete new value. * * `POST /account/{accountId}/connectors/{connectorId}` */ update(accountId: string, connectorId: string, params: ConnectorUpdateRequest): Promise; /** * Remove a connector account. * * A shop-scoped role may remove a connector of its own shop — the shop is * re-checked server-side — so creating processors and removing them are the * same rung of access rather than two. * * `DELETE /account/{accountId}/connectors/{connectorId}` */ delete(accountId: string, connectorId: string): Promise; /** * Clone a connector into another shop (business profile) of the same * merchant. `POST /account/{accountId}/connectors/{connectorId}/clone` * * Credentials are copied server-side, re-encrypted under the same merchant * key, so the caller never has to *supply* them — `retrieve` returns `null` * for the credential fields, which is what makes a client-side copy * impossible in the first place. * * The response, however, is the unredacted connector: `connector_account_details` * is masked, but `connector_webhook_details`, `connector_wallets_details`, * `pm_auth_config` and `additional_merchant_data` come back with the copied * secrets in them — values this caller never sent. Do not log or echo the * response; read `merchant_connector_id` and discard the rest. */ clone(accountId: string, connectorId: string, params: ConnectorCloneRequest): Promise; /** * Run the configuration checks for a vault (VGS) connector account: * credential validity, write-only Collect scope, reachability, environment * coherence, route coverage. Read-only but not cheap — it decrypts the * vault's management credential and talks to VGS. * * `POST /account/{accountId}/connectors/{connectorId}/vault/verify` */ verifyVault(accountId: string, connectorId: string, params: VaultVerifyRequest): Promise; /** * Compute the route document the vault SHOULD have and diff it against * what exists, without writing anything. The returned fingerprints must be * echoed byte for byte on {@link Connectors.applyVaultRoutes}. * * A router without these endpoints answers 404 — render that as "this * build cannot configure routes", never as "there is nothing to change". * * `POST /account/{accountId}/connectors/{connectorId}/vault/routes/preview` */ previewVaultRoutes(accountId: string, connectorId: string, params: VaultRoutesPreviewRequest): Promise; /** * Write the routes the merchant just previewed. Both fingerprints come * from the preview and are opaque: `expected_current_fingerprint` says the * vault has not moved (`null` = "the preview found no routes" and is sent * as `null`, never omitted), `expected_desired_fingerprint` says the * document is still the one on screen. A 409 (`DE_04`) means the vault * changed since the preview — nothing was written; preview again. * * `POST /account/{accountId}/connectors/{connectorId}/vault/routes/apply` */ applyVaultRoutes(accountId: string, connectorId: string, params: VaultRoutesApplyRequest): Promise; /** * What the processor says about this connector account: whether it will * accept charges, which capabilities are live, and anything it is still * waiting on. * * This is **not** {@link Connectors.verify}. That one asks whether the stored * credentials work, by running a test-card authorization; a processor can * revoke an account's ability to take payments while the credentials stay * perfectly valid, and then a credential check reports healthy while every * checkout silently stalls. This asks the account's own status instead, and * does not create a payment. By default it reads a scheduler snapshot; * `refresh: true` asks for a new probe and `stale` identifies an overdue result. * `test_mode` selects the requested environment; `credential_source` identifies * a sandbox, live or shared credential set. Not every provider reports chargeability. * * Connectors with no probe answer `state: 'unknown'` with * `unknown_reason: 'connector_not_supported'`, rather than an error — every * `'unknown'` carries a reason code you render your own words for. A router * without this endpoint answers 404 — render that as "this build cannot * report health", never as "healthy". * * `GET /account/{accountId}/connectors/{connectorId}/health` */ health(accountId: string, connectorId: string, options?: { test_mode?: boolean; refresh?: boolean; }): Promise; /** Verify connector credentials. `POST /account/connectors/verify` */ verify(params: Record): Promise>; /** * Register a webhook for a connector. * `POST /account/{merchantId}/connectors/webhooks/{connectorId}` * * @param params - Optional event scope. Defaults to `{ event_type: 'all_events' }` * when omitted; pass `{ event_type: { specific_event: '…' } }` to scope * to a single event. */ registerWebhook(merchantId: string, connectorId: string, params?: ConnectorWebhookRegisterRequest): Promise; /** * Register checkout/shop domains as Stripe payment method domains, so Apple * Pay renders on those pages. * `POST /account/{merchantId}/connectors/{connectorId}/stripe/payment-method-domains` * * Stripe connectors only. One call registers against a single credential set * (`environment`, default `'live'`) — call twice to cover live and sandbox. * Per-URL outcomes come back in `results`; a missing sandbox credential set * is a request-level 400. */ registerStripePaymentMethodDomains(merchantId: string, connectorId: string, params: StripePaymentMethodDomainsRegisterRequest): Promise; /** * Register checkout/shop domains as Airwallex Apple Pay domains, so Apple Pay * renders on those pages. * `POST /account/{merchantId}/connectors/{connectorId}/airwallex/apple-pay-domains` * * Airwallex connectors only. One call registers against a single credential * set (`environment`, default `'live'`) — call twice to cover live and * sandbox. Idempotent: a host already on the account comes back * `already_registered` without a write. */ registerAirwallexApplePayDomains(merchantId: string, connectorId: string, params: AirwallexApplePayDomainsRegisterRequest): Promise; /** Get registered webhooks for a connector. `GET /account/{merchantId}/connectors/webhooks/{connectorId}` */ getWebhook(merchantId: string, connectorId: string): Promise; /** * Bring an already-registered webhook's event subscription up to date with * the events Delopay handles. * `POST /account/{merchantId}/connectors/webhooks/{connectorId}/sync-events` * * A PSP freezes an endpoint's event list at registration time, so an endpoint * created before an event type was added never receives it — silently, with * no error anywhere. {@link getWebhook} reports the gap as `missing_events`; * this repairs it. * * Unlike re-registering, the endpoints are updated in place: the endpoint id * and its signing secret are preserved, so signature verification keeps * working across the change. Stripe connectors only; idempotent, so it is * safe to call on a schedule or after every deploy. * * @example * ```typescript * const sync = await delopay.connectors.syncWebhookEvents('mer_abc', 'mca_xyz'); * for (const endpoint of sync.endpoints) { * if (endpoint.error_message) console.warn(endpoint.connector_webhook_id, endpoint.error_message); * else if (endpoint.updated) console.log('subscribed', endpoint.added_events); * } * ``` */ syncWebhookEvents(merchantId: string, connectorId: string): Promise; /** List available payment methods. `GET /account/payment-methods` */ listPaymentMethods(): Promise[]>; } /** Create and manage customer profiles. */ declare class Customers { private readonly request; constructor(request: RequestFn); /** * Create a new customer. * * @param params - Customer creation parameters (name, email, phone, address, etc.). * @returns The created customer. * * @example * ```typescript * const customer = await delopay.customers.create({ * email: 'alice@example.com', * name: 'Alice Smith', * }); * ``` */ create(params: CustomerCreateRequest): Promise; /** * Retrieve a customer by their ID. * * @param customerId - The unique customer ID. * @returns The customer. * * @example * ```typescript * const customer = await delopay.customers.retrieve('cus_abc123'); * ``` */ retrieve(customerId: string): Promise; /** * Update an existing customer's details. * * @param customerId - The customer ID to update. * @param params - Fields to update (name, email, address, metadata, etc.). * @returns The updated customer. */ update(customerId: string, params: CustomerUpdateRequest): Promise; /** * Delete a customer and all their saved payment methods. * * @param customerId - The customer ID to delete. * @returns The deleted customer object. */ delete(customerId: string): Promise; /** * List customers, optionally filtered by customer id/name/email prefix, * shop (`profile_id`), or project (`project_id`). * * @param params - Optional filter and pagination parameters. * @returns Array of customer objects. * * @example * ```typescript * // Customers who have transacted in a specific shop. * const customers = await delopay.customers.list({ profile_id: 'pro_abc123' }); * * // Several shops at once (unions with profile_id / project_id). * const many = await delopay.customers.list({ * profile_ids: ['pro_abc123', 'pro_def456'], * }); * ``` */ list(params?: CustomerListParams): Promise; /** * List customers with count. Supports the same `profile_id` / `project_id` * shop filters as {@link list}. `GET /customers/list-with-count` */ listWithCount(params?: CustomerListWithCountParams): Promise; /** * List customers scoped to the authenticated dashboard user's shop * (business profile). The JWT auto-scopes to its own `profile`; an explicit * `profile_id` / `project_id` outside that scope is rejected with * `AccessForbidden`. `GET /customers/profile/list` * * @param params - Optional filter and pagination parameters. * @returns Array of customer objects. */ listByProfile(params?: CustomerListParams): Promise; /** * Profile-scoped variant of {@link listWithCount}. * `GET /customers/profile/list-with-count` */ listByProfileWithCount(params?: CustomerListWithCountParams): Promise; /** * Retrieve one customer, scoped to the authenticated dashboard user's shop * (business profile). The shop-scoped counterpart of {@link retrieve}, and * what makes a shop's own customer list clickable: the list is * `ProfileCustomerRead` while {@link retrieve} is merchant-level, so a shop * member could see a customer they could not open. * * A customer belonging to a sibling shop answers `CustomerNotFound` — the * same answer as a customer that does not exist, deliberately, so one shop * cannot enumerate another's customer ids. * * `GET /customers/profile/{customerId}` * * @param customerId - The unique customer ID. * @returns The customer, with `profile_ids` narrowed to the caller's own shop. */ retrieveByProfile(customerId: string): Promise; /** * Create a customer from a shop, recording that it belongs to that shop so * the shop can see the customer it just created. The shop is taken from the * credential; a caller-chosen `customer_id` is rejected. * * `POST /customers/profile` * * @param params - The customer to create. * @returns The created customer. */ createByProfile(params: CustomerCreateRequest): Promise; /** * Update a customer from a shop, if the customer belongs to that shop. * A sibling shop's customer answers `CustomerNotFound`, as with * {@link retrieveByProfile}. * * `POST /customers/profile/{customerId}` * * @param customerId - The customer ID to update. * @param params - Fields to update. * @returns The updated customer. */ updateByProfile(customerId: string, params: CustomerUpdateRequest): Promise; /** List mandates for a customer. `GET /customers/{customerId}/mandates` */ listMandates(customerId: string): Promise[]>; /** * List one customer's mandates, scoped to the caller's shop. Same membership * rule as {@link retrieveByProfile}: a sibling shop's customer answers * `CustomerNotFound` before any mandate is read. * * `GET /customers/profile/{customerId}/mandates` */ listMandatesByProfile(customerId: string): Promise[]>; } /** View and respond to payment disputes and chargebacks. */ declare class Disputes { private readonly request; constructor(request: RequestFn); /** Read native disputes and provider-only cases once, after shared authorization and filters. */ workspace(params?: DisputeWorkspaceRequest): Promise; /** Read filter options over the same scoped case set; pagination is ignored. */ workspaceFilters(params?: DisputeWorkspaceRequest): Promise; /** Count the filtered case set, including unknown outcomes; pagination is ignored. */ workspaceAggregate(params?: DisputeWorkspaceRequest): Promise; /** * Export the complete filtered set, or throw on the server's explicit * export-limit refusal. `GET /disputes/workspace/export` * * The body is streamed, and held to the `X-Delopay-Record-Count` the server * announces and to its own `total_count`: a set that arrives short rejects * with `EXPORT_INCOMPLETE` rather than resolving. `options` reports the * download's progress and takes a signal and an idle timeout — see * {@link ExportTransferOptions}. * * `format: 'pdf'` resolves to the same set laid out as a document (a * `Blob`), with the words in `options.presentation` — see * {@link ExportPresentation}. This route offers no CSV. */ workspaceExport(params?: DisputeWorkspaceRequest, options?: JsonExportOptions): Promise; workspaceExport(params: DisputeWorkspaceRequest | undefined, options: PdfExportOptions): Promise; workspaceExport(params: DisputeWorkspaceRequest | undefined, options: DynamicWorkspaceExportOptions): Promise; /** * Retrieve a dispute by its ID. * * @param disputeId - The unique dispute ID. * @returns The dispute. */ retrieve(disputeId: string): Promise; /** Read declared links and provider-reported refund references. Empty means unknown. */ listRefundAssociations(disputeId: string): Promise; /** Declare or clear an allocation against a successful refund of this payment. */ setRefundAssociation(disputeId: string, params: DisputeRefundAssociationRequest): Promise; /** * List disputes, optionally filtered by status, stage, or date range. * * @param params - Optional filter and pagination parameters. * @returns Array of disputes. */ list(params?: DisputeListParams): Promise; /** * Accept a dispute, conceding the chargeback to the customer. * * @param disputeId - The dispute ID to accept. * @returns The updated dispute. */ accept(disputeId: string): Promise; /** * Submit evidence to challenge a dispute. * * @param params - Evidence details and the dispute ID to contest. * @returns The updated dispute. */ submitEvidence(params: DisputeEvidenceRequest): Promise; /** * Attach evidence (e.g. file upload metadata) to a dispute. * * Uses `PUT /disputes/evidence`. */ attachEvidence(params: DisputeEvidenceRequest): Promise; /** * Retrieve previously stored evidence for a dispute. * * Returns an ARRAY of file-evidence blocks (this was previously mistyped * as the flat submit-request shape). Only file evidence is reported — * text evidence is not retrievable once submitted. * * @param disputeId - The dispute ID. * @returns The stored file-evidence blocks. */ retrieveEvidence(disputeId: string): Promise; /** * Delete submitted evidence for a dispute. * * @param params - Evidence request body identifying what to delete. * @returns The updated dispute. */ deleteEvidence(params: DeleteEvidenceRequest): Promise; /** List disputes (profile-scoped). `GET /disputes/profile/list` */ listByProfile(params?: DisputeListParams): Promise; /** Get dispute filter options. `GET /disputes/filter` */ getFilters(params?: Record): Promise>; /** Get dispute filters (profile-scoped). `GET /disputes/profile/filter` */ getFiltersByProfile(params?: Record): Promise>; /** Get dispute aggregates. `GET /disputes/aggregate` */ aggregate(params?: Record): Promise>; /** Get dispute aggregates (profile-scoped). `GET /disputes/profile/aggregate` */ aggregateByProfile(params?: Record): Promise>; /** * Fetch the latest dispute state from the connector (gateway) and persist it. * `GET /disputes/{disputeId}?force_sync=true` * * The path parameter is the **Delopay dispute id** (`dp_…`). Force-sync asks the * backend to pull the dispute from the connector (supported where the connector * implements the dispute-sync flow, e.g. Stripe) and update the stored record * before returning it. * * Note: this method previously called `GET /disputes/{id}/fetch`, which is a * different backend route — a bulk import keyed by **merchant connector account * id** with a required date range — so every call with a dispute id failed. */ fetchFromConnector(disputeId: string): Promise; } /** * PayPal seller cases, including external and subscription sales. * Every operation honors the caller's connector-account grants. */ declare class PaypalDisputes { private readonly request; constructor(request: RequestFn); actions(caseId: string, offset?: number): Promise; download(caseId: string, documentUrl: string): Promise; list(params?: PaypalDisputeListRequest): Promise; retrieve(caseId: string, forceSync?: boolean): Promise; history(caseId: string, offset?: number): Promise; import(params: PaypalDisputeImportRequest): Promise; /** The backend checks current provider permissions and deduplicates request_id. * An unknown result requires reconciliation, not a new request ID. */ act(caseId: string, params: PaypalDisputeActionRequest): Promise; } /** * Create short-lived ephemeral keys for secure client-side operations. * * Ephemeral keys grant a mobile or browser client temporary access to a * specific customer's data (e.g. to display saved payment methods) without * exposing your secret API key. * * The key is confined to the customer it was minted for, and that is * enforced on every customer and payment-method route: a request for another * customer — or for a payment method belonging to one — is refused rather * than served. Mint one key per customer; do not reuse a key across them. */ declare class EphemeralKeys { private readonly request; constructor(request: RequestFn); /** * Create an ephemeral key scoped to a specific customer. * * @param params - Customer ID and optional expiry. * @returns The ephemeral key with its plaintext secret and expiry timestamp. * * @example * ```typescript * const ephKey = await delopay.ephemeralKeys.create({ customer_id: 'cus_123' }); * // Pass ephKey.secret to your mobile app. * ``` */ create(params: EphemeralKeyCreateRequest): Promise; /** * Invalidate an ephemeral key before it expires. * * @param keyId - The ephemeral key ID to delete. * @returns The deleted key object. */ delete(keyId: string): Promise; } declare class Events { private readonly request; constructor(request: RequestFn); list(merchantId: string, params?: EventListParams): Promise; listDeliveryAttempts(merchantId: string, eventId: string): Promise; retryDelivery(merchantId: string, eventId: string): Promise; /** * Fire an outgoing webhook event by hand for one object. * `POST /events/{merchant_id}/trigger` * * Not a replay — see {@link EventManualTriggerRequest}. Needs the * `WebhookEventTrigger` permission, which is separate from the one that * authorizes {@link retryDelivery}, and is JWT-only: a forged payload must * be attributable to a person. */ trigger(merchantId: string, body: EventManualTriggerRequest): Promise; /** * List events (profile-scoped). `POST /events/profile/list` * * Takes the same constraints as {@link list} — it is the same endpoint * narrowed to the caller's own shop, so `profile_id` is redundant and * ignored there. */ listByProfile(params?: EventListParams): Promise; /** * Fire an outgoing webhook event by hand (profile-scoped). * `POST /events/profile/trigger` * * The shop-user twin of {@link trigger}; the shop is taken from the auth * context, so `profile_id` on the body is ignored. */ triggerByProfile(body: EventManualTriggerRequest): Promise; } /** * Merchant-scoped fee schedules. Each schedule optionally targets a * specific shop (via `shop_id`). Merchants can CRUD their own fee * overrides; platform-wide fee programs are administered by Delopay and * not exposed here. * * `fees.rules` manages the merchant-owned Euclid fee-rule program, which takes * precedence over the flat schedules above. A program is scoped either to one * shop (`profile_id`) or merchant-wide; a shop-scoped program wins for that * shop, otherwise the merchant-wide one applies. Build the program with the * `feeProgram()` helper. */ declare class Fees { private readonly request; /** Merchant-owned Euclid fee-rule program (`/merchant-fees/rules`). */ readonly rules: FeeRulesManager; constructor(request: RequestFn); /** * Create a merchant-scoped fee schedule (optionally per-shop). * * @param params - Fee schedule parameters. * @param merchantId - The merchant account ID. */ create(params: FeeScheduleCreateRequest, merchantId: string): Promise; /** * List the merchant's own fee schedules. * * @param merchantId - The merchant account ID. */ list(merchantId: string): Promise; /** * Update a merchant-scoped fee schedule. * * @param feeId - The fee schedule ID. * @param params - Fields to update. */ update(feeId: string, params: FeeScheduleUpdateRequest): Promise; /** * Delete a merchant-scoped fee schedule. * * @param feeId - The fee schedule ID. */ delete(feeId: string): Promise; } /** * Manages a merchant's Euclid fee-rule programs (merchant-wide or per-shop, one * active program per scope). Build the `algorithm` with `feeProgram()`. The SDK * injects `fee_owner: 'merchant'`; set `profile_id` on the input to scope a * program to a shop. */ declare class FeeRulesManager { private readonly request; constructor(request: RequestFn); /** * Create or replace the merchant's fee-rule program (a new active version; * the previous version is deactivated server-side). * * @param params - The program plus optional name / shop scope / validity window. * @param merchantId - The merchant account ID. */ upsert(params: PlatformFeeRuleInput, merchantId: string): Promise; /** * Retrieve the active fee-rule program for a scope, or `null` if none. * * @param merchantId - The merchant account ID. * @param profileId - Optional shop (`profile_id`) scope. Omit for the * merchant-wide program; pass a shop id to get that shop's program. */ retrieve(merchantId: string, profileId?: string): Promise; /** * Deactivate a fee-rule program (falls back to the flat fee schedules / * volume tier). Idempotent. * * @param merchantId - The merchant account ID. * @param profileId - Optional shop (`profile_id`) scope. Omit to target the * merchant-wide program; pass a shop id to delete only that shop's program * (other shops' programs are left intact). */ delete(merchantId: string, profileId?: string): Promise; /** * Dry-run a candidate fee-rule program against a sample transaction. * Returns the matched rule name, whether it fell through, and the computed fee. * Does not persist anything. * * @param input - Candidate program + sample transaction fields. * @param merchantId - The merchant account ID. */ preview(input: FeeRulePreviewRequest, merchantId: string): Promise; } /** View and revoke recurring payment mandates. */ declare class Mandates { private readonly request; constructor(request: RequestFn); /** * Retrieve a mandate by its ID. * * @param mandateId - The unique mandate ID. * @returns The mandate. */ retrieve(mandateId: string): Promise; /** * Revoke an active mandate, preventing future charges. * * @param mandateId - The mandate ID to revoke. * @returns Revocation confirmation. */ revoke(mandateId: string): Promise; /** * List mandates, optionally filtered by customer or status. * * @param params - Optional filter and pagination parameters. * @returns Array of mandates. */ list(params?: MandateListParams): Promise; } declare class MerchantAccounts { private readonly request; constructor(request: RequestFn); create(params: MerchantAccountCreateRequest): Promise; retrieve(accountId: string): Promise; update(accountId: string, params: MerchantAccountUpdateRequest): Promise; delete(accountId: string): Promise; /** List all merchant accounts. `GET /accounts/list` */ list(): Promise; /** Toggle key-value store for a merchant. `POST /accounts/{accountId}/kv` */ toggleKv(accountId: string): Promise>; /** Get KV status for a merchant. `GET /accounts/{accountId}/kv` */ getKvStatus(accountId: string): Promise>; /** Transfer keys between merchants. `POST /accounts/transfer` */ transferKeys(params: Record): Promise>; } /** Retrieve and list hosted payment links. */ declare class PaymentLinks { private readonly request; constructor(request: RequestFn); /** * Retrieve a payment link by its ID. * * @param linkId - The unique payment link ID. * @returns The payment link details. */ retrieve(linkId: string): Promise; /** * List payment links, optionally filtered by status or date range. * * @param params - Optional filter and pagination parameters. * @returns Paginated list of payment links. */ list(params?: PaymentLinkListParams): Promise; /** Initiate (render) a payment link page. `GET /payment-link/{merchantId}/{paymentId}` */ initiate(merchantId: string, paymentId: string): Promise>; /** Get payment link status. `GET /payment-link/status/{merchantId}/{paymentId}` */ status(merchantId: string, paymentId: string): Promise>; } /** Create and manage saved payment methods for customers. */ declare class PaymentMethods { private readonly request; constructor(request: RequestFn); /** * Save a new payment method (card, bank account, wallet, etc.). * * @param params - Payment method data including type and card/bank details. * @returns The saved payment method. * * @example * ```typescript * const pm = await delopay.paymentMethods.create({ * payment_method: 'card', * customer_id: 'cus_123', * client_secret: 'cs_...', * }); * ``` */ create(params: PaymentMethodCreateRequest): Promise; /** * Retrieve a saved payment method by its ID. * * @param methodId - The payment method ID. * @returns The payment method. */ retrieve(methodId: string): Promise; /** * Update an existing payment method (e.g. update card expiry). * * @param methodId - The payment method ID to update. * @param params - Fields to update (card expiry, holder name, etc.). * @returns The updated payment method. */ update(methodId: string, params: PaymentMethodUpdateRequest): Promise; /** * Delete a saved payment method. * * @param methodId - The payment method ID to delete. * @returns Deletion confirmation. */ delete(methodId: string): Promise; /** * List the payment methods available for a payment — the discovery endpoint a * custom checkout renders its tiles from. * * Callable with a publishable key plus the payment's `client_secret`, so it * runs from the browser. The returned set is already filtered by country, * order value and the merchant's availability rules, and each entry carries * `display` (name + icon slug) and `amount_limits` (the order values it stays * available for) so you do not have to maintain either alongside. * * This is *not* the customer's saved methods — see {@link listForCustomer}. * * @param params - `client_secret`, plus optional `country`, `amount` and filters. * @returns The methods available for the payment, grouped by payment method. * * @example * ```typescript * const { payment_methods } = await delopay.paymentMethods.list({ * client_secret: 'pay_abc_secret_xyz', * country: 'DE', * amount: 25000, * }); * * for (const group of payment_methods) { * for (const method of group.payment_method_types) { * // Re-check availability yourself as the cart total changes, instead of * // re-listing on every keystroke. * const limits = method.amount_limits; * const available = * !limits || * ((limits.min_amount == null || cartTotal >= limits.min_amount) && * (limits.max_amount == null || cartTotal <= limits.max_amount) && * !limits.excluded_ranges.some( * (band) => cartTotal >= band.min_amount && cartTotal <= band.max_amount, * )); * * if (available) render(method.display?.display_name, method.display?.icon_slug); * } * } * ``` */ list(params?: PaymentMethodListParams): Promise; /** * List all saved payment methods for a customer, optionally filtered. * * @param customerId - The customer ID. * @param params - Optional filters: `client_secret`, `accepted_countries`, `accepted_currencies`, * `amount`, `recurring_enabled`, `installment_payment_enabled`, `limit`, `card_networks`. * @returns Customer's saved payment methods. * * @example * ```typescript * const { customer_payment_methods } = await delopay.paymentMethods.listForCustomer( * 'cus_123', * { accepted_currencies: ['EUR'], amount: 5000 }, * ); * ``` */ listForCustomer(customerId: string, params?: CustomerPaymentMethodsListParams): Promise; /** * Set a payment method as the default for a customer. * * @param customerId - The customer ID. * @param methodId - The payment method ID to set as default. * @returns The updated payment method. */ setDefault(customerId: string, methodId: string): Promise; /** Migrate a payment method. `POST /payment-methods/migrate` */ migrate(params: Record): Promise; /** Batch migrate payment methods. `POST /payment-methods/migrate-batch` */ migrateBatch(params: Record[]): Promise>; /** Batch update payment methods. `POST /payment-methods/update-batch` */ updateBatch(params: Record[]): Promise>; /** Batch retrieve payment methods. `GET /payment-methods/batch` */ batchRetrieve(params?: Record): Promise; /** Tokenize a card. `POST /payment-methods/tokenize-card` */ tokenizeCard(params: Record): Promise>; /** Batch tokenize cards. `POST /payment-methods/tokenize-card-batch` */ tokenizeCardBatch(params: Record[]): Promise>; /** Initiate payment method collect link flow. `POST /payment-methods/collect` */ collect(params: Record): Promise>; /** Save a payment method. `POST /payment-methods/{methodId}/save` */ save(methodId: string, params?: Record): Promise; /** Create payment method auth link token. `POST /payment-methods/auth/link` */ createAuthLink(params: Record): Promise>; /** Exchange payment method auth token. `POST /payment-methods/auth/exchange` */ exchangeAuthToken(params: Record): Promise>; /** Tokenize card using existing PM. `POST /payment-methods/{methodId}/tokenize-card` */ tokenizeCardForMethod(methodId: string, params: Record): Promise>; } /** Manage payment intents — create, confirm, capture, cancel, and list payments. */ declare class Payments { private readonly request; constructor(request: RequestFn); /** * Create a new payment intent. * * @param params - Payment creation parameters including amount and currency. * @param options - Optional per-call extras: extra `headers` (e.g. an * `Idempotency-Key` to make the create safe to retry), a `timeout` override, * and an `AbortSignal`. * @returns The created payment intent. * * @example * ```typescript * const payment = await delopay.payments.create( * { amount: 5000, currency: 'EUR', customer_id: 'cus_123' }, * { headers: { 'Idempotency-Key': 'order_1001' } }, * ); * ``` * * @example Send `test_mode` to pick the environment per payment, so a staging * deploy cannot charge real cards and a forgotten processor toggle cannot * swallow production traffic: * ```typescript * const payment = await delopay.payments.create({ * amount: 5000, * currency: 'EUR', * test_mode: process.env.NODE_ENV !== 'production', * }); * ``` * * A payment that pins one connector through `routing` (the `single` form) * is now checked against `test_mode` here rather than at confirm: if that * connector has no credentials for the environment asked for, create fails * instead of handing back a payment whose checkout the buyer cannot * complete. `priority` and `volume_split` name several accounts and are * still resolved at confirm. */ create(params: PaymentCreateRequest, options?: RequestExtras): Promise; /** * Retrieve a payment by its ID. * * @param paymentId - The unique payment intent ID. * @param options - Optional query flags. `force_sync` reconciles the * intent's state with the connector before returning (useful to recover * a stuck intent when a webhook was lost). `all_keys_required` forces a * connector sync even for intents in early states like * `requires_payment_method` that would otherwise return the local * snapshot. Both flags work with JWT and API-key authentication. * @returns The payment intent. * * @example * ```typescript * const payment = await delopay.payments.retrieve('pay_abc123'); * const synced = await delopay.payments.retrieve('pay_abc123', { * force_sync: true, * all_keys_required: true, * }); * ``` */ retrieve(paymentId: string, options?: PaymentRetrieveOptions): Promise; /** * List every attempt made on a payment, each with its full failure detail * (`error_code` / `error_message`, the Delopay-unified `unified_code` and * `unified_message`, and structured `error_details`). * * Useful for surfacing retries across connectors — e.g. "attempt 1 stripe → * insufficient_funds, attempt 2 adyen → success". * * `GET /payments/{paymentId}/attempts` * * @param paymentId - The payment intent ID whose attempts to list. * @param options - Optional per-call extras: extra `headers`, a `timeout` * override, and an `AbortSignal`. * @returns The attempt list — `size` plus a `data` array of attempts. * @throws If the payment does not exist or belongs to another merchant (404). * * @example * ```typescript * const { size, data } = await delopay.payments.listAttempts('pay_abc123'); * for (const attempt of data) { * console.log(attempt.status, attempt.unified_message ?? attempt.error_message); * } * ``` */ listAttempts(paymentId: string, options?: RequestExtras): Promise; /** * The status timeline of a payment: every recorded creation / status * transition of the intent and its attempts, refunds and disputes, oldest * first. `complete: false` marks timelines partially reconstructed from * current records (payments created before the status log existed). * * @param paymentId - The payment intent ID. * @returns The ordered status-history events. * * @example * ```typescript * const { events, complete } = await delopay.payments.listStatusHistory('pay_abc123'); * for (const event of events) { * console.log(event.timestamp, event.entity_type, event.status); * } * ``` */ listStatusHistory(paymentId: string, options?: RequestExtras): Promise; /** * What the payment's rail says about settling each attempt: whether the * money is still `pending` in the rail's hands, `available` in its balance * (**not** paid out), `paid_out` to a bank — with the payout's date and * reference — or came back as `payout_failed`; and, for the rest, which * kind of not-knowing applies (`unavailable`, `unknown`, `not_recorded`). * See {@link ConnectorSettlementStatus} for the seven values. The same * status for the active attempt rides on every list row and retrieve as * `connector_settlement_status`, and the list filters on it. * * Not the hosted-shop settlement API under `settlement.*`, which is host → * shop owner and manual; this is acquirer → merchant bank, per payment. * * Each attempt also carries what the rail itself took for it — * `processor_cost_amount` with its own `processor_cost_currency` and * `processor_cost_exponent`, the `processor_cost_source` that says how the * figure was obtained, the `processor_cost_basis` behind an estimate, and * the `connector_method_code` it was priced under. Recorded for **every** * merchant, not only those that host shops. Never present an `estimated` * cost as an observation, and read an absent `processor_cost_source` as * "nothing was ever written", which is not the `unavailable` the rail * answers with. * * `GET /payments/{paymentId}/settlement` * * @param paymentId - The payment intent ID. * @param options - Optional per-call extras: extra `headers`, a `timeout` * override, and an `AbortSignal`. * @returns The attempts this caller may see, oldest first, each with its * settlement status. * @throws If the payment does not exist (404), or the caller is a * profile-scoped (shop) role, who is refused rather than shown a redacted * body (403). * * @example * ```typescript * const { attempts } = await delopay.payments.settlement('pay_abc123'); * for (const attempt of attempts) { * if (attempt.status === 'paid_out') { * console.log(attempt.paid_out_at, attempt.payout_reference); * } * if (attempt.processor_cost_source === 'estimated') { * // Label it an estimate — it was derived from a cost schedule. * } * } * ``` */ settlement(paymentId: string, options?: RequestExtras): Promise; /** * Update an existing payment intent before it is confirmed. * * @param paymentId - The payment intent ID to update. * @param params - Fields to update (amount, currency, metadata, etc.). * @returns The updated payment intent. */ update(paymentId: string, params: PaymentUpdateRequest): Promise; /** * Confirm a payment intent, triggering authorisation with the selected gateway. * * @param paymentId - The payment intent ID to confirm. * @param params - Confirmation parameters (payment method data, return URL, etc.). * @returns The updated payment intent. */ confirm(paymentId: string, params: PaymentConfirmRequest): Promise; /** * Capture a previously authorised payment. * * @param paymentId - The payment intent ID to capture. * @param params - Optional capture parameters (partial capture amount, etc.). * @returns The updated payment intent. */ capture(paymentId: string, params?: PaymentCaptureRequest): Promise; /** * Cancel a payment intent that has not yet been captured. * * @param paymentId - The payment intent ID to cancel. * @param params - Optional cancellation reason. * @returns The updated payment intent. */ cancel(paymentId: string, params?: PaymentCancelRequest): Promise; /** * List payment intents, optionally filtered by customer or date range. * * @param params - Optional filter and pagination parameters. * @param options - Optional per-call extras: extra `headers`, a `timeout` * override, and an `AbortSignal` for cancellation. * @returns Paginated list of payment intents. * * @example * ```typescript * const { data } = await delopay.payments.list({ customer_id: 'cus_123', limit: 25 }); * ``` */ list(params?: PaymentListParams, options?: RequestExtras): Promise; /** * The status timeline of client/device observations captured while the * buyer interacted with the payment (checkout opens, confirms, redirect * legs, reported client signals), oldest first. * * `GET /payments/{paymentId}/client-context` */ listClientContext(paymentId: string, options?: RequestExtras): Promise; /** * The routing decision recorded behind each attempt of a payment: which rule * matched, which candidates routing produced and which filter removed each, * which volume bucket was drawn and what the counters read, which * configuration version was live. * * This is the only truthful account of **how the connector was chosen**. * `routing_approach` on the payment is seeded on creation and several paths — * mandate, pre-routed and pinned-account among them — return before it is * overwritten, so it can still read `default_fallback` on a payment that was * routed by a rule. Read {@link RoutingDecision.decision_kind} instead. * * An attempt with `decision: null` simply has no record: the recorder is * best-effort, attempts that never reached routing have nothing to record, * and attempts predating the record exist in quantity. It is not an error. * * `GET /payments/{paymentId}/routing-decisions` * * @param paymentId - The payment intent ID. * @param options - Optional per-call extras: extra `headers`, a `timeout` * override, and an `AbortSignal`. * @returns Every attempt returned for this payment, with its decision. * * @example * ```typescript * const { attempts } = await delopay.payments.routingDecisions('pay_abc123'); * for (const attempt of attempts) { * if (!attempt.decision) continue; * console.log(attempt.connector, attempt.decision.decision_kind); * } * ``` */ routingDecisions(paymentId: string, options?: RequestExtras): Promise; /** * Open a playback of the payment's stored checkout recording: the audited * act of watching. Answers the manifest — which slices exist, how long the * recording runs, whether a pause in it is expected — and the * `playback_id` every {@link Payments.replaySlice} call carries. * * A payment that was never recorded answers `200` with `slices: []` and * `session_id: null` — the ordinary case for every payment made before the * checkout started recording — not a `404`. `404` means the payment does not * exist. Fetch the slices in `sequence` order; a number missing from the * list is a gap, not the end. * * A `POST`, because it writes one audit row: the SDK retries `GET` on its * own, and a lost response must not open three playbacks. Calling this * again deliberately opens another playback. * * Requires the `PaymentReplay` permission on a dashboard session in addition * to payment read access. * * `POST /payments/{paymentId}/replay/playback` * * @param paymentId - The payment intent ID. * @param options - Optional per-call extras: extra `headers`, a `timeout` * override, and an `AbortSignal`. */ openReplayPlayback(paymentId: string, options?: RequestExtras): Promise; /** * One slice of the payment's stored checkout recording, read against an * open playback: the rrweb events the checkout recorded under that number. * `playbackId` and the numbers come from {@link Payments.openReplayPlayback}; * a `playbackId` that names no playback of this payment opened by this * caller, and a number the manifest does not list, both answer `404`. * Not audited per slice — the playback was. * * `GET /payments/{paymentId}/replay/playback/{playbackId}/slices/{sequence}` * * @param paymentId - The payment intent ID. * @param playbackId - The playback, from `openReplayPlayback`. * @param sequence - The slice number, from the playback's manifest. * @param options - Optional per-call extras: extra `headers`, a `timeout` * override, and an `AbortSignal`. */ replaySlice(paymentId: string, playbackId: string, sequence: number, options?: RequestExtras): Promise; /** * Soft-delete a payment. Only payments whose status is in the merchant's * delete policy (see {@link Payments.getDeletePolicy}) can be deleted; * anything else fails with a precondition error. * * `DELETE /payments/{paymentId}` */ delete(paymentId: string, options?: RequestExtras): Promise; /** * The effective deletable-status set for the calling merchant — lets a * dashboard show the delete action only where it is allowed. * * `GET /payments/delete-policy` */ getDeletePolicy(options?: RequestExtras): Promise; /** Generate session tokens. `POST /payments/session-tokens` */ sessionTokens(params: Record): Promise>; /** Retrieve payment with gateway credentials. `POST /payments/sync` */ sync(params: Record): Promise; /** Cancel after partial capture. `POST /payments/{paymentId}/cancel-post-capture` */ cancelPostCapture(paymentId: string, params?: Record): Promise; /** Incrementally authorize more funds. `POST /payments/{paymentId}/incremental-authorization` */ incrementalAuthorization(paymentId: string, params: Record): Promise; /** Extend authorization window. `POST /payments/{paymentId}/extend-authorization` */ extendAuthorization(paymentId: string, params?: Record): Promise; /** Complete authorization. `POST /payments/{paymentId}/complete-authorize` */ completeAuthorize(paymentId: string, params?: Record): Promise; /** Dynamic tax calculation. `POST /payments/{paymentId}/calculate-tax` */ calculateTax(paymentId: string, params: Record): Promise>; /** Update payment metadata. `POST /payments/{paymentId}/update-metadata` */ updateMetadata(paymentId: string, params: Record): Promise; /** Retrieve extended card info. `GET /payments/{paymentId}/extended-card-info` */ extendedCardInfo(paymentId: string): Promise>; /** List payments (profile-scoped). `GET /payments/profile/list` */ listByProfile(params?: PaymentListParams): Promise; /** List payments across all shops. `GET /payments/list-all-shops` */ listAllShops(params?: PaymentListParams): Promise; /** List payments by filter (POST body). `POST /payments/list` */ listByFilter(params: PaymentListFilterConstraints): Promise; /** * List payments by filter, scoped to the caller's profile (the shop-user * twin of `listByFilter`). The backend narrows to the profile from the * auth context, so `profile_id` / `project_id` must not be sent — and * neither may `connector_settlement_status`, which this route refuses * (403): a shop-scoped viewer is not shown the settlement state, and the * type leaves the key out so it cannot be probed through `total_count`. * * Not to be confused with {@link Payments.listByProfile}, which is the GET * cursor variant and rejects this body. * * `POST /payments/profile/list` */ listByProfileFilter(params: ProfilePaymentListFilterConstraints, options?: RequestExtras): Promise; /** Get payment filter options. `GET /payments/filter` */ getFilters(params?: Record): Promise; /** * Get payment filter options, scoped to the caller's profile. * `GET /payments/profile/filter` */ getFiltersByProfile(params?: Record): Promise; /** Get payment aggregates. `GET /payments/aggregate` */ aggregate(params?: Record): Promise>; /** Get payment aggregates (profile-scoped). `GET /payments/profile/aggregate` */ aggregateByProfile(params?: Record): Promise>; /** Manually update payment status. `PUT /payments/{paymentId}/manual-update` */ manualUpdate(paymentId: string, params: Record): Promise; /** Approve a payment waiting for review. `POST /payments/{paymentId}/approve` */ approve(paymentId: string, params?: Record): Promise; /** Reject a payment waiting for review. `POST /payments/{paymentId}/reject` */ reject(paymentId: string, params?: Record): Promise; /** Initiate external 3DS authentication. `POST /payments/{paymentId}/3ds/authentication` */ threeDsAuthentication(paymentId: string, params: Record): Promise>; } /** Create and manage payouts — fund transfers from merchant to a recipient bank account. */ declare class Payouts { private readonly request; constructor(request: RequestFn); /** * Create a new payout. * * @param params - Payout parameters including amount, currency, and destination. * @returns The created payout. * * @example * ```typescript * const payout = await delopay.payouts.create({ * amount: 10000, * currency: 'EUR', * customer_id: 'cus_123', * }); * ``` */ create(params: PayoutCreateRequest): Promise; /** * Retrieve a payout by its ID. * * @param payoutId - The unique payout ID. * @returns The payout. */ retrieve(payoutId: string): Promise; /** * Update a payout before it is confirmed. * * @param payoutId - The payout ID to update. * @param params - Fields to update. * @returns The updated payout. */ update(payoutId: string, params: PayoutUpdateRequest): Promise; /** * Confirm a payout, triggering the actual transfer. * * @param payoutId - The payout ID to confirm. * @param params - Optional confirmation parameters. * @returns The updated payout. */ confirm(payoutId: string, params?: PayoutUpdateRequest): Promise; /** * Cancel a payout before it is fulfilled. * * @param payoutId - The payout ID to cancel. * @returns The cancelled payout. */ cancel(payoutId: string): Promise; /** * Mark a payout as fulfilled (manual confirmation of successful transfer). * * @param payoutId - The payout ID to fulfil. * @returns The fulfilled payout. */ fulfill(payoutId: string): Promise; /** * List payouts, optionally filtered by status or date range. * * @param params - Optional filter and pagination parameters. * @returns Paginated list of payouts. */ list(params?: PayoutListParams): Promise; /** List payouts (profile-scoped). `GET /payouts/profile/list` */ listByProfile(params?: PayoutListParams): Promise; /** List payouts by filter (POST body). `POST /payouts/list` */ listByFilter(params: Record): Promise; /** Get payout filter options. `GET /payouts/filter` */ getFilters(params?: Record): Promise>; /** Get payout filters (profile-scoped). `GET /payouts/profile/filter` */ getFiltersByProfile(params?: Record): Promise>; /** Get payout aggregates. `GET /payouts/aggregate` */ aggregate(params?: Record): Promise>; /** Get payout aggregates (profile-scoped). `GET /payouts/profile/aggregate` */ aggregateByProfile(params?: Record): Promise>; /** Manually update payout status. `PUT /payouts/{payoutId}/manual-update` */ manualUpdate(payoutId: string, params: Record): Promise; } declare class Poll { private readonly request; constructor(request: RequestFn); getStatus(pollId: string): Promise; } declare class ProfileAcquirers { private readonly request; constructor(request: RequestFn); create(params: ProfileAcquirerCreateRequest): Promise; update(profileId: string, profileAcquirerId: string, params: ProfileAcquirerUpdateRequest): Promise; } declare class Profiles { private readonly request; constructor(request: RequestFn); create(accountId: string, params: ProfileCreateRequest): Promise; retrieve(accountId: string, profileId: string): Promise; list(accountId: string): Promise; /** * List the business profiles the caller can see at profile scope — the * `ProfileAccountRead` twin of `list()` (which needs merchant-level read). * A shop-scoped user gets exactly their own shop back. * * `GET /account/{accountId}/profile` */ listByProfile(accountId: string): Promise; update(accountId: string, profileId: string, params: ProfileUpdateRequest): Promise; delete(accountId: string, profileId: string): Promise; /** Toggle extended card info for a profile. `POST /account/{accountId}/business-profile/{profileId}/toggle-extended-card-info` */ toggleExtendedCardInfo(accountId: string, profileId: string): Promise; /** Toggle connector agnostic MIT. `POST /account/{accountId}/business-profile/{profileId}/toggle-connector-agnostic-mit` */ toggleConnectorAgnosticMit(accountId: string, profileId: string): Promise; } /** Create and manage projects — optional grouping layers that contain one or more shops. */ declare class Projects { private readonly request; constructor(request: RequestFn); /** * Create a new project under a merchant account. * * @param params - Project creation parameters (name, description, etc.). * @param merchantId - The merchant account ID that owns this project. * @returns The created project. * * @example * ```typescript * const project = await delopay.projects.create({ name: 'EU Stores' }, 'merch_123'); * ``` */ create(params: ProjectCreateRequest, merchantId: string): Promise; /** * Retrieve a project by its ID. * * @param projectId - The unique project ID. * @param merchantId - Optional merchant scope. When provided, sent as * `?merchant_id=…` — required by dashboards that authenticate with a JWT * spanning multiple merchants and need to disambiguate which one this * call applies to. API-key callers can omit it. * @returns The project. */ retrieve(projectId: string, merchantId?: string): Promise; /** * Update a project's details. * * @param projectId - The project ID to update. * @param params - Fields to update. * @param merchantId - Optional merchant scope. See {@link Projects.retrieve}. * @returns The updated project. */ update(projectId: string, params: ProjectUpdateRequest, merchantId?: string): Promise; /** * Delete a project. * * @param projectId - The project ID to delete. * @param merchantId - Optional merchant scope. See {@link Projects.retrieve}. * @returns The deleted project object. */ delete(projectId: string, merchantId?: string): Promise; /** * List all projects for a merchant. * * @param merchantId - The merchant account ID. * @returns Array of projects. */ list(merchantId: string): Promise; /** * Get aggregate payment statistics across all projects for a merchant. * * The response's flat `shops[]` array holds every shop, including shops that * belong to no project — those are absent from `projects[].shops[]`, so look * a single shop up in `shops[]`. Requires `MerchantAccountRead`; a * shop-scoped user should call {@link Shops.stats} instead. * * @param merchantId - The merchant account ID. * @param period - Window in days, or `'all'` for an all-time total. * Omitted means the server default of 30 days. * @returns Project statistics. * * @example * ```typescript * const stats = await delopay.projects.stats('merch_123', 'all'); * const shop = stats.shops.find((s) => s.shop_id === 'pro_1'); * ``` */ stats(merchantId: string, period?: StatsPeriod): Promise; /** * Get a high-level overview (volume, counts, top connectors) for a merchant. * * @param merchantId - The merchant account ID. * @returns Merchant overview data. */ overview(merchantId: string): Promise; } /** Create and manage refunds for completed payments. */ declare class Refunds { private readonly request; constructor(request: RequestFn); /** * Create a refund for a payment. * * Dashboard-initiated refunds are subject to the caller's operation-limit * rule, resolved against the role the request authenticated with. An * over-limit refund either fails with `DE_01` (the rule blocks) or with * HTTP 409 `DE_06` — the rule requires approval, and `DelopayError.data` * carries `PendingApprovalErrorDetails`. No refund exists in either case; * `DE_06` names one that a second approver can still let through, via * `operationLimits.approve()`. * * @param params - Refund parameters, including the required `payment_id` and optional amount. * @returns The created refund. * * @example * ```typescript * const refund = await delopay.refunds.create({ * payment_id: 'pay_abc123', * amount: 2500, // partial refund of 25.00 EUR * }); * ``` */ create(params: RefundCreateRequest): Promise; /** * Retrieve a refund by its ID. * * @param refundId - The unique refund ID. * @returns The refund. * * @example * ```typescript * const refund = await delopay.refunds.retrieve('ref_abc123'); * ``` */ retrieve(refundId: string): Promise; /** * Update the reason or metadata on an existing refund. * * @param refundId - The refund ID to update. * @param params - Fields to update (reason, metadata). * @returns The updated refund. */ update(refundId: string, params: RefundUpdateRequest): Promise; /** * List refunds, optionally filtered by payment, status, or date range. * * @param params - Optional filter and pagination parameters. * @returns Paginated list of refunds. */ list(params?: RefundListParams): Promise; /** List refunds (profile-scoped). `POST /refunds/profile/list` */ listByProfile(params?: RefundListParams): Promise; /** Get refund filter options. `GET /refunds/filter` */ getFilters(params?: Record): Promise>; /** Get refund aggregates. `GET /refunds/aggregate` */ aggregate(params?: Record): Promise; /** Get refund aggregates (profile-scoped). `GET /refunds/profile/aggregate` */ aggregateByProfile(params?: Record): Promise; /** Manually update refund status. `PUT /refunds/{refundId}/manual-update` */ manualUpdate(refundId: string, params: Record): Promise; } declare class Relay { private readonly request; constructor(request: RequestFn); create(params: RelayRequest): Promise; retrieve(relayId: string): Promise; } /** * Create and manage payment routing algorithms. * * Routing rules determine which gateway connector handles each payment based on * card type, currency, amount, or custom conditions. */ declare class Routing { private readonly request; readonly decision: RoutingDecisionManager; /** Merchant-friendly, per-payment-method surcharge rules (no Euclid DSL). */ readonly surchargeRules: SurchargeRules; /** Which stored appearance variant a buyer is shown. Decides a look, never a payment. */ readonly checkoutThemeRules: CheckoutThemeRules; /** Rendered-to-paid conversion per appearance variant and segment. */ readonly checkoutThemeConversion: CheckoutThemeConversion; constructor(request: RequestFn); /** * Create a new routing algorithm. * * @param params - Routing algorithm definition (rule-based, priority, or volume-based). * @returns Metadata record for the created routing configuration. The full * algorithm body is not echoed back — use `retrieve(id)` if you need it. * * @example * ```typescript * const config = await delopay.routing.create({ * name: 'EU Priority', * algorithm: { type: 'priority', data: [{ connector: 'stripe' }] }, * }); * ``` * * @example Conditional volume split (advanced): cards → 90% epayouts / 10% stripe. * Rules run top-down (first match wins); `defaultSelection` is the fallback. * Splits must sum to 100; `amount` conditions are in minor units. * ```typescript * const config = await delopay.routing.create({ * name: 'Card split 90/10', * profile_id: 'pro_...', * algorithm: { * type: 'advanced', * data: { * defaultSelection: { type: 'priority', data: [{ connector: 'stripe' }] }, * rules: [ * { * name: 'cards', * connectorSelection: { * type: 'volume_split', * data: [ * { connector: { connector: 'epayouts' }, split: 90 }, * { connector: { connector: 'stripe' }, split: 10 }, * ], * }, * statements: [ * { * condition: [ * { * lhs: 'payment_method', * comparison: 'equal', * value: { type: 'enum_variant', value: 'card' }, * metadata: {}, * }, * ], * }, * ], * }, * ], * metadata: {}, * }, * }, * }); * await delopay.routing.activate(config.id); * ``` */ create(params: RoutingConfigCreateRequest): Promise; /** * Retrieve a routing algorithm by its ID. * * @param algorithmId - The routing algorithm ID. * @returns The full routing configuration including the algorithm body. */ retrieve(algorithmId: string): Promise; /** * Activate a routing algorithm, making it the active routing strategy. * * Always sends a JSON body (default `{}`) so the request carries the * `Content-Type: application/json` header that the server requires. * * @param algorithmId - The routing algorithm ID to activate. * @param params - Optional activation payload (e.g. `transaction_type`). */ activate(algorithmId: string, params?: RoutingActivatePayload): Promise; /** * Deactivate the currently active routing algorithm (falls back to default routing). * * Always sends a JSON body (default `{}`) so the request carries the * `Content-Type: application/json` header that the server requires. * * @param params - Optional deactivation payload. */ deactivate(params?: RoutingDeactivateRequest): Promise; /** * Edit a static routing configuration. * * Partial: send only the fields to change. `name`/`description` are * metadata-only; `algorithm` is a wholesale rule replacement, validated * against the shop exactly as at create. `modified_at` is bumped either way. * * `PUT /routing/{algorithmId}` * * @param algorithmId - The routing algorithm ID to edit. * @param params - The fields to change (at least one required). * @returns The updated routing configuration including the algorithm body. */ update(algorithmId: string, params: RoutingConfigUpdateRequest): Promise; /** * Delete a static routing configuration. * * A soft delete: the configuration disappears from every list and read, but * its version history is kept server-side for audit. The live configuration * of a shop cannot be deleted — the backend refuses it (400, `IR_16`); deactivate it first. * Only `single`, `priority`, `volume_split` and `advanced` payment * configurations qualify (400 otherwise). * * `DELETE /routing/{algorithmId}` * * @param algorithmId - The routing algorithm to delete. */ delete(algorithmId: string): Promise; /** * Every content window a routing configuration has had, oldest first. * * A configuration's rule can be edited in place, so this is what makes "which * rule decided this payment" answerable after the fact. Each entry is the rule * as it stood between `valid_from` and `valid_until`; the windows of one * config abut exactly, with no gap. * * Paging covers the whole timeline including the live window, so a page never * holds more than `limit` entries and the live one — the only entry without a * `valid_until` — comes back on exactly one page. Advance `offset` by `limit`; * a page past the end is empty, and `total_count` says where that end is * without probing for it. * * `GET /routing/{algorithmId}/history` * * @param algorithmId - The routing algorithm to read the history of. * @param params - Optional paging. */ history(algorithmId: string, params?: RoutingHistoryParams): Promise; /** * A shop's per-connector caps, each with its unit and window and how much of * the current window is spent (`used`) or held by payments in flight * (`reserved`). * * `GET /routing/connector-caps/{profileId}` */ connectorCaps(profileId: string): Promise; /** * The live `routing_volume` counters behind a shop's active advanced * program: one per distinct budget the program reads, each with the window * it covers, the figure in the pinned threshold currency, and the rules that * read it. Lets a merchant see whether a `routing_volume < 50000` rule is at * 120 or at 499 today, and support answer why a payment went to the overflow * connector. * * What routing will use right now, not what the shop turned over: the * counters are held in Redis only and a lost Redis restarts the window at * zero. Read-only; needs the same permission as reading the rules. * * `GET /routing/volume-counters/{profileId}` */ volumeCounters(profileId: string): Promise; /** * Replace a shop's per-connector caps. * * Whole-set replacement, not a patch: the list sent becomes the complete set * of capped connectors, and an empty list clears them all — which is how * acquirer onboarding finishes, the new account ceasing to be a special case. * Re-send every other cap with its `unit`, `window` and `currency` as read: * an amount cap sent back without its `unit` is refused, because it carries a * currency and would otherwise be a payment count. * * Every account named must belong to this shop; one that does not is refused. * So is an amount cap without a `currency`, and a payment cap with one. * * `PUT /routing/connector-caps/{profileId}` */ setConnectorCaps(profileId: string, params: RoutingConnectorCaps): Promise; /** * List all routing algorithms for the current merchant. * * @returns The routing dictionary (records + currently active id). */ list(): Promise; /** * Connector names denied at routing for a shop (explicit denies plus * whitelist-implied exclusions). Read-only; surfaced in the routing builder. * * `GET /routing/connector-restrictions/{profileId}` */ connectorRestrictions(profileId: string): Promise; /** Get active routing config. `GET /routing/active` */ getActive(): Promise; /** Update default routing config. `POST /routing/default` */ updateDefault(params: Record): Promise; /** Retrieve default config for profiles. `GET /routing/default/profile` */ getDefaultProfile(): Promise; /** * Retrieve one profile's default config. `GET /routing/default/profile/{profileId}` * * The narrow counterpart of `getDefaultProfile()`: that one returns every * profile of the merchant and needs merchant-level routing read, so a * profile-scoped (shop) role could write its own default through * `updateDefaultProfile()` but never read it back. This read is gated on * profile-level routing read and pinned to the caller's own profile — a shop * role asking for a sibling profile is refused. */ getDefaultForProfile(profileId: string): Promise; /** Update default config for a profile. `POST /routing/default/profile/{profileId}` */ updateDefaultProfile(profileId: string, params: Record): Promise; /** List routing configs for profile. `GET /routing/list/profile` */ listForProfile(): Promise; /** Evaluate a routing rule. `POST /routing/rule/evaluate` */ evaluateRule(params: Record): Promise>; /** Migrate routing rules for profile. `POST /routing/rule/migrate` */ migrateRule(params: Record): Promise>; /** Evaluate routing for a payment. `POST /routing/evaluate` */ evaluate(params: Record): Promise>; /** Update gateway scores for dynamic routing. `POST /routing/feedback` */ feedback(params: Record): Promise>; } declare class RoutingDecisionManager { private readonly request; constructor(request: RequestFn); /** Upsert decision manager config. `PUT /routing/decision` */ upsert(params: Record): Promise>; /** Retrieve decision manager config. `GET /routing/decision` */ retrieve(): Promise>; /** Delete decision manager config. `DELETE /routing/decision` */ delete(): Promise>; /** Upsert surcharge decision config. `PUT /routing/decision/surcharge` */ upsertSurcharge(params: Record): Promise>; /** Retrieve surcharge decision config. `GET /routing/decision/surcharge` */ retrieveSurcharge(): Promise>; /** Delete surcharge decision config. `DELETE /routing/decision/surcharge` */ deleteSurcharge(): Promise>; } /** * Merchant-friendly surcharge rules: configure a per-payment-method surcharge * (fixed or %) added to the amount the buyer pays — without hand-writing the * Euclid DSL. Scope to a shop via `profile_id` (omit for merchant-wide). * * Distinct from the platform fee (a balance deduction): a surcharge changes what * the buyer is charged. */ declare class SurchargeRules { private readonly request; constructor(request: RequestFn); /** * Create or replace the surcharge program for a scope. * * `PUT /routing/surcharge/rules` * * @example * ```typescript * await delopay.routing.surchargeRules.upsert({ * surcharges: [ * { payment_method: 'crypto', surcharge: { rate: { percent: 1.0 } } }, * { payment_method: 'card', surcharge: { fixed: { amount: 35 } } }, * ], * }); * ``` */ upsert(params: SurchargeRuleRequest): Promise; /** * Retrieve the active surcharge program for a scope, or `null` when none is set. * * `GET /routing/surcharge/rules?profile_id={profileId}` * * @param profileId - Shop scope. Omit for the merchant-wide rule. */ retrieve(profileId?: string): Promise; /** * Deactivate the active surcharge program for a scope. * * `DELETE /routing/surcharge/rules?profile_id={profileId}` * * @param profileId - Shop scope. Omit for the merchant-wide rule. */ delete(profileId?: string): Promise; } /** * Checkout theme programs: which of a shop's stored appearance variants a buyer * is shown. * * Same engine and same wire format as the advanced routing rules above — a * Euclid program whose rules run top-down, first match wins, with * `defaultSelection` as the fallback — with the output swapped for a variant * name. That is deliberate: the dashboard's routing rule builder can author * these without learning a second condition language. * * **A theme program decides a look and nothing else.** It cannot express which * payment methods are offered, what is charged, which provider processes the * payment, or whether it succeeds. The allowed dimensions are fixed server-side * by the output type — see {@link CheckoutThemeDimension} — so that is a * property of the API rather than a convention. * * Naming a variant the shop has not defined is **not** an error: the checkout * falls back to the shop default, exactly as it does for an unknown `?theme=`, * because a buyer who cannot pay is worse than a buyer who sees the default * look. Such names come back in `warnings` instead, recomputed on every read. */ declare class CheckoutThemeRules { private readonly request; constructor(request: RequestFn); /** * Create or replace the theme program for a scope. * * `PUT /routing/checkout-theme/rules` * * Supersedes rather than overwrites: the previous active version is retired * and a new one stored, so the record of which look was live when survives. * Pass `active: false` to store a revision **without** retiring the live one — * that is where a program drafted against a variant you have not built yet * belongs. * * @example A phone in Germany gets the compact look; everyone else the house style. * ```typescript * await delopay.routing.checkoutThemeRules.upsert({ * name: 'Autumn targeting', * profile_id: 'pro_...', * algorithm: { * rules: [ * { * name: 'German phones', * connectorSelection: { theme: { variant: 'compact' } }, * statements: [ * { * condition: [ * { * lhs: 'device_class', * comparison: 'equal', * value: { type: 'enum_variant', value: 'phone' }, * metadata: {}, * }, * { * lhs: 'browser_language', * comparison: 'equal', * value: { type: 'enum_variant', value: 'de' }, * metadata: {}, * }, * ], * }, * ], * }, * ], * defaultSelection: { theme: { variant: 'house' } }, * metadata: {}, * }, * }); * ``` */ upsert(params: CheckoutThemeProgramRequest): Promise; /** * Retrieve the active theme program for a scope, or `null` when none is set. * * `GET /routing/checkout-theme/rules?profile_id={profileId}` * * @param profileId - Shop scope. Omit for the merchant-wide program. A * shop-scoped caller that omits it gets its own shop's program. */ retrieve(profileId?: string): Promise; /** * Deactivate the active theme program for a scope. Idempotent. * * `DELETE /routing/checkout-theme/rules?profile_id={profileId}` * * Deactivation, not deletion — the stored row is what says which look was * live when, and that history cannot be reconstructed after the fact. Shops * go back to their default appearance immediately. * * @param profileId - Shop scope. Omit for the merchant-wide program. */ delete(profileId?: string): Promise; } /** * Rendered-to-paid conversion, per appearance variant and segment. * * Answers the one question theme targeting exists for: *does variant B convert * better than the house style, on phones, in Germany?* * * **What a rate here means.** The denominator is backend-observed checkout page * opens, deduplicated by device within the hosted checkout's cache window, with * automated traffic excluded from both sides. It is narrower than "paints", and * that is deliberate - counting one buyer's refresh as a second render would * understate conversion. The exact basis travels with every response in * `denominator`, and the ways it is not the whole truth travel in `caveats`. * * **The server decides what may be concluded, not you.** Rate, Wilson interval, * per-cell `verdict` and the variant-vs-default `separates` test are computed * once, server-side, and shipped as values. Do not recompute them: the first * client that rounds differently tells a merchant a difference is real when the * server says it is not. */ declare class CheckoutThemeConversion { private readonly request; constructor(request: RequestFn); /** * Rendered-to-paid conversion for a shop over a window. * * `GET /routing/checkout-theme/conversion` * * Requires `CheckoutBranding` read at profile scope - the same permission * that governs seeing how a checkout looks. * * @param params - Window (RFC3339, `start` inclusive / `end` exclusive), the * shop, and the dimension to group by. * * @example Which variant wins on which device, over the last 30 days. * ```typescript * const report = await delopay.routing.checkoutThemeConversion.retrieve({ * profile_id: 'pro_...', * start: '2026-07-21T00:00:00Z', * end: '2026-08-20T00:00:00Z', * segment: 'device', * }); * * for (const c of report.comparisons) { * if (!c.separates) continue; // "not shown to differ" - say nothing * console.log(`${c.variant} on ${c.segment}: ${c.higher} converts better`); * } * ``` */ retrieve(params: CheckoutThemeConversionQuery): Promise; } /** Index discriminator returned for each search result group. */ type SearchIndex = 'payment_attempts' | 'payment_intents' | 'refunds' | 'disputes' | 'payouts' | 'sessionizer_payment_attempts' | 'sessionizer_payment_intents' | 'sessionizer_refunds' | 'sessionizer_disputes' | 'routing_rules' | 'webhook_events' | 'audit_logs' | 'subscriptions'; type SearchStatus = 'Success' | 'Failure'; /** One result group (per index) in the response array. */ interface SearchGroupResponse { count: number; index: SearchIndex; hits: Record[]; status: SearchStatus; } /** * The window a global search covers. The documented wire fields are * `start_time` (required) and `end_time` (optional — omit it for "up to * now"); the server also accepts the camelCase spellings as aliases, which * earlier SDK versions sent, so both are declared and either compiles. * Prefer the snake_case pair: it is what the operation documents. */ type SearchTimeRange = { start_time: string; end_time?: string | null; } /** @deprecated Use `start_time` / `end_time` — the documented wire names. */ | { startTime: string; endTime?: string | null; }; interface GlobalSearchRequest { query: string; filters?: Record; /** Naive ISO 8601 bounds; `end_time` may be omitted. */ timeRange?: SearchTimeRange; /** Wire alias for `timeRange` — the server reads either. */ time_range?: SearchTimeRange; } /** Global cross-index search. */ declare class Search { private readonly request; constructor(request: RequestFn); /** * Search every supported index for `query`. * `POST /analytics/search` * * @example * ```typescript * const groups = await delopay.search.global({ query: 'pay_abc' }); * for (const g of groups) console.log(g.index, g.count); * ``` */ global(params: GlobalSearchRequest, options?: { signal?: AbortSignal; }): Promise; } /** Manage gateway connections for a specific shop. */ declare class ShopGateways { private readonly request; constructor(request: RequestFn); /** * Connect a payment gateway to a shop. * * @param merchantId - The merchant account ID. * @param shopId - The shop (business profile) ID. * @param params - Gateway connector credentials and configuration. * @returns The created gateway connection. */ connect(merchantId: string, shopId: string, params: GatewayConnectRequest): Promise; /** * List all gateway connections for a shop. * * @param merchantId - The merchant account ID. * @param shopId - The shop ID. * @returns Array of gateway connections. */ list(merchantId: string, shopId: string): Promise; /** * Disconnect a gateway from a shop. * * @param merchantId - The merchant account ID. * @param shopId - The shop ID. * @param gatewayId - The gateway connector ID to remove. * @returns The removed gateway connection. */ disconnect(merchantId: string, shopId: string, gatewayId: string): Promise; } /** * Create and manage shops (business profiles) within a merchant account. * * Each shop can have its own gateway connections, routing rules, and fee schedules. */ declare class Shops { private readonly request; /** Gateway connection management for shops. */ readonly gateways: ShopGateways; constructor(request: RequestFn); /** * Create a new shop under a merchant account. * * @param merchantId - The merchant account ID. * @param params - Shop creation parameters (name, etc.). * @returns The created shop. * * @example * ```typescript * const shop = await delopay.shops.create('merch_123', { shop_name: 'EU Store' }); * ``` */ create(merchantId: string, params: ShopCreateRequest): Promise; /** * Retrieve a shop by its ID. * * @param merchantId - The merchant account ID. * @param shopId - The shop ID. * @returns The shop. */ retrieve(merchantId: string, shopId: string): Promise; /** * Update a shop's configuration. * * @param merchantId - The merchant account ID. * @param shopId - The shop ID to update. * @param params - Fields to update. * @returns The updated shop. */ update(merchantId: string, shopId: string, params: ShopUpdateRequest): Promise; /** * Delete a shop. * * @param merchantId - The merchant account ID. * @param shopId - The shop ID to delete. * @returns The deleted shop object. */ delete(merchantId: string, shopId: string): Promise; /** * List all shops under a merchant account. * * @param merchantId - The merchant account ID. * @returns Array of shops. */ list(merchantId: string): Promise; /** * Successful-order count and revenue for one shop. * * Unlike `projects.stats()` this needs only `ProfileAccountRead`, so a * shop-scoped user can load it for their own shop; merchant-level users can * load any shop of their merchant. * * Revenue comes back FX-converted as `revenue_usd` (USD major units) plus a * `revenue_by_currency` breakdown. The legacy `revenue` field is a raw * cross-currency minor-unit sum and should not be displayed. * * @param merchantId - The merchant account ID. * @param shopId - The shop (business profile) ID. * @param period - Window in days, or `'all'` for an all-time total. * Omitted means the server default of 30 days. * @returns The shop's stats over the requested window. * * @example * ```typescript * const stats = await delopay.shops.stats('merch_123', 'pro_1', 'all'); * console.log(stats.orders, stats.revenue_usd); * ``` */ stats(merchantId: string, shopId: string, period?: StatsPeriod): Promise; /** * Upload a logo file for a shop. The file is stored in Delopay's configured * object store and a public HTTPS URL is returned. This method does NOT write * the URL into the shop's `payment_link_config.logo` — call * `shops.update` afterwards with the returned `logo_url` to persist the change. * * Accepts PNG, JPEG, WebP or SVG. The file must be ≤ 1 MiB. * * @param merchantId - The merchant account ID. * @param shopId - The shop (business profile) ID. * @param file - The logo file (Blob / File in browsers). * @returns The publicly-reachable URL of the uploaded logo. * * @example * ```typescript * const { logo_url } = await delopay.shops.uploadLogo('merch_1', 'pro_1', file); * await delopay.shops.update('merch_1', 'pro_1', { * payment_link_config: { logo: logo_url }, * }); * ``` */ uploadLogo(merchantId: string, shopId: string, file: Blob): Promise; /** * Update only the checkout appearance (the `payment_link_config` blob: * theme, logo, colours, seller name, SDK layout/rules, DeloPay-branding * toggle) of a shop. Applied as a whole-object replace of * `payment_link_config`, mirroring the shop-update semantics. * * Gated on the dedicated `CheckoutBranding` permission, so "may restyle * the checkout" can be granted without full account/shop write. * * `POST /shops/{merchantId}/{shopId}/checkout-branding` * * @param merchantId - The merchant account ID. * @param shopId - The shop (business profile) ID to restyle. * @param params - The new `payment_link_config` blob (full replacement). * @returns The saved branding — the same shape `retrieveCheckoutBranding` * returns, not the whole shop. A role whose only power is restyling a * checkout must not receive the webhook signing key or the card-vault * configuration back from a save. */ updateCheckoutBranding(merchantId: string, shopId: string, params: CheckoutBrandingUpdate, options?: RequestExtras): Promise; /** * Read only the checkout appearance of a shop, so the role that may restyle a * checkout can load the checkout it may restyle. * * Gated on `ProfileCheckoutBrandingRead` — the read twin of the * `ProfileCheckoutBrandingEdit` guard on `updateCheckoutBranding` above. * Prefer this over `shops.retrieve` for the branding editor: `retrieve` * returns the whole profile, including the webhook signing key and the * card-vault configuration, and needs the shop-read permission for exactly * that reason. * * `GET /shops/{merchantId}/{shopId}/checkout-branding` * * @param merchantId - The merchant account ID. * @param shopId - The shop (business profile) ID whose branding to read. * @returns The shop's id, name and stored `payment_link_config`. That config * is `null` when the shop has never been styled — the untouched default, not * an error, and distinct from a stored-but-empty style. */ retrieveCheckoutBranding(merchantId: string, shopId: string, options?: RequestExtras): Promise; } declare class StripeConnect { private readonly request; constructor(request: RequestFn); createAccount(params: StripeConnectAccountRequest): Promise; createAccountLink(params: StripeConnectLinkRequest): Promise; /** Get onboarding action URL. `POST /connector-onboarding/action-url` */ getActionUrl(params: Record): Promise>; /** Sync onboarding status. `POST /connector-onboarding/sync` */ syncOnboarding(params: Record): Promise>; /** Reset tracking ID. `POST /connector-onboarding/reset-tracking-id` */ resetTrackingId(params: Record): Promise>; } declare class ThreeDsRules { private readonly request; constructor(request: RequestFn); execute(params: ThreeDsRuleExecuteRequest): Promise; } declare class Users { private readonly request; constructor(request: RequestFn); signUp(params: SignUpRequest | SignUpWithMerchantRequest): Promise; signIn(params: SignInRequest): Promise; signOut(): Promise>; /** * Sliding-session refresh: exchange the current (still-valid) login JWT * for a fresh one with the same claims and a full lifetime. The backend * keeps the session's identity (`jti`), slides `user_session.expires_at` * forward and re-sets the `login_token` cookie. * * Requires a token backed by a revocable session (a `jti` claim). Signin * and switch-merchant/-profile tokens have one; **session-less tokens do * not and are rejected with 400** — team-impersonation tokens are the * case in practice, and they are deliberately tab-scoped and * time-bounded rather than renewable. A 400 here is not a dead session: * the token remains valid for ordinary calls, it simply cannot slide. * * Rejected (401) for expired, blacklisted or revoked tokens — refresh can * only extend a session that is still alive. Rate-limited server-side * (429) to one mint per session per minute; treat a 429 as "still fresh * enough", not as an error. * * The returned token is NOT applied to this client automatically — pass * it to `setJwtToken()`, or use {@link Delopay.refreshSession} which does * both. * * `POST /user/token/refresh`. Requires a logged-in JWT. */ refreshToken(): Promise; /** * Paginated login history for the authenticated user -- IP, User-Agent, * country / city / lat-lon (when GeoIP is enabled), success and failure * events with their reasons. Strictly scoped to the JWT subject; a user * can only see their own activity. * * `GET /user/me/login-activity`. Requires a logged-in JWT. * * Returns an empty page when a Delopay admin is impersonating a merchant, * so the admin's metadata is not exposed inside the merchant dashboard. */ listLoginActivity(params?: LoginHistoryParams): Promise; /** * List the authenticated user's currently-active dashboard sessions * (one row per minted login JWT that hasn't been revoked or expired). * * The row matching the JWT making this call has `is_current: true`, * which is what lets the dashboard render a "This device" tag. * * `GET /user/me/sessions`. Requires a logged-in JWT. Returns an empty * list when a Delopay admin is impersonating a non-admin merchant * (same guard as `listLoginActivity`). */ listActiveSessions(): Promise; /** * Disconnect one of the authenticated user's sessions. The matching * JWT is rejected on its next request — fast-path via Redis, fall back * to the persistent `revoked_at` column. * * Idempotent: revoking an already-revoked or unknown id returns 404, * which the caller can treat as success for retry purposes. Revoking * a session id that belongs to a different user also returns 404 — * the response intentionally doesn't leak whether the id exists. * * `POST /user/me/sessions/{sessionId}/revoke`. */ revokeSession(sessionId: string): Promise; getDetails(): Promise; update(params: UpdateUserDetailsRequest): Promise; /** * Read a preference owned by the logged-in user. Missing keys return value null. * Works in both dashboards with a login JWT; API keys and impersonation are rejected. * Keys contain 1–128 ASCII letters, digits, dots, underscores or hyphens. * Unknown widget IDs and their order are preserved; consumers choose what to render. */ getPreference(key: string): Promise; /** * Save one own preference: omitted/false if_absent replaces it (last write wins); * true preserves an existing object and returns the winner. The JSON object is * limited to 16 KiB of compact UTF-8 JSON. For layouts, use one namespaced key * per page (e.g. merchant.analytics.payments) and ordered widget IDs per region, * without titles, fetched metrics or rendered state. Requires a login JWT. */ setPreference(key: string, params: SetUserPreferenceRequest): Promise; /** * Initialize only a missing preference. Existing objects, including a client * reset marker saved with setPreference (for example { reset: true }), win. Render/cache the returned value, which may differ from * the proposed value. Safe for migration retries after a lost response. * Explicit edits and reset markers use setPreference with no if_absent flag. * DELETE removes the key; it leaves no marker and permits initialization again. * Requires a login JWT and the same key/object limits as setPreference. */ initializePreference(key: string, value: Record): Promise; /** * Remove an own preference. Idempotent; returns value null, without storing a * tombstone. Later initialization may import a value again. To suppress legacy * migration after a reset, save a client reset-marker object with setPreference. * Login JWT only. */ deletePreference(key: string): Promise; /** * RFC 7396 merge-patch the caller's own user-scoped metadata bucket. * Returns the full user details, so callers can refresh their context * without a second fetch. * * `PATCH /user/metadata` */ updateMetadata(params: UpdateMetadataRequest): Promise; /** * RFC 7396 merge-patch the merchant-scoped metadata bucket shared by * every dashboard user of the merchant. Same response contract as * {@link Users.updateMetadata}. * * `PATCH /user/merchant/metadata` */ updateMerchantMetadata(params: UpdateMetadataRequest): Promise; /** * Permanently delete the caller's account. Requires a fresh password * (and a current 6-digit TOTP code if the user has TOTP enrolled). On * success all role assignments are removed, the user record is * deactivated, and all in-flight sessions are invalidated. The caller * should clear local credentials and route to the login page. * * Returns `InvalidDeleteOperation` when the caller is the sole * owner-level admin of an org / merchant / profile -- they must * transfer ownership first. */ deleteAccount(params: DeleteAccountRequest): Promise>; changePassword(params: ChangePasswordRequest): Promise; rotatePassword(params: ResetPasswordRequest): Promise; forgotPassword(params: ForgotPasswordRequest): Promise>; /** * Commit a password reset. * * The caller is responsible for obtaining a `SinglePurposeToken` with * `purpose: reset_password` via the email-token exchange + TOTP flow * (see `fromEmail`, `beginTotp`, `updateTotp`/`verifyTotp`, * `generateRecoveryCodes`, `terminate2fa`) and setting it on the client * via `setJwtToken` before calling this method. `body.token` must still * be the original `EmailToken` from the reset-link URL — the handler * decodes it a second time to find the user. */ resetPassword(params: ResetPasswordRequest): Promise>; /** * Exchange an email-link token (`EmailToken`) for a single-purpose JWT * that drives the next step of the flow (TOTP, verify email, accept * invitation, etc.). No authentication required. * * The `token_type` in the response tells you which step to run next. */ fromEmail(params: FromEmailRequest): Promise; verifyEmail(params: Record): Promise; sendVerificationEmail(params: ForgotPasswordRequest): Promise>; createMerchant(params: Record): Promise; switchMerchant(params: SwitchMerchantRequest): Promise; switchProfile(params: SwitchProfileRequest): Promise; listMerchants(): Promise[]>; listProfiles(): Promise[]>; inviteUsers(params: InviteUsersRequest[]): Promise; /** * Add a team member directly, without sending an invite email. * `POST /user/employees/add` * * Unlike `inviteUsers`, the account is active immediately and you hand over * the credentials yourself. Omit `password` to have the server generate one * and return it once in `password` on the response; supply your own and it is * not echoed back. Either way the member must change it on first sign-in. * * Same role rules as invite: you cannot grant a role above your own, and a * shop-scoped caller can only target their own shop. */ addUser(params: AddUserRequest): Promise; /** * Impersonate one of your own team members — `POST /user/employees/impersonate`. * * Mints a session token **as** the given member, so the dashboard renders * exactly what they see (useful for support and role verification). The * caller needs the *Impersonation* permission, and the member's role must * rank **strictly below** the caller's (`Profile < Merchant < Organization`); * the server rejects self-impersonation, cross-merchant targets, and * equal/higher roles. * * The returned token is tab-scoped by design: open it in a fresh tab (e.g. * `/auth/impersonate?token=…`) rather than replacing the caller's own * session. No auth cookie is set on the response. */ impersonateEmployee(params: ImpersonateEmployeeRequest): Promise; acceptInvitation(params: Record): Promise; /** * Accept invitations before a session exists. * * Sign-in issues a `token_type: "accept_invite"` step token instead of a * session when the caller has no active role yet — every role is still an * unaccepted invitation. With that token set via `setJwtToken`, pass the * entities from `listInvitations` here; the response is the next step of * the sign-in flow (`user_info` for the session, or `force_set_password` * first). Rejected when none of the entities carries a pending invitation. */ acceptInvitationsPreAuth(params: AcceptInvitationsPreAuthRequest): Promise; /** * Accept an invitation via the email-link flow. * * Caller must already hold a `SinglePurposeToken` with * `purpose: accept_invitation_from_email` (obtained via `fromEmail` + any * required TOTP step) and have set it on the client via `setJwtToken`. * `body.token` must still be the original `EmailToken` from the * invite-link URL — the handler decodes it a second time to find the * invitee and the entity lineage. */ acceptInviteFromEmail(params: FromEmailRequest): Promise; /** * Start TOTP setup (or no-op if already set). * * Returns the QR-code payload when the user has no TOTP configured yet; * returns `{ secret: null }` when the user is already set up (caller * should then prompt for a 6-digit code and call `verifyTotp`). * * Requires `Authorization: Bearer `. */ beginTotp(): Promise; /** * Verify a 6-digit TOTP code for a user whose TOTP is already set up. * Marks the code as used in Redis so subsequent flow steps can advance. * * Requires `Authorization: Bearer `. */ verifyTotp(params: VerifyTotpRequest): Promise>; resetTotp(): Promise>; generateRecoveryCodes(): Promise; verifyRecoveryCode(params: Record): Promise; sendPhoneOtp(params: PhoneOtpRequest): Promise; verifyPhoneOtp(params: PhoneOtpVerifyRequest): Promise; /** * List all roles visible to the caller (predefined + custom). * * `GET /user/role/list`. With `groups: true` the response is the * parent-groups shape: `[{role_id, role_name, entity_type, role_scope, * parent_groups: [{name, description, scopes}]}]`; without it, the * deprecated flat `groups` shape. */ listRoles(params?: { groups?: boolean; entity_type?: string; }): Promise[]>; listUserRoles(params?: Record): Promise[]>; /** * Change a team member's role. `POST /user/employees/update-role` * * Pass `profile_id` to name the shop when managing a shop's team as a * merchant-scoped admin — see {@link UpdateUserRoleRequest.profile_id}. */ updateUserRole(params: UpdateUserRoleRequest): Promise>; /** * Remove a team member. `DELETE /user/employees/delete` * * Pass `profile_id` to name the shop when managing a shop's team as a * merchant-scoped admin — see {@link DeleteUserRoleRequest.profile_id}. */ deleteUserRole(params: DeleteUserRoleRequest): Promise>; /** Sign in via OIDC. `POST /user/oidc` */ signInOidc(params: Record): Promise; /** Transfer key. `POST /user/key/transfer` */ transferKey(params: Record): Promise>; /** * Invitations still waiting on the caller. `GET /user/list/invitation` * * Accepts an `accept_invite` step token as well as a session JWT, so it * can feed `acceptInvitationsPreAuth` during sign-in. */ listInvitations(): Promise; /** Check 2FA status. `GET /user/2fa` */ check2faStatus(): Promise>; /** * Finish first-time TOTP setup: commit the secret generated by `beginTotp` * against a 6-digit code from the user's authenticator app. * * `PUT /user/2fa/totp/verify`. Requires `Authorization: Bearer `. */ updateTotp(params: VerifyTotpRequest): Promise>; /** * Complete the TOTP step and advance to the next flow stage (e.g. * `reset_password`). Returns a fresh single-purpose token with the * next `token_type`. * * `GET /user/2fa/terminate`. Requires `Authorization: Bearer `. */ terminate2fa(query?: Terminate2faQueryParams): Promise; /** Create auth method. `POST /user/auth` */ createAuthMethod(params: Record): Promise>; /** Update auth method. `PUT /user/auth` */ updateAuthMethod(params: Record): Promise>; /** List auth methods. `GET /user/auth/list` */ listAuthMethods(): Promise[]>; /** Get auth URL. `GET /user/auth/url` */ getAuthUrl(): Promise>; /** Select auth method. `POST /user/auth/select` */ selectAuth(params: Record): Promise>; /** * List users in lineage. * * Needs the Users *view* grant now — the response carries colleagues' email * addresses, so a role without it is refused rather than handed a roster. * A shop-scoped role keeps reading its own shop's members. * * `GET /user/employees/list` */ listUsersInLineage(params?: ListUsersInLineageParams): Promise; /** Resend invite. `POST /user/resend-invite` */ resendInvite(params: Record): Promise>; /** * Get the caller's parent permission groups + scopes. * * `GET /user/role` */ getRolePermissions(): Promise; /** * List invitable roles. `GET /user/role/list/invite` * * @param params - Optional query. `entity_type` scopes the role list to a * particular entity (e.g. `'merchant'` to list only merchant-scoped roles * when inviting employees from the merchant dashboard). */ listInvitableRoles(params?: ListInvitableRolesParams): Promise[]>; /** List updatable roles. `GET /user/role/list/update` */ listUpdatableRoles(): Promise[]>; /** Get parent list. `GET /user/parent/list` */ getParentList(): Promise[]>; /** Create a role. `POST /user/role` */ createRole(params: Record): Promise>; /** Get role by ID. `GET /user/role/{roleId}` */ getRoleById(roleId: string): Promise>; /** Update role by ID. `PUT /user/role/{roleId}` */ updateRole(roleId: string, params: Record): Promise>; /** * Delete a custom role. Predefined roles and roles still assigned to * team members are rejected by the backend with a 400. * * `DELETE /user/role/{roleId}` */ deleteRole(roleId: string): Promise>; /** * Read which individual connector accounts a role may see. * * `GET /user/role/{roleId}/connectors` * * **Check `restricted` before reading `connectors`.** An empty list is * ambiguous by itself, so the backend states which case it is: `false` means * the role holds no grant and sees whatever its entity and profile scope * already allowed. Rendering an empty `connectors` array as "this role sees * nothing" inverts the meaning. * * Requires the permission that *edits a role*, not a connector permission. */ getRoleConnectors(roleId: string): Promise; /** * Replace the set of connector accounts a role may see. * * `PUT /user/role/{roleId}/connectors` * * The call **replaces** the whole set rather than adding to it, so send the * complete list every time. An empty `merchant_connector_ids` clears the * grant and returns the role to unrestricted. * * The backend refuses an id that is not a connector account of the caller's * own merchant, an `Organization`-scoped role (a connector account belongs to * exactly one merchant, so an org-spanning role cannot hold one coherently), * and any predefined role (one static entry shared by every tenant). * * Editing a grant invalidates the role cache and blacklists tokens minted * before the edit, so **users holding this role must sign in again**. Worth * saying in the UI before the save, not after. */ updateRoleConnectors(roleId: string, params: UpdateRoleConnectorGrantParams): Promise; } declare class Verification { private readonly request; constructor(request: RequestFn); registerApplePayDomains(merchantId: string, params: ApplePayVerificationRequest): Promise; getApplePayVerifiedDomains(params: Record): Promise; } declare class Analytics { private readonly request; constructor(request: RequestFn); /** * Scoped, drill-level analytics dashboard for the authenticated merchant * (the same engine as the admin portal, pinned server-side to your own * merchant). The server ignores `merchant_id` — it always scopes to your * merchant, and to your single shop for profile-scoped users — so pass only * `project_id` / `shop_id` to drill and the window / `sections` fields. * Returns one drill level: the scope's daily series + previous window, * processor mix and direct children. `GET /analytics/scope` */ scope(params?: AnalyticsScopeRequest): Promise; /** * Device analytics over the canonical client-context observation per * payment (browser/platform families, device classes and models, checkout * channel mix, time-to-pay), pinned server-side to your own merchant and * drillable via `project_id` / `shop_id` exactly like `scope`. Gated on the * client-context optimisation-use switch: when it is off the server answers * 200 with `enabled: false` and a caveat naming the switch. * `GET /analytics/devices` */ devices(params?: ClientAnalyticsRequest): Promise; /** * Geo analytics over the canonical client-context observation per payment: * country totals, city bubbles (IP mode), buyer languages, buyer-local * purchase hours and the IP-vs-billing mismatch share. `mode` selects the * location claim (`ip` default, `billing`); the two are never coalesced. * Same drill, window and gating contract as `devices`. * `GET /analytics/geo` */ geo(params?: ClientAnalyticsRequest): Promise; /** * The recent payments behind one clicked geo target: a map country (in the * active claim mode), optionally narrowed to an IP-resolved city, or one * buyer-local heatmap cell (`dow` + `hour`, paid sessions only). Same * window/scope/filter and gating contract as `geo`; capped at 50 rows, * newest first, with the full match count alongside. * `GET /analytics/geo/transactions` */ geoTransactions(params: GeoDrillRequest): Promise; /** * The recent payments behind one clicked device target: exactly one of a * browser family, a platform family, an identified device-model label, or * a device class. Family/model targets are resolved server-side with the * same classifiers the cards use. Same window/scope/filter and gating * contract as `devices`; 50 rows per page (`offset` for the next page), * newest first, with the full match count alongside. * `GET /analytics/devices/transactions` */ deviceTransactions(params: DeviceDrillRequest): Promise; /** * The payments behind one clicked element of the payments dashboard: a * processor-donut slice, a payment-method slice, an outcome segment of a * series bar, a series bucket, or a breakdown row — combinable, with * `target_bucket` narrowing every other target. The outcome mapping is the * one the series and donuts are folded with, expanded server-side, so the * list is what the clicked number was made of. Rows and the whole-match * `summary` carry USD figures converted with the dashboard's own rates. * Same window/scope/chip contract as `scope`; sorted per `sort_on` / * `sort_by`, `limit` rows (default 50) per page, `offset` for the next. * `GET /analytics/scope/transactions` */ scopeTransactions(params: ScopeDrillRequest): Promise; /** * Subscription analytics over `subscription` and `invoice`: estimated * recurring volume, the invoice funnel, movement (new / expansion / * contraction / churn), both processor axes, plan mix and the breakdown one * level below the scope. Pinned server-side to your own merchant and * drillable via `project_id` / `shop_id` exactly like `scope`. * * Half the figures are **stocks** — a snapshot at the window's end rather * than a sum over it — so `est_monthly_volume_usd` and `active` can match * across a 7-day and a 30-day window while `billed_volume_usd` does not. * Day granularity only. `GET /analytics/subscriptions` */ subscriptions(params?: SubscriptionAnalyticsRequest): Promise; /** * The billing cycles behind one clicked element of the subscription * dashboard: an invoice outcome, a processor slice on either axis, a plan * row, a subscription status, a movement component, a series bucket or a * breakdown row. Same window/scope/filter contract as `subscriptions`; 50 * rows per page (`offset` for the next), newest first, with the full match * count alongside. * * A cycle that never reached a payment is listed too — that is what "still * unpaid" means — and carries its invoice id as `payment_id` with * `invoice_id` set to the same value, so you can always tell which you got. * `GET /analytics/subscriptions/list` */ subscriptionsList(params: SubscriptionDrillRequest): Promise; /** * Every analytics exclusion of the merchant, with the payments each one * matched in the lookback window. * * These rules change what the merchant's own dashboards count. Financial * records and transaction lists are never filtered by them. * * `matched_payments` counts overlap between rules: a payment matched by * three rules is counted once by each, so the values are not additive. The * envelope's `best_effort` and `lookback_days` apply to every row — matching * runs on client signals that may be absent or expired, within that window. * `GET /analytics/exclusions` */ listExclusions(): Promise; /** * Write a new exclusion. A shop-scoped user must name their own shop in * `profile_id`; omitting it applies the rule to every shop of the merchant. * Omitting `surfaces` means **all five**, not none. * `POST /analytics/exclusions` */ createExclusion(rule: AnalyticsExclusionRequest): Promise; /** * Replace a stored exclusion **completely**. This is not a patch: every * field you leave out reverts to its default rather than keeping its stored * value, so send the whole rule — including an explicit `null` for a * `profile_id`, `value`, `valid_from`, `valid_until` or `note` you mean to * clear, and the full `surfaces` list you mean to keep. * `PUT /analytics/exclusions/{id}` */ updateExclusion(id: string, rule: AnalyticsExclusionRequest): Promise; /** * Delete a stored exclusion. Returns the rule as it was, so a caller can * say what it removed. `DELETE /analytics/exclusions/{id}` */ deleteExclusion(id: string): Promise; /** Global search. `POST /analytics/search` */ search(params: Record): Promise>; /** Domain-specific search. `POST /analytics/search/{domain}` */ searchDomain(domain: string, params: Record): Promise>; /** Get analytics info. `GET /analytics/{domain}/info` */ getInfo(domain: string): Promise>; /** Get API event logs. `GET /analytics/api-event-logs` */ apiEventLogs(params?: Record): Promise>; /** Get SDK event logs. `POST /analytics/sdk-event-logs` */ sdkEventLogs(params: Record): Promise>; /** Get connector event logs. `GET /analytics/connector-event-logs` */ connectorEventLogs(params?: Record): Promise>; /** Get routing event logs. `GET /analytics/routing-event-logs` */ routingEventLogs(params?: Record): Promise>; /** Get outgoing webhook event logs. `GET /analytics/outgoing-webhook-event-logs` */ outgoingWebhookEventLogs(params?: Record): Promise>; } declare class AnalyticsDashboard { private readonly request; constructor(request: RequestFn); /** Get analytics dashboard data. `GET /analytics-dashboard` */ retrieve(params?: Record): Promise>; /** Generate analytics dashboard report. `POST /analytics-dashboard` */ generate(params: Record): Promise>; } declare class Cards { private readonly request; constructor(request: RequestFn); /** Create a card. `POST /cards/create` */ create(params: Record): Promise>; /** Update a card. `POST /cards/update` */ update(params: Record): Promise>; /** Retrieve card info by BIN. `GET /cards/{bin}` */ retrieve(bin: string): Promise>; } /** * Emit a list as a file. * * Every method here mirrors one list endpoint and takes **that list's own * filters**, unchanged — there is no second filter vocabulary for exports. The * shop scope is resolved the same way too, so an export cannot show a shop its * list would not: a shop-scoped token exports its own shop, and naming another * is refused rather than ignored. * * Every export is **one read**, not a paged walk, and is bounded at 25,000 * rows (500 for `pdf`, which is a document rather than a data feed). Over the * bound the request is refused with `DE_07 export_row_limit_exceeded` carrying * `{entity, max_rows}` — a truncated finance file that reports success is the * thing these endpoints exist not to produce. When too many exports are * already running the server answers `429` `IR_52` (`export_capacity_busy`); * a `GET` export is retried after its `Retry-After` like any other `429`. * * `csv` and `json` are **streamed**: the server counts the match set, sends * that count in `X-Delopay-Record-Count` before the first byte, and writes the * rows as it reads them. Each method holds the finished body to that count and * rejects with `EXPORT_INCOMPLETE` rather than resolve to a file missing rows — * see {@link ExportTransferOptions}, which also carries the download's * `onProgress`, `signal` and (idle) `timeout`. `pdf` stays a buffered body. * * `format` selects an `Accept` header, never a request field: an export reuses * its list's filter model as literally the same type, and several of those are * `deny_unknown_fields`, so a `format` key would be a 400. It defaults to * `json`, which resolves to a typed {@link ExportEnvelope}; `csv` and `pdf` * resolve to a `Blob`. */ declare class Export { private readonly request; constructor(request: RequestFn); /** * Transactions. `POST /export/transactions` * * The payments `payments.listByFilter` would list for the same body, over the * whole match set: same filters, same order, same scope. `limit` and * `offset` are ignored — the page is what an export does away with. No time * window is required; the row bound refuses an export that matches too much. */ transactions(params?: PaymentListFilterConstraints, options?: JsonExportOptions): Promise>; transactions(params: PaymentListFilterConstraints | undefined, options: BinaryExportOptions): Promise; transactions(params: PaymentListFilterConstraints | undefined, options: ExportOptions): Promise | Blob>; /** * The caller's own shop's transactions. * `POST /export/profile/transactions` * * The shop-scoped twin of {@link Export.transactions}, taking the body * `payments.listByProfileFilter` sends and scoped exactly as that list is: to * the shop on the token. Gated on the profile-level payment permission, which * a shop-scoped role can hold where the merchant-level one cannot. Its rows * carry no `connector_settlement_status`, which a shop viewer is not shown. */ transactionsForProfile(params?: ProfilePaymentListFilterConstraints, options?: JsonExportOptions): Promise>; transactionsForProfile(params: ProfilePaymentListFilterConstraints | undefined, options: BinaryExportOptions): Promise; transactionsForProfile(params: ProfilePaymentListFilterConstraints | undefined, options: ExportOptions): Promise | Blob>; /** Refunds. `POST /refunds/list/export` */ refunds(params?: RefundListParams, options?: JsonExportOptions): Promise>; refunds(params: RefundListParams | undefined, options: BinaryExportOptions): Promise; refunds(params: RefundListParams | undefined, options: ExportOptions): Promise | Blob>; /** The caller's own shop's refunds. `POST /refunds/profile/list/export` */ refundsForProfile(params?: RefundListParams, options?: JsonExportOptions): Promise>; refundsForProfile(params: RefundListParams | undefined, options: BinaryExportOptions): Promise; refundsForProfile(params: RefundListParams | undefined, options: ExportOptions): Promise | Blob>; /** Disputes. `GET /disputes/list/export` */ disputes(params?: DisputeListParams, options?: JsonExportOptions): Promise>; disputes(params: DisputeListParams | undefined, options: BinaryExportOptions): Promise; disputes(params: DisputeListParams | undefined, options: ExportOptions): Promise | Blob>; /** The caller's own shop's disputes. `GET /disputes/profile/list/export` */ disputesForProfile(params?: DisputeListParams, options?: JsonExportOptions): Promise>; disputesForProfile(params: DisputeListParams | undefined, options: BinaryExportOptions): Promise; disputesForProfile(params: DisputeListParams | undefined, options: ExportOptions): Promise | Blob>; /** Payouts. `POST /payouts/list/export` */ payouts(params: PayoutListParams, options?: JsonExportOptions): Promise>; payouts(params: PayoutListParams, options: BinaryExportOptions): Promise; payouts(params: PayoutListParams, options: ExportOptions): Promise | Blob>; /** The caller's own shop's payouts. `POST /payouts/profile/list/export` */ payoutsForProfile(params: PayoutListParams, options?: JsonExportOptions): Promise>; payoutsForProfile(params: PayoutListParams, options: BinaryExportOptions): Promise; payoutsForProfile(params: PayoutListParams, options: ExportOptions): Promise | Blob>; /** * Subscriptions. `GET /subscriptions/list/export` * * `profileId` becomes the `X-Profile-Id` header, exactly as on the list. A * shop-scoped token exports its own shop whatever this says. */ subscriptions(profileId: string, params?: SubscriptionListParams, options?: JsonExportOptions): Promise>; subscriptions(profileId: string, params: SubscriptionListParams | undefined, options: BinaryExportOptions): Promise; subscriptions(profileId: string, params: SubscriptionListParams | undefined, options: ExportOptions): Promise | Blob>; /** Settlement statements. `GET /settlement/statements/export` */ /** One period's settlement lines. `GET /settlement/lines/export` */ /** * The merchant's own audit log. `GET /audit/export` * * Requires a dashboard JWT whose role holds the merchant-level `AuditLog` * permission — a shop-scoped role cannot hold it. There is no `merchant_id` * parameter: the scope comes from the token. */ auditLog(params?: MerchantAuditLogListParams, options?: JsonExportOptions): Promise>; auditLog(params: MerchantAuditLogListParams | undefined, options: BinaryExportOptions): Promise; auditLog(params: MerchantAuditLogListParams | undefined, options: ExportOptions): Promise | Blob>; } /** * What each connector can do: payment methods, capture methods, webhook * flows, and whether an unverified webhook is acted on. */ declare class FeatureMatrix { private readonly request; constructor(request: RequestFn); /** Retrieve the feature matrix. `GET /feature-matrix` */ retrieve(): Promise; /** * Retrieve the feature matrix scoped to a merchant. Beta connectors * are filtered against the merchant's allowlist so the dashboard only * surfaces connectors the merchant can actually attach. * `GET /feature-matrix/{merchantId}` */ retrieveForMerchant(merchantId: string): Promise; } declare class Files { private readonly request; constructor(request: RequestFn); /** Upload a file. `POST /files` */ create(params: Record): Promise>; /** Retrieve/download a file. `GET /files/{fileId}` */ retrieve(fileId: string): Promise>; /** Delete a file. `DELETE /files/{fileId}` */ delete(fileId: string): Promise>; } declare class Forex { private readonly request; constructor(request: RequestFn); /** Retrieve forex rates. `GET /forex/rates` */ getRates(params?: Record): Promise>; /** Convert from minor currency. `GET /forex/convert-from-minor` */ convertFromMinor(params: Record): Promise>; } /** * Custom regions (named country groups) scoped to a shop/profile, used by the * geo-aware payment-method availability overrides. Every endpoint is scoped by * `profileId`. Admin (`sk_*`) access required. */ declare class Regions { private readonly request; constructor(request: RequestFn); /** Create a region. `POST /regions?profile_id=` */ create(profileId: string, params: RegionCreateRequest): Promise; /** List all regions for a profile. `GET /regions/list?profile_id=` */ list(profileId: string): Promise; /** List the built-in (global) region groups (EU/EEA/SEPA/LATAM/APAC). `GET /regions/groups` */ groups(): Promise; /** Retrieve a region by id. `GET /regions/{regionId}?profile_id=` */ retrieve(regionId: string, profileId: string): Promise; /** Update a region. `PUT /regions/{regionId}?profile_id=` */ update(regionId: string, profileId: string, params: RegionUpdateRequest): Promise; /** Delete a region. `DELETE /regions/{regionId}?profile_id=` */ delete(regionId: string, profileId: string): Promise; /** Get the countries that belong to a region. `GET /regions/{regionId}/countries?profile_id=` */ getCountries(regionId: string, profileId: string): Promise; /** Replace the full country membership of a region. `PUT /regions/{regionId}/countries?profile_id=` */ setCountries(regionId: string, profileId: string, params: RegionSetCountriesRequest): Promise; } /** * Merchant payment-method availability overrides. Force-show or force-hide a * payment method per country/region at global, project, or shop scope, on top * of the curated country defaults. Admin (`sk_*`) access required. */ declare class AvailabilityOverrides { private readonly request; constructor(request: RequestFn); /** Create an availability override. `POST /availability-overrides` */ create(params: AvailabilityOverrideCreateRequest): Promise; /** List a merchant's availability overrides. `GET /availability-overrides` */ list(merchantId: string): Promise; /** Delete an availability override by id. `DELETE /availability-overrides/{id}` */ delete(id: string): Promise; /** * Preview the methods a customer in `country` would be shown for a shop — * the connector ceiling narrowed by the smart country defaults and the * merchant overrides, without an active payment. Pass `amount` + `currency` * to also evaluate order-value rules. * `GET /availability-overrides/preview` */ preview(params: AvailabilityPreviewParams): Promise; } /** * Sell-to restrictions: the buyer countries a merchant refuses to sell to, * merchant-wide and per shop. A refused payment fails with error code `DE_10`. * * Every route needs a dashboard session. Reading needs the `SellToRestrictions` * area at `Read`, overriding a shop needs `Edit` (a shop-scoped role may * override only its own shop), and changing the merchant-wide policy needs * `Write`. */ declare class SellToRestrictions { private readonly request; constructor(request: RequestFn); /** * The merchant-wide policy and every shop's effective policy. A caller * scoped to one shop receives that shop alone, and no merchant-wide policy. * `GET /sell-to-restrictions` */ retrieve(): Promise; /** * Replace the merchant-wide policy every shop without an override inherits. * The change is recorded in the audit log before it is made. * `PUT /sell-to-restrictions/merchant` */ updateMerchantPolicy(params: SellToRestrictionPolicyRequest): Promise; /** * Give one shop its own policy, replacing the merchant-wide policy for that * shop. An empty `blocked_countries` lifts every restriction for it. * `PUT /sell-to-restrictions/shops/{profile_id}` */ updateShopOverride(profileId: string, params: SellToRestrictionPolicyRequest): Promise; /** * Remove one shop's override, so it inherits the merchant-wide policy again. * Removing an override the shop does not have changes nothing. * `DELETE /sell-to-restrictions/shops/{profile_id}` */ deleteShopOverride(profileId: string): Promise; /** * Which checks each payment path performs, and how this deployment resolves * a buyer's country — read before relying on a restriction for a path. * `GET /sell-to-restrictions/capabilities` */ capabilities(): Promise; /** * Why a payment was refused: where, by which signal, for which country and * under which policy. A shop-scoped caller sees only refusals in its shop. * `GET /sell-to-restrictions/payments/{payment_id}/decisions` */ listDecisions(paymentId: string): Promise; } /** * Merchant cost rules: what a merchant's own goods and partners' shares cost * it, as a percentage, a flat amount or both, merchant-wide or for one shop, * over a validity window. * * A rule is a record, not an instruction. It writes no billing ledger entry and * moves no money. Its rate, category and scope cannot be edited; a price change * is a new rule with a new window. * * Every route needs a dashboard session for the merchant: listing needs the * merchant account read permission, every change the write permission. A * shop-scoped session reaches nothing here. */ declare class CostRules { private readonly request; constructor(request: RequestFn); /** * Every cost rule of the merchant. * `GET /cost-rules` * * @param merchantId - The merchant account ID. */ list(merchantId: string): Promise; /** * Record a cost rule. * `POST /cost-rules` * * @param params - The rule. * @param merchantId - The merchant account ID. */ create(params: CreateMerchantCostRuleRequest, merchantId: string): Promise; /** * Close, deactivate or rename a cost rule. * `PUT /cost-rules/{rule_id}` * * @param ruleId - The rule ID. * @param params - What to change. * @param merchantId - The merchant account ID. */ update(ruleId: string, params: UpdateMerchantCostRuleRequest, merchantId: string): Promise; /** * Delete a cost rule. * `DELETE /cost-rules/{rule_id}` * * @param ruleId - The rule ID. * @param merchantId - The merchant account ID. */ delete(ruleId: string, merchantId: string): Promise; } /** * Out-of-band transitions: what a PSP did that DeloPay never observed. * * A refund issued in the PSP's own dashboard, a chargeback settled by phone, a * payment the rail holds as paid but never sent a usable notification for. The * transition is written through the normal path, an outgoing webhook is raised * for the shop, and an audit row records who asserted it and why. * * This asserts a fact about somebody else's money, so it is deliberately * expensive to call: the reason is compelled, the PSP's own reference belongs * with it, and every record is attributable. It needs a dashboard session with * the profile external-record write permission — an API key reaches nothing * here. */ declare class ExternalRecords { private readonly request; constructor(request: RequestFn); /** * Record one out-of-band transition. * `POST /external-records` * * Which kinds a deployment answers for depends on the payment's connector * and on which arms are live: an arm that is not served answers `501`, which * is a statement about this rail rather than about the request. * * @param params - What happened, and why it is being recorded. */ create(params: ExternalRecordRequest): Promise; /** * What this deployment will accept, per kind. * `GET /external-records/capabilities` * * Read it before offering the operator a form. Every kind is listed, so an * entry that is not served is distinguishable from a kind this release does * not know at all — and `create` says *why* it is not served: a gap * (`not_implemented`) or a rule (`not_applicable`). Treating those as the * same thing is how a caller ends up waiting for something that will never * be built. * * The answer describes the binary rather than anything a merchant owns, so * it is the same for every caller of one deployment and is safe to cache for * the life of a dashboard session. */ capabilities(): Promise; } /** * A shop's product catalogue: what it sells, and at which prices. * * **Every route here belongs to one shop**, and the backend resolves it from * an `X-Profile-Id` header, answering `IR_04` without one. That is why * `profileId` is a required argument on every method rather than something to * remember in `options.headers`: a header that can be forgotten is forgotten, * and the failure arrives at runtime in a caller's dashboard rather than at * their compiler. * * Two shapes of the catalogue are worth knowing before calling this: * * * **A product is created with its prices.** The API requires at least one, * because a product nobody can be charged for cannot be sold. * * **A price is closed, never rewritten.** `updatePrice` changes its label, * its default flag and its metadata; the amount, the currency and the * billing interval are fixed for the life of the price, so a charge that was * made can always be explained by a price that still exists. Charging a new * figure means adding a price and archiving the old one. * * **Every price route answers with the whole product.** The catalogue's unit * is the product: a price means something only beside its siblings, and * which one is the default changes the moment another is added. So these * methods return `ProductResponse`, and the price that was touched is found * in its `prices` — typing them as the price alone let a caller read * `result.amount` as a number that is `undefined` at runtime, and mistake * the product's id for a price id. */ declare class Products { private readonly request; constructor(request: RequestFn); /** The shop scope every route here takes, as the backend expects it. */ private scoped; /** * Create a product together with its prices. * `POST /products` * * @param params - The product, with at least one price. * @param profileId - The shop the product belongs to. */ create(params: ProductCreateRequest, profileId: string, options?: RequestExtras): Promise; /** * One page of the shop's catalogue. * `GET /products/list` * * Archived products are returned too unless `status` narrows them away — * they are what past charges refer to. */ list(params: ProductListParams | undefined, profileId: string, options?: RequestExtras): Promise; /** * Retrieve one product with its prices. * `GET /products/{product_id}` */ retrieve(productId: string, profileId: string, options?: RequestExtras): Promise; /** * Change a product's name, description or metadata. * `POST /products/{product_id}` * * Its kind and its prices are not editable here — see the class note. */ update(productId: string, params: ProductUpdateRequest, profileId: string, options?: RequestExtras): Promise; /** * Take a product off sale. * `POST /products/{product_id}/archive` * * It stays readable and keeps its prices: a payment made against it has to * remain explicable after it is withdrawn. */ archive(productId: string, profileId: string, options?: RequestExtras): Promise; /** * Put an archived product back on sale. * `POST /products/{product_id}/restore` */ restore(productId: string, profileId: string, options?: RequestExtras): Promise; /** * Add a price to an existing product. * `POST /products/{product_id}/prices` * * This is how a price changes: the new figure is added and the old price is * archived, so both remain readable. */ addPrice(productId: string, params: ProductPriceCreateRequest, profileId: string, options?: RequestExtras): Promise; /** * Change a price's label, its default flag or its metadata. * `POST /products/{product_id}/prices/{price_id}` * * Not its amount, its currency or its interval: see the class note. */ updatePrice(productId: string, priceId: string, params: ProductPriceUpdateRequest, profileId: string, options?: RequestExtras): Promise; /** * Take one price off sale, leaving the product on it. * `POST /products/{product_id}/prices/{price_id}/archive` */ archivePrice(productId: string, priceId: string, profileId: string, options?: RequestExtras): Promise; } /** * A merchant's own processor cost schedules: what the processor the merchant * brought itself charges it, per connector and — as narrowly as the rate is * written — per shop, payment method, method type, card network or the * processor's own method code. * * A schedule prices the rails that report no fee on the payment itself. What * it produces is stamped `estimated` on the settlement line, never * `reported`, and the rates that priced it are copied onto the line as * `processor_cost_basis` so the figure survives the schedule being replaced * or deleted. * * The DeloPay-wide defaults a merchant inherits are DeloPay's own acquirer * pricing. They are never listed here, and they are edited through the * internal client instead (`DelopayInternal.adminPortal`). * * A rate is never edited in place: it priced transactions that already * happened. {@link ProcessorCosts.update} only closes, deactivates or * annotates a schedule; a price change is {@link ProcessorCosts.replace}, * which closes the old window and opens the successor in one step. * * Every route takes the merchant account id and needs a dashboard session for * that merchant. */ declare class ProcessorCosts { private readonly request; constructor(request: RequestFn); /** * Every processor cost schedule of the merchant. * `GET /processor-costs` * * @param merchantId - The merchant account ID. */ list(merchantId: string): Promise; /** * Record a processor cost schedule. * `POST /processor-costs` * * A `shop_id` that is well formed but not a shop of this merchant is * answered `404` and nothing is recorded: a rate scoped to a shop the * merchant does not own would price no attempt, leaving every line it was * meant to price `unavailable` while the screen showed a configured rate. * * @param params - The schedule. * @param merchantId - The merchant account ID. */ create(params: CreateProcessorCostScheduleRequest, merchantId: string): Promise; /** * Record several schedules at once, all or none — copy a price list across * methods or shops. * `POST /processor-costs/bulk` * * 1 to 200 entries. Every entry is validated before anything is stored: an * invalid one is answered `422` naming its index, and nothing is stored. * The answer lists the created schedules in request order. * * @param params - The schedules. * @param merchantId - The merchant account ID. */ bulkCreate(params: BulkCreateProcessorCostSchedulesRequest, merchantId: string): Promise; /** * Close, deactivate or annotate a schedule. The rate is not editable. * `PUT /processor-costs/{schedule_id}` * * @param scheduleId - The schedule ID. * @param params - What to change. * @param merchantId - The merchant account ID. */ update(scheduleId: string, params: UpdateProcessorCostScheduleRequest, merchantId: string): Promise; /** * Close a schedule at `effective_from` and open its successor with the same * scope and the same end, in one step: no transaction is priced by both * rates, and none falls between them. * `POST /processor-costs/{schedule_id}/replace` * * Answers the successor schedule. An `effective_from` outside the old * schedule's window, an inactive schedule, or one a concurrent replacement * already moved is answered `400` — retry against the schedule as it now * stands rather than forcing the write. * * @param scheduleId - The schedule to replace. * @param params - The new rate. * @param merchantId - The merchant account ID. */ replace(scheduleId: string, params: ReplaceProcessorCostScheduleRequest, merchantId: string): Promise; /** * Delete a schedule. Restates nothing: every figure it derived is already * stamped on the settlement lines it priced. * `DELETE /processor-costs/{schedule_id}` * * @param scheduleId - The schedule ID. * @param merchantId - The merchant account ID. */ delete(scheduleId: string, merchantId: string): Promise; } /** Merchant control of the five extra device fields collected on hosted-checkout confirm. */ declare class CheckoutBrowserInfo { private readonly request; constructor(request: RequestFn); /** Read the merchant's own setting and effective collection policy. */ retrieve(merchantId: string): Promise; /** Replace the merchant setting. An admin refusal still wins; changes are audited. */ update(merchantId: string, body: UpdateCheckoutBrowserInfoScopeRequest): Promise; } /** * A shop's payment-method layout for the hosted checkout: the express strip, * the main list, the "other payment methods" list, and each item's overrides * (text, translations, icon, look, open target, visibility). One document per * shop (business profile), owned by the shop rather than by each connector * account. Until a shop saves one, the router derives a layout from the * shop's connector accounts and reports `source: 'derived'`. * * Dashboard JWT (`CheckoutMethods` read / write) or an admin key. */ declare class CheckoutMethods { private readonly request; constructor(request: RequestFn); /** * The shop's layout (stored, or derived from its accounts) and every item * it may place. `GET /checkout-methods/{profile_id}` */ retrieve(merchantId: string, profileId: string): Promise; /** * Replace the shop's layout. The router refuses an express item that does * not draw its own button, more than two rows of two in the express strip, * a title without text, or an item no account of the shop offers. * `PUT /checkout-methods/{profile_id}` */ update(merchantId: string, profileId: string, body: CheckoutMethodsUpdateRequest): Promise; /** * Delete the stored layout; the shop is back on the derived one. * `DELETE /checkout-methods/{profile_id}` */ reset(merchantId: string, profileId: string): Promise; } /** * Subscription endpoints are profile-scoped: the backend requires an * `X-Profile-Id` header to resolve the shop / billing processor (`IR_04` * otherwise). Pass it through the per-call `options.headers`, e.g. * `subscriptions.list(params, { headers: { 'X-Profile-Id': profileId } })`. */ declare class Subscriptions { private readonly request; constructor(request: RequestFn); /** * Create and immediately confirm a subscription. `POST /subscriptions` * * For billing processors that require buyer approval (e.g. PayPal), the * response carries a `redirect_url` the customer must be sent to. */ createAndConfirm(params: CreateAndConfirmSubscriptionRequest, options?: RequestExtras): Promise; /** Create a subscription without confirming it. `POST /subscriptions/create` */ create(params: CreateSubscriptionRequest, options?: RequestExtras): Promise; /** * Adopt an existing Creem subscription. `POST /subscriptions/adopt` * * Requires adoption to be enabled on the deployment and `X-Profile-Id` in * options.headers. Adoption charges nothing and emits no invoice or webhook. * Repeating the processor subscription ID returns the existing DeloPay ID. * Retry when handoff_confirmed is false. That flag confirms persistence, * not the merchant's external switch-over: stop processing Creem events in * your own integration when handing subsequent events to DeloPay. */ adopt(params: AdoptSubscriptionRequest, options?: RequestExtras): Promise; /** Retrieve a subscription by ID. `GET /subscriptions/{subscriptionId}` */ retrieve(subscriptionId: string, options?: RequestExtras): Promise; /** * Confirm a previously created subscription. `POST /subscriptions/{subscriptionId}/confirm` * * Like {@link createAndConfirm}, the response may carry a `redirect_url` for * processors that require buyer approval. */ confirm(subscriptionId: string, params: ConfirmSubscriptionRequest, options?: RequestExtras): Promise; /** Update a subscription's plan/price. `PUT /subscriptions/{subscriptionId}/update` */ update(subscriptionId: string, params: UpdateSubscriptionRequest, options?: RequestExtras): Promise; /** List subscriptions for the profile. `GET /subscriptions/list` */ list(params?: SubscriptionListParams, options?: RequestExtras): Promise; /** Estimate the cost of a subscription before creating it. `GET /subscriptions/estimate` */ getEstimate(params: SubscriptionEstimateParams, options?: RequestExtras): Promise; /** List purchasable subscription items (plans/addons). `GET /subscriptions/items` */ getItems(params: GetSubscriptionItemsParams, options?: RequestExtras): Promise; /** * Pause a subscription. `POST /subscriptions/{subscriptionId}/pause` * * The body defaults to `{}` so the request still carries * `Content-Type: application/json` even when no params are passed — the * backend rejects the POST otherwise ("Unsupported content type"). */ pause(subscriptionId: string, params?: PauseSubscriptionRequest, options?: RequestExtras): Promise; /** Resume a paused subscription. `POST /subscriptions/{subscriptionId}/resume` */ resume(subscriptionId: string, params?: ResumeSubscriptionRequest, options?: RequestExtras): Promise; /** Cancel a subscription. `POST /subscriptions/{subscriptionId}/cancel` */ cancel(subscriptionId: string, params?: CancelSubscriptionRequest, options?: RequestExtras): Promise; /** * Resolve which of the given payments were raised by a subscription. * `POST /subscriptions/payments/lookup` * * The linkage exists in one direction only — an invoice points at the payment * it settled, and nothing is stamped on the payment — so this is the only way * to tell a subscription charge from a one-off one when you are holding a * page of payments. In particular, do not use `off_session` or the presence * of a mandate: an ordinary saved-card charge sets those identically. * * Ids that belong to no subscription are **absent** from `links` rather than * returned as an error, so match on presence: * * ```ts * const { links } = await subscriptions.lookupPayments( * { payment_ids: page.map((p) => p.payment_id) }, * { headers: { 'X-Profile-Id': profileId } }, * ); * const bySubscription = new Map(links.map((l) => [l.payment_id, l])); * ``` * * Profile-scoped like every other subscription route, and that matters more * here than elsewhere: a `payment_id` is merchant-supplied and only unique * within a merchant, so the shop is part of the question, not an * optimisation. Pass the profile that owns **the payments** — for a list * spanning several shops, group the ids by shop and call once per group. * * At most 200 ids per call. */ lookupPayments(params: SubscriptionPaymentLookupRequest, options?: RequestExtras): Promise; /** * One subscription's billing history, newest cycle first. * `GET /subscriptions/{subscriptionId}/invoices` * * {@link retrieve} carries only the *latest* invoice, which is the current * cycle — a subscription that has renewed monthly for a year has one of those * and twelve of these. Use this wherever a merchant needs to see what a * subscription has actually billed, in particular on self-charging processors * (Creem, PayPal) where each renewal is charged by the processor and mirrored * here rather than raised as a DeloPay payment. * * Two things to render honestly, both decided rather than incidental: * * - `amount` is **gross** and `refunded_amount` sits beside it. Do not net * them: the difference between the two is not a smaller charge. * - A `refunded_amount` of `null` is "not reported" and must not render as * `0`. Likewise a processor-hosted origination records a bootstrap invoice * at `0` before the buyer has paid anything, so a zero amount on such a * subscription is a placeholder rather than a free cycle. * * Profile-scoped like every other subscription route. */ listInvoices(subscriptionId: string, params?: SubscriptionInvoiceListParams, options?: RequestExtras): Promise; /** * Which billing processor this shop's subscriptions run on. * `GET /subscriptions/billing_processor` * * The same mapping is derivable from the connector inventory * (`GET /account/{merchant_id}/connectors`), but that route is gated by a * connector-read permission granted independently of subscriptions — so a * role authorised to create subscriptions could be unable to learn which * processor it was creating them on. This answers under the same * authorization as the rest of the subscription API. * * Reach for it when the client must branch on the processor *before* calling * — origination differs by processor, and guessing is destructive. Resolve * the shop's `billing_processor_id` first: a shop that runs no subscriptions * has none assigned, and this route has no identity to report for it. */ getBillingProcessor(options?: RequestExtras): Promise; } /** * A merchant's own audit log — everything changed on their account and their * shops, grouped by the sign-in it happened in. * * Requires a dashboard JWT (`setJwtToken`) whose role holds the `AuditLog` * permission; an API key cannot read it. This is deliberate: the log names * people, and an API key names no one. * * There is no `merchant_id` parameter. The scope comes from the token, so this * resource cannot be pointed at another merchant — see * {@link MerchantAuditLogListParams}. */ declare class Audit { private readonly request; constructor(request: RequestFn); list(params?: MerchantAuditLogListParams): Promise; /** * One entry. An id belonging to another merchant answers 404, not 403 — the * endpoint does not confirm that an entry it will not serve exists. */ retrieve(logId: string): Promise; } /** * Hosted-shop settlement: monthly statements, the live current-period * rollup, per-line detail, fee schedules and backfills. * * Every read takes an explicit `test_mode` — test and live figures must * never blend, so the environment lives in the signature rather than in a * default. `false` is transmitted, not dropped. * * Shop-owner responses are redacted server-side: absent platform-fee fields * are a permission boundary, not a gap — never re-derive them client-side. */ declare class Settlement { private readonly request; constructor(request: RequestFn); /** * Read signed refund/dispute processor fees and report availability. * Merchant scope and connector grants apply. Empty movements confirm zero * only when the source is reported and complete. * Supply payment_id without year/month for all history. force_sync reads * provider reports for this page; it requires payment_id and limit <= 20, * and never issues a payment, refund or dispute action. * Omit test_mode for every reporting partition of one payment; period * queries default to live. Each source states its reporting partition. */ listProcessorCostEvents(params: ProcessorCostEventsRequest, options?: RequestExtras): Promise; /** * Per-shop settlement rollup for the host merchant: unpaid totals and the * running current period, one row per shop. * * `GET /settlement/overview` */ overview(params: SettlementOverviewParams, options?: RequestExtras): Promise; /** * Live rollup of the current (not yet statemented) period. * * `GET /settlement/current` */ current(params: SettlementCurrentParams, options?: RequestExtras): Promise; /** * What a period's payments cost, and what was left over: gross, the * platform fee, hosting fees, what the rails took, and the margin, with a * per-connector breakdown. * * Send `year` and `month` together to report one UTC calendar month, or * neither for the running month so far. * * **Host-only.** The response is the host's cost base, which a shop owner * must never see, so a profile-scoped caller is refused with a 403 rather * than given a redacted shell. Gate the surface on the caller's scope * instead of calling it and handling the failure. Connector-restricted * roles cannot read this merchant-wide aggregate; fee-event reads retain * their connector grants. * * Two things not to flatten when rendering the result: * `margin_usd` is absent — not zero — whenever `margin_quality` is * `'unknown'`, and `unlined_captured_attempt_count` (cost definitely * missing) means something different from `unlined_unresolved_attempt_count` * (mostly ordinary abandonment). * * `GET /settlement/cost` * * @example * ```typescript * const cost = await delopay.settlement.cost({ test_mode: false, year: 2026, month: 7 }); * if (cost.margin_quality === 'unknown') { * // cost.margin_usd is absent — say so, do not render 0.00 * } * ``` */ cost(params?: SettlementCostParams, options?: RequestExtras): Promise; /** * What a period's payments cost at the rails, grouped by shop, connector * and payment method. Answers for **every** merchant, including one that * hosts nobody — it reads the per-attempt settlement record rather than the * settlement lines, which exist only where hosting fees are configured. * * A sibling of {@link Settlement.cost} rather than a widening of it. That * endpoint is a profit statement whose revenue term is hosting income, and * a merchant who hosts nobody has none. This publishes cost and never * margin. * * Send `year` and `month` together to report one UTC calendar month, or * neither for the running month so far. * * **The response carries no period total, and a caller must not compute * one here.** Every figure keeps the unit the rail reported it in, and the * unit is part of each bucket's key, so adding two buckets is not addition. * Convert caller-side if you need one number, and say which rates you used. * The counts are totalled, because a count has no unit. * * Two distinctions not to flatten when rendering a bucket: * `cost_amount` covers `reported_count + estimated_count` attempts out of * `attempt_count` and is a lower bound whenever those disagree; and * `unavailable_count` (the rail says it has no figure) means something * different from `unwritten_count` (a gap in our own recording). * * **Merchant scope.** A token scoped to a single shop, a role narrowed to * specific connector accounts, or a role without the settlement-read * permission group is refused `403`: what a rail charged is the merchant's * own cost base, and here it is the whole response, so there is nothing * coherent to redact down to. Gate the surface on the caller's scope rather * than calling it and handling the failure. * * `GET /settlement/processor-cost` * * @example * ```typescript * const period = await delopay.settlement.processorCost({ * test_mode: false, * year: 2026, * month: 7, * }); * for (const bucket of period.buckets) { * // Each bucket is its own unit — render per bucket, never a sum. * console.log(bucket.connector, bucket.cost_amount, bucket.cost_currency); * } * ``` */ processorCost(params?: ProcessorCostPeriodRequest, options?: RequestExtras): Promise; /** * List generated settlement statements, newest first. * * `GET /settlement/statements` */ listStatements(params: SettlementStatementListParams, options?: RequestExtras): Promise; /** * One statement with its per-connector/currency breakdown. * * `GET /settlement/statements/{statementId}` */ retrieveStatement(statementId: string, options?: RequestExtras): Promise; /** * Generate (or regenerate) the statement for one shop and calendar month. * * `POST /settlement/statements/generate` */ generateStatement(params: StatementGenerateRequest, options?: RequestExtras): Promise; /** * Record payout progress on a statement (`unpaid` / `partial` / `paid`). * * Subject to the caller's `settlement_payout` operation limit — a * per-operation ceiling, a windowed total, a windowed count, or any * combination: an over-limit call fails with `DE_01` and nothing is * recorded. There is no approval route out of it — four-eyes needs an * executor that can run the operation once somebody says yes, and only * refunds have one, so a settlement rule can only block. * * `POST /settlement/statements/{statementId}/payout` */ updateStatementPayout(statementId: string, params: StatementPayoutUpdateRequest, options?: RequestExtras): Promise; /** Append one external transfer record. Send a stable idempotency_key for safe retries. */ recordStatementPayout(statementId: string, params: StatementPayoutRecordRequest, options?: RequestExtras): Promise; /** Read individual transfer records, their signed total, and any overpayment. */ listStatementPayouts(statementId: string, options?: RequestExtras): Promise; /** Record a transfer against a shop's month before its statement exists. */ recordAdvance(params: StatementAdvanceRecordRequest, options?: RequestExtras): Promise; listAdvances(params: SettlementAdvanceListRequest, options?: RequestExtras): Promise; /** * Export a statement as PDF. Returns the raw PDF bytes as a `Blob`, with * the same auth, retries and error handling as every other call — persist * or object-URL it caller-side. * * `GET /settlement/statements/{statementId}/pdf` * * @example * ```typescript * const pdf = await delopay.settlement.downloadStatementPdf('stmt_1', { * currency: 'EUR', * include_transactions: true, * }); * const url = URL.createObjectURL(pdf); * ``` */ downloadStatementPdf(statementId: string, params?: StatementPdfParams, options?: RequestExtras): Promise; /** * The individual settled attempts of one shop's calendar month. * * `GET /settlement/lines` */ listLines(params: SettlementLineListParams, options?: RequestExtras): Promise; /** * The fee schedules that currently apply to a shop. * * `GET /settlement/fee-config` */ feeConfig(params: ShopFeeConfigParams, options?: RequestExtras): Promise; /** * Enqueue a settlement-line backfill over historical attempts. Attempts * already covered by a line are always skipped. * * `POST /settlement/backfill` */ backfill(params?: SettlementBackfillRequest, options?: RequestExtras): Promise; /** * Toggle whether a shop's owner can see their own settlement figures. * * `POST /settlement/shops/visibility` */ setShopVisibility(params: ShopVisibilityUpdateRequest, options?: RequestExtras): Promise; /** * Manual adjustments recorded on a statement. * * `GET /settlement/statements/{statementId}/adjustments` */ listStatementAdjustments(statementId: string, options?: RequestExtras): Promise; /** * Add a manual adjustment to a statement. Positive `amount_usd` charges * the shop (reducing their payout); negative credits them. * * Subject to the caller's `settlement_adjustment` operation limit (amount * dimensions only): an over-limit call fails with `DE_01` and no adjustment * is added. A settlement rule can only block — approval is refund-only, for * the reason given on `updateStatementPayout()`. * * `POST /settlement/statements/{statementId}/adjustments` */ createStatementAdjustment(statementId: string, params: StatementAdjustmentCreateRequest, options?: RequestExtras): Promise; /** * Remove a manual adjustment from a statement. * * `DELETE /settlement/statements/{statementId}/adjustments/{adjustmentId}` */ deleteStatementAdjustment(statementId: string, adjustmentId: string, options?: RequestExtras): Promise; /** * Calculate a period in its transaction currency. Requires a host-level * dashboard JWT and settlement read permission. Profile users and roles * restricted to selected connector accounts cannot read this aggregate. * Rates apply once to the period total. Unknown or estimated fees keep * dependent amounts provisional. Native money values remain integer strings. */ previewNative(params: NativeSettlementPreviewRequest, options?: RequestExtras): Promise; /** * Recalculate and freeze an immutable report version. Requires settlement * edit permission. Explicitly allow provisional figures when source fees * are incomplete. A report records a calculation and sends no money. * Reuse the same idempotency key when retrying the same request. */ createNativeReport(params: NativeSettlementReportCreateRequest, options?: RequestExtras): Promise; retrieveNativeReport(reportId: string, options?: RequestExtras): Promise; /** List report summaries without fetching every source row in each saved snapshot. */ listNativeReports(params: NativeSettlementListRequest, options?: RequestExtras): Promise; /** * Record a transfer already made, under settlement write permission and * the caller's payout limits. This method never sends money. Native minor * units remain strings; negative records must name a same-scope reversal. * These records belong to the native ledger, separate from monthly USD * statement payouts and advances. */ recordNativePayout(params: NativeSettlementPayoutCreateRequest, options?: RequestExtras): Promise; listNativePayouts(params: NativeSettlementListRequest, options?: RequestExtras): Promise; /** Refresh a bounded page of provider fee evidence. Follow next_offset while has_more is true. */ refreshNativeFees(params: NativeSettlementFeeRefreshRequest, options?: RequestExtras): Promise; downloadNativeReportPdf(reportId: string, params?: NativeSettlementPdfRequest, options?: RequestExtras): Promise; /** Download the frozen source rows and calculation as CSV. */ downloadNativeReportCsv(reportId: string, options?: RequestExtras): Promise; /** Export the frozen report as JSON, excluding host account-level costs. */ exportNativeReportJson(reportId: string, options?: RequestExtras): Promise; } /** * Per-operation spending limits: rules scoped to the merchant, a role or a * user, the merchant-level enforcement settings, and the approvals inbox. * Enforcement resolves the most specific rule: user > role > merchant. * * A rule set to `require_approval` does not refuse an over-limit operation — * it parks it. The original call fails with HTTP 409 `DE_06` carrying * `PendingApprovalErrorDetails`, and the operation runs only once a second * person approves the request through this inbox. * * **Refunds only.** Approval needs an executor that can run the operation * after the decision, and only refunds have one; the settlement operations * take `block` alone, which the request type enforces. So every request in * this inbox is a refund, and `DE_06` never comes back from a settlement * call — an over-limit settlement adjustment or payout fails with `DE_01`. */ declare class OperationLimits { private readonly request; constructor(request: RequestFn); /** * List the merchant's limit rules, optionally for one operation. * * `GET /operation-limits/rules` */ listRules(params?: OperationLimitRuleListParams, options?: RequestExtras): Promise; /** * Create or replace the limit rule for one target. Full-replace upsert: * absent limit fields clear that dimension. * * `PUT /operation-limits/rules` */ upsertRule(params: UpsertOperationLimitRuleRequest, options?: RequestExtras): Promise; /** * Delete a limit rule. * * `DELETE /operation-limits/rules/{ruleId}` */ deleteRule(ruleId: string, options?: RequestExtras): Promise; /** * The merchant-level enforcement settings. An untouched merchant gets the * defaults: rolling window, admins not exempt. * * `GET /operation-limits/settings` */ retrieveSettings(options?: RequestExtras): Promise; /** * Update the enforcement settings. Only provided fields change. * * `PUT /operation-limits/settings` */ updateSettings(params: UpdateOperationLimitSettingsRequest, options?: RequestExtras): Promise; /** * The approvals inbox: over-limit operations waiting on a second person. * * Both filters default rather than widen. With no `status` the list holds * **pending requests only** — approved, rejected and expired ones are * reachable only by asking for that status, so a history view must pass one * per status. With no `operation` it lists **refunds only**; the list is one * operation at a time. `limit` defaults to 100 and is clamped to 1–500. * * Requests past their `expires_at` are expired before the list is read, so * nothing here is shown as actionable when it is not. * * `GET /operation-limits/approvals` */ listApprovals(params?: PendingOperationListParams, options?: RequestExtras): Promise; /** * Approve a parked operation and execute it. * * Refused for the user who requested it, and for an approver whose own * limit would not have covered the operation — the permission is necessary * and not sufficient. * * Approval and execution are two facts. A request that was approved but * whose operation then failed comes back `approved` with `execution_error` * set and no `result_entity_id`; that is a real outcome, not a partial read. * * `POST /operation-limits/approvals/{id}/approve` */ approve(id: string, params?: DecidePendingOperationRequest, options?: RequestExtras): Promise; /** * Reject a parked operation. Nothing is executed and the request is closed. * * `POST /operation-limits/approvals/{id}/reject` */ reject(id: string, params?: DecidePendingOperationRequest, options?: RequestExtras): Promise; } /** * Stored shop risk indexes, per connector. * * Both reads return snapshots and never score on demand, so `computed_at` is * the age of the answer rather than the time of the call. * * The caller's own scope is what bounds the answer, and it is enforced * server-side: a profile-scoped role, or an API key pinned to one shop, gets * that shop from both endpoints and cannot read or enumerate a sibling's. */ declare class Risk { private readonly request; constructor(request: RequestFn); /** * Every shop's stored risk for the caller's merchant, with the worst band * across them. * * `GET /risk` */ retrieve(options?: RequestExtras): Promise; /** * One shop's stored risk index per connector. * * `GET /risk/shops/{profileId}` */ retrieveShop(profileId: string, options?: RequestExtras): Promise; } /** * Events emitted by the debug logger. * - `request` — about to send a request (`method`, `url`, `path`) * - `response` — response received (`status`, `method`, `path`, `requestId?`) * - `retry` — about to retry after a transient failure (`attempt`, `maxRetries`, `method`, `path`) */ type DelopayLogger = (event: 'request' | 'response' | 'retry', data: Record) => void; /** * Configuration options for the Delopay client. */ interface DelopayOptions { /** Use the sandbox environment (`https://sandbox.delopay.net`). Defaults to `false` (production). */ sandbox?: boolean; /** Override the base URL entirely. Takes precedence over `sandbox`. */ baseUrl?: string; /** Request timeout in milliseconds. Defaults to `30000` (30 seconds). */ timeout?: number; /** * Maximum number of automatic retries for transient failures (5xx, timeout, network errors). * Retries use exponential backoff. Set to `0` to disable. Defaults to `2`. * Only idempotent-safe requests (GET, DELETE, and requests with an `Idempotency-Key` header) are retried. */ maxRetries?: number; /** Enable debug logging of requests and responses. Defaults to `false`. */ debug?: boolean; /** * Custom logger for debug events (`request`, `response`, `retry`). When omitted, * debug output is written to `console.log`. Has no effect unless `debug` is `true`. * Useful for routing SDK logs through pino, winston, or similar structured loggers. */ logger?: DelopayLogger; } /** * One step of a response body's download, reported to * {@link RequestOptions.onDownloadProgress}. */ interface DownloadProgressEvent { /** Body bytes received so far on this attempt, `chunk` included. */ loaded: number; /** * `Content-Length`, when the response carried one — a buffered body such as * a PDF. `null` for a streamed (chunked) body, whose size is not known * until it ends. With a compressed transfer it is the compressed size, so * `loaded` can pass it. */ total: number | null; /** * The bytes this event reports. Empty on the first event of an attempt, * which fires as soon as the headers arrive, before any of the body. * A copy: changing it cannot change the value the request resolves to. */ chunk: Uint8Array; /** The response headers, available from the first event on. */ headers: Headers; /** * Which attempt this is, from `0`. A retried request reports its body * again from the start under the next number, so a listener that * accumulates state resets it when this changes. */ attempt: number; } /** * Low-level options forwarded to a single HTTP request. */ interface RequestOptions { /** Request body, serialised as JSON. */ body?: unknown; /** * @internal Resource assertion that this operation deduplicates by the JSON * body's idempotency_key. Null means that optional key is absent, so the write * must not be retried. The transport checks the assertion against the body; * it does not send this option or copy a JSON key into an HTTP header. */ bodyIdempotencyKey?: string | null; /** * Query-string parameters. `undefined` and `null` values are omitted. * Array values are emitted as repeated keys (`?tag=a&tag=b`) — not comma-joined. */ query?: Record; /** Additional HTTP headers merged with the default `api-key` header. */ headers?: Record; /** * Override the client-level timeout for this request, in milliseconds. * * Covers the whole request, body included — unless `onDownloadProgress` is * set, in which case it is an **idle** timeout: it restarts when the * headers arrive and on every chunk of the body, so a long download that * keeps making progress is never cut off, and one that stalls still is. */ timeout?: number; /** * Caller-provided `AbortSignal`. Aborting it cancels the in-flight request and rejects * with a `DelopayError` carrying code `'ABORTED'`. Combined with the per-request timeout. */ signal?: AbortSignal; /** * How to decode a 2xx response body. `'json'` (the default) parses JSON; * `'blob'` / `'arraybuffer'` return the raw bytes for binary endpoints * such as PDF exports. Error responses are always decoded as JSON and * thrown as `DelopayError` regardless of this setting. */ responseType?: 'json' | 'blob' | 'arraybuffer'; /** * Pass `keepalive: true` to let the request outlive its page — e.g. * telemetry sent while the document is navigating away. Browsers cap * keepalive request bodies at ~64 KiB and reject larger ones. */ keepalive?: boolean; /** * Report a 2xx response body as it downloads. Called once as soon as the * headers arrive (with an empty `chunk` and `loaded: 0`), then once per * chunk of the body; the value the request resolves to is decoded from * those same bytes, per `responseType`. Error responses are not reported. * * Setting it turns `timeout` into an idle timeout (see there). A body that * fails part-way rejects with the ordinary `NETWORK` error and is retried * on the usual terms, starting again under the next `attempt`. A listener * that throws rejects the request with that error, unretried. */ onDownloadProgress?: (event: DownloadProgressEvent) => void; } type RequestFn = (method: string, path: string, options?: RequestOptions) => Promise; /** * Per-call options that resource methods accept as an optional final argument: * extra HTTP headers (e.g. `Idempotency-Key`), a per-request timeout override, * an `AbortSignal` for cancellation, and a download-progress listener — the * last is what a file download (a statement PDF, a report CSV) reports its * bytes through. */ type RequestExtras = Pick; /** * Delopay API client. * * Instantiate once with your API key and reuse across your application. * All resource sub-clients (payments, refunds, customers, …) are exposed * as properties on the instance. * * @example * ```typescript * const delopay = new Delopay('prd_...', { sandbox: false }); * const payment = await delopay.payments.create({ amount: 5000, currency: 'EUR' }); * ``` */ declare class Delopay { /** Utility for verifying incoming webhook signatures (static, no instance needed). */ static webhooks: { verify(rawBody: string | Uint8Array, signatureHeader: string, secret: string): Promise; }; /** The resolved base URL used for all API requests. */ readonly baseUrl: string; private readonly apiKey; private readonly timeout; private readonly maxRetries; private readonly debug; private readonly logger?; private jwtToken?; readonly payments: Payments; readonly refunds: Refunds; readonly customers: Customers; readonly paymentMethods: PaymentMethods; readonly paymentLinks: PaymentLinks; readonly mandates: Mandates; readonly disputes: Disputes; readonly paypalDisputes: PaypalDisputes; readonly payouts: Payouts; readonly ephemeralKeys: EphemeralKeys; readonly events: Events; readonly poll: Poll; readonly connectors: Connectors; readonly routing: Routing; readonly profiles: Profiles; readonly shops: Shops; readonly profileAcquirers: ProfileAcquirers; readonly authentication: Authentication; readonly verification: Verification; readonly users: Users; readonly apiKeys: ApiKeys; readonly billing: Billing; readonly blocklist: Blocklist; readonly fees: Fees; readonly merchantAccounts: MerchantAccounts; readonly projects: Projects; readonly relay: Relay; readonly stripeConnect: StripeConnect; readonly threeDsRules: ThreeDsRules; /** * A merchant's own audit log. Needs a dashboard JWT, not an API key — * see {@link Audit}. */ readonly audit: Audit; readonly settlement: Settlement; readonly operationLimits: OperationLimits; readonly risk: Risk; readonly subscriptions: Subscriptions; readonly files: Files; readonly export: Export; readonly forex: Forex; readonly regions: Regions; readonly availabilityOverrides: AvailabilityOverrides; readonly sellToRestrictions: SellToRestrictions; readonly costRules: CostRules; /** Out-of-band transitions a PSP made that DeloPay never observed. */ readonly externalRecords: ExternalRecords; /** A shop's product catalogue: what it sells, and at which prices. */ readonly products: Products; /** What the merchant's own processor charges it — see {@link ProcessorCosts}. */ readonly processorCosts: ProcessorCosts; readonly checkoutBrowserInfo: CheckoutBrowserInfo; readonly checkoutMethods: CheckoutMethods; readonly analytics: Analytics; readonly analyticsDashboard: AnalyticsDashboard; readonly featureMatrix: FeatureMatrix; readonly cards: Cards; readonly search: Search; /** * Create a new Delopay client. * * @param apiKey - Your Delopay API key (e.g. `prd_...` or `snd_...`). * Pass an empty string or omit for JWT-only usage (e.g. dashboard apps). * @param options - Optional configuration (sandbox mode, base URL override, timeout). */ constructor(apiKey?: string, options?: DelopayOptions); /** * Set a JWT token for subsequent requests. * When set, requests use `Authorization: Bearer ` instead of `api-key`. * Useful after `users.signIn()` returns a JWT for dashboard operations. */ setJwtToken(token: string): void; /** * Clear the JWT token, reverting to API key authentication. */ clearJwtToken(): void; /** * Refresh the current login JWT (see {@link Users.refreshToken}) and * apply the fresh token to this client, so subsequent requests use it. * Returns the fresh token for the caller to persist (e.g. session * storage) — the backend has already re-set the `login_token` cookie. * * If the client's auth state changes while the refresh is pending — * `clearJwtToken()` on sign-out, or `setJwtToken()` switching to another * session — the stale completion is discarded and this rejects with a * `session_changed` `DelopayError` (status 0), so the explicit change * wins and the caller never persists a token for a session that is gone. * * Otherwise throws like any other request; in particular a 401 means the * session is dead (expired/blacklisted/revoked), a 429 means a refresh * was already minted for this session within the last minute, and a 400 * means this token has no revocable session to slide (no `jti` — team * impersonation is the case in practice) and can never be refreshed, * though it stays valid for ordinary calls. */ refreshSession(): Promise; /** * Make a raw HTTP request to the Delopay API. * * You rarely need to call this directly — prefer the typed resource methods. * Use it only for endpoints not yet covered by a resource class. * * @param method - HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`). * @param path - API path starting with `/` (e.g. `/payments`). * @param options - Optional body, query parameters, and headers. * @returns Parsed JSON response body typed as `T`. * @throws {DelopayAuthenticationError} On 401 responses. * @throws {DelopayError} On all other non-2xx responses, timeouts, and network errors. */ request(method: string, path: string, options?: RequestOptions): Promise; /** * Auto-paginate a list endpoint. Yields items one by one, fetching * the next page automatically when the current one is exhausted. * * Delopay list endpoints use one of two pagination styles, so this helper * supports both: * - **Offset** (default) — for endpoints like `customers.list` that accept * `offset`/`limit`. Each page advances `offset` by the number of items returned. * - **Cursor** — for endpoints like `payments.list` and `payouts.list` that page * with `starting_after`/`limit` (they ignore `offset`). Pass a `cursor` extractor * that returns the id of an item; the next page is requested with * `starting_after` set to the last item's id. * * @param listFn - A function that takes the paging params and returns `{ data: T[] }` or `T[]`. * @param params - Additional parameters to pass to every page request. * @param options - Page size (number) for offset mode, or `{ pageSize?, cursor? }`. * Provide `cursor` to switch to cursor pagination. * * @example * ```typescript * // Offset endpoint (customers): * for await (const c of delopay.paginate((p) => delopay.customers.list(p))) { * console.log(c.customer_id); * } * * // Cursor endpoint (payments): extract the id used as the next cursor. * for await (const payment of delopay.paginate( * (p) => delopay.payments.list(p), * undefined, * { cursor: (p) => p.payment_id }, * )) { * console.log(payment.payment_id); * } * ``` */ paginate>(listFn: (params: P & { limit: number; offset?: number; starting_after?: string; }) => Promise<{ data: T[]; } | T[]>, params?: P, options?: number | { pageSize?: number; cursor?: (item: T) => string | undefined; }): AsyncGenerator; } /** * Error thrown when the Delopay API returns a non-2xx response, or when a * timeout or network error occurs. * * @example * ```typescript * try { * await delopay.payments.create({ amount: 5000, currency: 'EUR' }); * } catch (e) { * if (e instanceof DelopayError) { * console.error(e.status, e.code, e.requestId, e.message); * } * } * ``` */ declare class DelopayError extends Error { /** HTTP status code returned by the API, or `0` for timeout/network errors. */ readonly status: number; /** Machine-readable error code returned by the API (e.g. `'HE_00'`). */ readonly code: string; /** Error category (e.g. `'invalid_request'`, `'timeout_error'`). */ readonly type: string; /** Value of the `x-request-id` response header, when present. Include this when contacting support. */ readonly requestId?: string; /** * Raw response body (truncated to ~2 KB). Populated when the server returns a * non-JSON error body (e.g. an HTML 502 from an upstream proxy) so debugging * still has something to go on. */ readonly rawBody?: string; /** * Structured error context the API attaches under `error.data` for select * codes — e.g. `{ retry_after_secs: 248 }` on rate-limit / max-attempt * lockouts. Schema is per-code; consult the API reference for the shape. */ readonly data?: Record; constructor(message: string, options: { status: number; code: string; type: string; requestId?: string; rawBody?: string; data?: Record; }); } /** * Thrown when the API key is missing, invalid, or revoked (HTTP 401). * * @example * ```typescript * if (e instanceof DelopayAuthenticationError) { * // Prompt user to re-enter their API key. * } * ``` */ declare class DelopayAuthenticationError extends DelopayError { constructor(message?: string, options?: { code?: string; type?: string; requestId?: string; rawBody?: string; data?: Record; }); } /** * The payload of a webhook event, tagged by kind. * * Mirrors the backend's `{ "type": …, "object": … }` envelope: `type` names the * payload shape and `object` carries it. Narrow on `content.type` to get a * fully-typed `object`: * * ```typescript * if (event.content.type === 'payment_details') { * event.content.object.payment_id; // typed as PaymentResponse * } * ``` */ type WebhookContent = { type: 'payment_details'; object: PaymentResponse; } | { type: 'refund_details'; object: RefundResponse; } | { type: 'dispute_details'; object: DisputeResponse; } | { type: 'mandate_details'; object: MandateResponse; } | { type: 'payout_details'; object: PayoutResponse; } | { type: 'subscription_details'; object: SubscriptionWebhookContent; }; /** * Whether a `subscription_details` payload is a lifecycle snapshot * ({@link LifecycleWebhook}) rather than a cycle chargeback or a paid invoice. * * Three shapes share `content.type`; what marks a lifecycle snapshot on the * wire is `transition_id`, which neither of the other two carries. Narrowing * on it — rather than on `event_type` — keeps a handler correct for a * lifecycle event added after it was written. A payload that fails this check * is not yet a paid invoice: test {@link isSubscriptionDisputeWebhook} next. * * @example * ```typescript * if (event.content.type === 'subscription_details') { * const object = event.content.object; * if (isSubscriptionLifecycleWebhook(object)) { * object.status; // SubscriptionStatus at the transition * object.cancel_at_period_end; // true for a scheduled cancellation * } else if (isSubscriptionDisputeWebhook(object)) { * object.amount; // what this chargeback took from the cycle * } else { * object.invoice; // the paid invoice * } * } * ``` */ declare function isSubscriptionLifecycleWebhook(object: SubscriptionWebhookContent): object is LifecycleWebhook; /** * Whether a `subscription_details` payload is a chargeback against a * subscription cycle ({@link SubscriptionDisputeWebhook}, sent as * `invoice_disputed`). * * It is the only `subscription_details` shape that carries * `connector_dispute_id`; neither a lifecycle snapshot nor a paid invoice does. */ declare function isSubscriptionDisputeWebhook(object: SubscriptionWebhookContent): object is SubscriptionDisputeWebhook; /** * A parsed and verified Delopay webhook event. * * Matches the signed wire body exactly: * `{ merchant_id, event_id, event_type, content: { type, object }, timestamp }`. * * - `event_type` identifies the event, e.g. `'payment_succeeded'`. * - `content.type` tags the payload kind, e.g. `'payment_details'`. * - `content.object` is the payload; narrow on `content.type` to type it. */ interface WebhookEvent { /** ID of the merchant that owns this event. */ merchant_id: string; /** Unique ID for this event (stable across delivery retries). */ event_id: string; /** Event type identifier, e.g. `'payment_succeeded'` or `'refund_succeeded'`. */ event_type: EventType; /** The event payload, tagged by kind. Narrow on `content.type` to type `object`. */ content: WebhookContent; /** ISO 8601 timestamp at which the webhook was sent. */ timestamp: string; } declare const Webhooks: { /** * Verify the signature of an incoming Delopay webhook and return the parsed event. * * Delopay signs each outgoing webhook with HMAC-SHA512 over the raw request body, * using your shop's webhook secret (the *payment response hash key* configured on * the shop). The hex-encoded digest is delivered in the `X-Webhook-Signature-512` * HTTP header. * * Uses the Web Crypto API (`globalThis.crypto.subtle`), so it runs unchanged in * Node 18+, modern browsers, Deno, Bun, and edge runtimes (Cloudflare Workers, Vercel Edge). * * Available as a static property on the `Delopay` class * (`Delopay.webhooks.verify`) and does not require a client instance. * * @param rawBody - The raw request body. Pass the original bytes (`Uint8Array` / * `Buffer`) when possible; if you pass a string, it must be the unmodified UTF-8 * text of the request body. Do **not** parse it before passing. * @param signatureHeader - The value of the `X-Webhook-Signature-512` HTTP header. * @param secret - Your shop's webhook signing secret. * @returns Promise that resolves to the parsed webhook event. * @throws {Error} When the signature header is malformed or does not match the body. * * @example * ```typescript * // Express example * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => { * try { * const event = await Delopay.webhooks.verify( * req.body, // Buffer from express.raw() * req.header('x-webhook-signature-512') ?? '', * process.env.DELOPAY_WEBHOOK_SECRET!, * ); * console.log(event.event_type, event.content.object); * res.sendStatus(200); * } catch { * res.status(400).send('Invalid signature'); * } * }); * ``` */ verify(rawBody: string | Uint8Array, signatureHeader: string, secret: string): Promise; }; /** A single condition leaf in the builder's condition tree. */ interface LeafNode { kind: 'leaf'; lhs: string; comparison: EuclidComparisonType; value: EuclidValue; } /** An AND (`all`) or OR (`any`) group of condition nodes. */ interface GroupNode { kind: 'all' | 'any'; children: ConditionNode[]; } type ConditionNode = LeafNode | GroupNode; /** * A condition leaf. Numeric values (amount, merchant_volume) tag as `number`; * string values (payment_method, connector, currency, card_network) tag as * `enum_variant`. */ declare function leaf(lhs: string, comparison: EuclidComparisonType, value: string | number): LeafNode; /** AND group — all children must match. */ declare function allOf(...children: ConditionNode[]): GroupNode; /** OR group — any child matching is enough. */ declare function anyOf(...children: ConditionNode[]): GroupNode; /** * How a rule (or the default) prices a transaction. `fee_type` is inferred: * percentage-only → `percentage`, flat-only → `flat`, both → `combined`. */ interface FeeSpecInput { /** Percentage fee, e.g. `2.5` means 2.5%. */ percentage?: number; /** Flat fee in minor units. */ flat?: number; /** ISO 4217 currency for the flat fee. */ flatCurrency?: string; /** Clamp floor in minor units. */ min?: number; /** Clamp ceiling in minor units. */ max?: number; } /** * Friendly conditions for a rule. Every provided key becomes one condition and * they are ANDed together. For dimensions not covered here (payment-method-type * keys like `crypto`/`wallet`, metadata, value arrays) use `rawConditions`. */ interface FeeRuleConditions { paymentMethod?: PaymentMethod; connector?: Connector; currency?: Currency; cardNetwork?: string; /** * Customer billing-address country. Must be the exact backend `Country` enum * variant (PascalCase full name, e.g. `Germany`/`UnitedStatesOfAmerica`), not * an ISO code — the engine lowers `billing_country` via case-sensitive * `from_str`. */ billingCountry?: string; /** `amount == n` (minor units). */ amountEquals?: number; /** `amount > n` (minor units). */ amountGreaterThan?: number; /** `amount < n` (minor units). */ amountLessThan?: number; /** * `merchant_volume == n` — the merchant's previous-month volume snapshot * (USD minor units). Combine with any other condition, e.g. * `{ paymentMethod: 'crypto', merchantVolumeGreaterThan: 1_000_000 }`. */ merchantVolumeEquals?: number; /** `merchant_volume > n` (USD minor units). */ merchantVolumeGreaterThan?: number; /** `merchant_volume < n` (USD minor units). */ merchantVolumeLessThan?: number; } interface FeeRuleInput { name: string; /** Friendly conditions (ANDed). Omit for an always-matching rule (prefer `otherwise`). */ when?: FeeRuleConditions; /** Extra raw conditions ANDed in, for dimensions `when` does not cover. */ rawConditions?: EuclidComparison[]; /** Nested AND/OR condition tree. Mutually exclusive with `when`/`rawConditions`. */ match?: ConditionNode; fee: FeeSpecInput; } /** * Decode a rule's `statements[]` back into a condition tree. * * Returns a tree that is **logically equivalent** to the source. It is * deep-equal to `normalizeNode(input)` only when no `all` group contains two * or more `any` groups; where the encoder distributed AND over OR, the decoded * shape differs (still equivalent). */ declare function ruleMatchToTree(statements: EuclidIfStatement[]): ConditionNode; /** * Decode a stored program into the builder's editable model. * * Returns a tree that is **logically equivalent** to the source. It is * deep-equal to `normalizeNode(input)` only when no `all` group contains two * or more `any` groups; where the encoder distributed AND over OR, the decoded * shape differs (still equivalent). */ declare function programToTree(program: PlatformFeeProgram): { rules: { name: string; match: ConditionNode; fee: PlatformFeeOutput | null; }[]; otherwise: PlatformFeeOutput | null; }; /** * Fluent builder for a platform fee-rule program. Emits the exact Euclid wire * shape (camelCase tree, tagged values, `metadata: {}` everywhere) so callers * never hand-write the AST. Rules are evaluated in order; the first match wins, * else `otherwise` (the default selection). * * @example * ```ts * const algorithm = feeProgram() * .rule({ name: 'crypto', when: { paymentMethod: 'crypto' }, fee: { percentage: 1.0 } }) * .rule({ * name: 'card_on_cryptomus', * when: { paymentMethod: 'card', connector: 'cryptomus' }, * fee: { percentage: 2.0 }, * }) * .otherwise({ percentage: 3.0 }) * .build(); * * await delopay.fees.rules.upsert({ algorithm }, merchantId); * ``` */ declare class FeeProgramBuilder { private readonly rules; private defaultFee; /** Append a rule. Provided `when`/`rawConditions` are ANDed. */ rule(input: FeeRuleInput): this; /** Set the default selection (applied when no rule matches). */ otherwise(fee: FeeSpecInput): this; /** Produce the wire-ready program. */ build(): PlatformFeeProgram; } /** Start building a platform fee-rule program. See {@link FeeProgramBuilder}. */ declare function feeProgram(): FeeProgramBuilder; /** * The card layouts the checkout can draw. * * Presentation only. Every one of them collects the card inside Airwallex's * own iframes and confirms the same intent, so the PCI scope, the guards and * the return leg are identical whichever the merchant picks — which is what * makes this a setting rather than a second rail. * * `fullFeaturedCard`, Airwallex's own all-in-one element, is deliberately not * on this list. It is a different element with a different theming API, and it * takes the layout away from us entirely; it needs its own decision, not a * seventh row in a list of geometries. */ declare const AIRWALLEX_CARD_PRESETS: readonly ["single_row", "split_fields", "split_fields_labelled", "stacked", "number_expiry_row", "compact"]; type AirwallexCardPreset = (typeof AIRWALLEX_CARD_PRESETS)[number]; /** * A field of the card form, named as Airwallex's `createElement` names it. * * `card` is the combined element — number, expiry and CVC in one iframe on one * line — and it never appears alongside the other three: a layout is either * the one element or the trio. */ type AirwallexCardField = 'card' | 'cardNumber' | 'expiry' | 'cvc'; interface AirwallexCardLayout { /** * The elements to create, in creation order. * * Order is load-bearing twice over: `confirm()` is called on the first one * (the combined element, or the number field, which coordinates its * siblings), and completion advances the caret to the next. */ readonly elements: readonly AirwallexCardField[]; /** * The rows to draw, top to bottom, each naming the fields on it left to * right. Fields sharing a row share its width evenly. * * Every field in `elements` appears exactly once across `rows`, and nothing * else does — `airwallexCardLayoutIsCoherent` is that claim, executable. */ readonly rows: readonly (readonly AirwallexCardField[])[]; /** * How a row's width is shared, when sharing it evenly is wrong. * * **Optional, and absent is the normal case**: a row with no weights splits * evenly, which is what every other preset wants — `stacked` is full-width * rows, `split_fields`, `split_fields_labelled` and `compact` pair expiry * and CVC, which are both short and genuinely want half each, and * `single_row` is one element. Only `number_expiry_row` puts two fields of * very different lengths on one line, and only it declares weights. * * Declared as weights rather than widths on purpose. A fixed width is right * for exactly the embed width it was tuned against and wrong either side of * it; a weight is a ratio and holds at any width. That is not hypothetical * — the defect that produced this field was a card-number placeholder * clipped at 460px, which no fixed width would have fixed for the merchant * whose embed is 380px. * * A field with no entry weighs 1. */ readonly weights?: Readonly>>; /** * Whether this preset captions its fields by default. * * The merchant can override it (`branding.airwallexLabelStyle`); this is * what the preset means when they have not. Captions are always visual: see * `AIRWALLEX_LABEL_STYLES`. */ readonly labels: boolean; /** * `compact` draws one step tighter and one step smaller than the merchant's * own `inputSize` and field type size, rather than replacing them — a * merchant who chose spacious spacing and this preset gets spacious-minus- * one, not the same form as a merchant who chose compact. */ readonly density: 'default' | 'compact'; } /** * Every preset's geometry, and the single source of it. * * Typed as a total `Record` on purpose. Adding a preset to * `AIRWALLEX_CARD_PRESETS` without adding its geometry here does not compile, * so the two cannot come apart in the direction that would leave a renderer * guessing. */ declare const AIRWALLEX_CARD_LAYOUTS: Record; /** * The preset that renders when nobody has chosen one. * * Not `DEFAULT_BRANDING.airwallexCardPreset`, which is the empty string: the * branding field means "the merchant's choice, in the shop's checkout * customizer", and empty means they have not made one. This is the last step * of the resolution, after the connector account's own setting. * See `resolveAirwallexCardPreset`. */ declare const AIRWALLEX_CARD_PRESET_FALLBACK: AirwallexCardPreset; /** * Which preset the buyer's card form is drawn from. * * Three sources, in this order, and the order is the migration story: * * 1. **The shop's branding.** Set in the checkout customizer, empty when the * merchant has not been there. Empty rather than pre-filled with the * default precisely so that (2) still means something — a branding field * that always held a value would silently override every connector * account already configured, which is a rendering change no merchant * asked for. * 2. **The connector account's `integration_style` metadata**, where this * setting lived before the customizer existed, projected onto the checkout * payload as `card_connector_integration_style`. Still authoritative for * every merchant who set it and has not overridden it here. Nothing * migrates it; it simply keeps working. * 3. **`split_fields_labelled`**, for a shop that has never set either. * * Step 3 is the one visible change: a shop with nothing configured used to get * the combined single row, and now gets the labelled split form. * * Anything unrecognised at either step falls through rather than failing. The * dashboard and the router deploy separately and may already offer a preset * this build has not heard of; a card form in the wrong layout is a cosmetic * disappointment, and no card form is a lost sale. */ declare function resolveAirwallexCardPreset(fromBranding: string | null | undefined, fromConnector: string | null | undefined): AirwallexCardPreset; /** The preset named, or `null` for anything this build does not know. */ declare function asAirwallexCardPreset(value: unknown): AirwallexCardPreset | null; /** The geometry for a preset. Total, by the type of `AIRWALLEX_CARD_LAYOUTS`. */ declare function airwallexCardLayout(preset: AirwallexCardPreset): AirwallexCardLayout; /** * How much of its row a field takes, relative to its neighbours. * * `1` unless the layout says otherwise, so a caller can multiply blindly and * an unweighted row divides evenly — which is what the CSS did before weights * existed, and therefore what every preset but one still gets. */ declare function airwallexCellWeight(layout: AirwallexCardLayout, field: AirwallexCardField): number; /** * Whether a layout's rows draw exactly the elements it creates. * * The one thing the `Record` type cannot check. An element created with no row * to mount into leaves its field permanently pending — the pane's own comment * for that case is "the form would sit on the skeleton until the deadline with * no reason given" — and a row naming a field with no element draws an empty * box the buyer can click into and never type in. Both are silent. * * Executed by the checkout's preview guard over every preset, which is where a * new one gets caught. */ declare function airwallexCardLayoutIsCoherent(layout: AirwallexCardLayout): boolean; /** * How the field's caption is drawn. * * Every one of these is **visual only**, and that is not a limitation we chose: * `for` cannot cross into a cross-origin iframe, so a `