OpportunitiesEntities
Docs
    Ring Platform

    Decentralized Self-building Future

    Sign In
    Entities
    Opportunities
    Store
    Docs
    Platform Concepts
    RING EconomySonoratek LLCGlobal ImpactAI Meets Web3
    Get Started
    Quick StartCalculatorRoadmap
    Privacy|Contact
    v1.104.26|Sonoratek LLC

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Credit Rewards
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    News Kingdom architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server
    OSS vs enterprise

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Credit Rewards
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    News Kingdom architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server
    OSS vs enterprise

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Credit Rewards
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    News Kingdom architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server
    OSS vs enterprise

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)
    Ring Logo

    Loading documentation...

    Preparing Ring content

    Ring Logo

    Loading documentation...

    Preparing Ring content

    Ring Logo

    Loading documentation...

    Preparing Ring content

    PaymentConductor

    PaymentConductor is Ring's config-driven payment layer. One ledger (payment_transactions) and one webhook dispatcher serve store, membership, news promotion, wallet credit top-up, and public pool (DAO jar) card/PayPal chip-ins. Browser UIs never open a PSP by brand — they follow Conductor CheckoutRedirect via followCheckoutResult (lib/payments/checkout-redirect.ts). Deep types and sequences: PaymentConductor architecture.

    Use Founder / Developer tabs in the docs sidebar to filter this page. audience frontmatter controls in-page blocks; sidebar article visibility is curated separately.

    Provider status (Conductor): WayForPay, Stripe, and PayPal are live external PSPs on /api/payments/{wayforpay|stripe|paypal}/webhook. Credit balance and native token settle in-process inside createCheckout (no PSP webhook). nft_gate may appear in ring-config.json fee tables — it is not a PaymentProcessorId on Conductor. Stripe Connect marketplace payouts remain backlog (see below). Prefer Payment integration for clone env wiring.

    Rail vs processor (comparison)

    Buyers pick a rail. Conductor persists a processor on the ledger row. Card rail resolves to WayForPay or Stripe via getPaymentProvider(purpose) — never from UI brand strings.

    Buyer rail (PaymentRail)Typical processor (PaymentProcessorId)Settlement
    cardwayforpay or stripeExternal webhook after HPP / Checkout
    paypalpaypalOrders v2 capture webhook (+ Subscriptions lifecycle for recurring membership)
    credit_balancecredit_balanceSynchronous in createCheckout
    native_tokennative_tokenSynchronous on-chain transfer → treasury
    Previous / UI habitRing equivalent
    Hard-coded WayForPay SDK in a pagePaymentConductor.createCheckout + followCheckoutResult
    Opening WFP HPP as a GET query URLCheckoutRedirect.mode = form_post (POST fields)
    Clone-local stripe-promotion-servicePlatform stripe.processor.ts + purpose handler
    Legacy /api/store/payments/wayforpay/webhook or /api/news/promotion/wayforpay-webhookStill may delegate — canonical is /api/payments/wayforpay/webhook
    “Top up buys RING”wallet_topup credits fiat points 1:1 via creditBalanceService.addFiatUsd — not on-chain RING

    Payment purposes

    PurposeHandler familyTypical processorsEntry
    store_orderhandlers/store-order*.tsWayForPay, Stripe, credit, native token, PayPalPOST /api/store/payments/{wayforpay|stripe|token|credit|paypal|card}
    membership_upgradehandlers/membership-upgrade*.ts (+ PayPal subscription handler)WayForPay, Stripe, PayPal, native tokenMembership payment actions / POST /api/membership/payment/*
    news_promotionhandlers/news-promotion*.tsWayForPay, Stripe, PayPalNews promotion submit
    wallet_topuphandlers/wallet-topup*.tsWayForPay, Stripe, PayPal

    Additional typed purposes (project_order, task_escrow, collective_order_slot, scheduled_service_slot) share the same Conductor + dispatcher pattern with matching handlers under lib/payments/conductor/handlers/.

    wallet_topup ≠ on-chain RING. public_pool_contribution converts charged fiat to pledged native via the Token Desk oracle (nativeUi = fiatMajor / nativePerMainCurrency) — never 1:1. See Public Pools.

    Recommended path

    Use Cursor skill international-payment-systems-specialist (or UA WayForPay desk commerce-ua-dropshipping-woocommerce-wayforpay-guru) with this starter prompt — review the plan before mutating env/secrets:

    “Configure Ring PaymentConductor for this clone: set PAYMENT_DEFAULT_PROCESSOR and purpose overrides, enable rails in ring-config.json payment.gateways, wire WayForPay/Stripe/PayPal credentials from env.local.template, register canonical webhooks under /api/payments/*/webhook, and smoke-test store + wallet_topup. Do not invent routes or fee contracts.”

    Inputs checklist: clone domain / public base URL · merchant PSP dashboard access · ring-config.json payment block · secrets only in env (never MDX) · pause for approval before writing production secrets.

    Operator walkthrough (non-agent): Payment integration · WayForPay.

    Manual path

    Why this matters for your clone

    • One money desk — store, membership, news, wallet top-up, and DAO jar chip-ins share ledger + webhook path.
    • Swap PSP without rewriting UI — change PAYMENT_DEFAULT_PROCESSOR / purpose env + gateways.*.enabled.
    • Zero-fee internal rails — credit and native token when enabled (PAYMENT_STORE_ALLOW_CREDIT, PAYMENT_STORE_ALLOW_TOKEN / public flags).
    • Admin visibility — GET /api/admin/users/[id]/payments lists ledger rows (defaults include membership_upgrade + wallet_topup).
    • Membership + store PayPal — live when NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true, PAYPAL_* set, and gateways.paypal.enabled. Recurring membership uses Subscriptions v1 — see SubscriptionConductor.

    Gateway fee rates (ring-config.json defaults)

    GatewayFee % (config)Fixed (config)CurrencyConductor status
    WayForPay2.5%—UAHLive webhook

    Implementation surface

    Root: lib/payments/conductor/, lib/payments/processors/, lib/payments/payment.config.ts.

    ModulePathRole
    Conductorlib/payments/conductor/payment-conductor.tscreateCheckout (+ normalizeCheckoutResult), handleWebhook
    Types / redirectlib/payments/conductor/types.tsPaymentPurpose, PaymentRail, PaymentProcessorId, CheckoutRedirect
    Client handofflib/payments/checkout-redirect.tsfollowCheckoutRedirect / followCheckoutResult
    Configlib/payments/payment.config.tsgetPaymentProvider, isRailEnabled, credit currency allow-list
    Processors

    Frequently asked questions

    Impact

    Will credit top-up mint RING?

    No. wallet_topup adds fiat credit points. On-chain RING buy paths are Token Desk / confidential onramp — not this purpose.

    Will swapping PAYMENT_DEFAULT_PROCESSOR rewrite store UI?

    No. UI follows rails + CheckoutRedirect. Processor is resolved server-side per purpose.

    Migration

    Do I keep legacy WayForPay webhook URLs?

    Prefer canonical /api/payments/wayforpay/webhook. Legacy store/news webhook routes may still delegate — point new PSP dashboard entries at the canonical path.

    What env name accepts UAH store orders when points are USD?

    PAYMENT_CREDIT_BALANCE_ACCEPTED_ORDER_CURRENCIES (comma-separated), or store.creditBalanceAcceptedOrderCurrencies in config — not the older PAYMENT_CREDIT_ACCEPT_ORDER_CURRENCY spelling.

    Ops

    Why did WayForPay checkout “open but fail”?

    HPP requires form POST (form_post). Opening the URL as GET breaks the session.

    What happens when a client retries with the same idempotency key?

    A paid row replays (same txHash, no second charge); an in-flight row returns 409 IDEMPOTENCY_IN_FLIGHT. Never retry with a fresh key after a timeout — reuse the intent key.

    Who can refund a native payment?

    Platform admin for any native row; a vendor can refund store_order native payments of their own store (order vendorSettlements match). Refunds are full-amount, idempotent, and mark only payment_transactions refunded.

    Where do admins see membership/top-up rows?

    GET /api/admin/users/[id]/payments (platform admin). Defaults focus on membership_upgrade and wallet_topup.

    Related documentation

    Related documentation

    Payments Overview

    Prerequisite: high-level rails before Conductor purpose details.

    Payment Gateway Integration

    Next-step: operator PSP dashboard and env wiring for a clone.

    Public Pools & DAO Jars

    Same-workflow: public_pool_contribution card jar, desk FX, and builder payout.

    WayForPay Payment Integration

    Deep-dive: WayForPay env, HMAC, and orderReference prefixes.

    1. Docs
    2. /Features
    3. /PaymentConductor

    Updated Aug 22, 20269 min listen

    PaymentConductor

    PaymentConductor is Ring's config-driven payment layer. One ledger (payment_transactions) and one webhook dispatcher serve store, membership, news promotion, wallet credit top-up, and public pool (DAO jar) card/PayPal chip-ins. Browser UIs never open a PSP by brand — they follow Conductor CheckoutRedirect via followCheckoutResult (lib/payments/checkout-redirect.ts). Deep types and sequences: PaymentConductor architecture.

    Use Founder / Developer tabs in the docs sidebar to filter this page. audience frontmatter controls in-page blocks; sidebar article visibility is curated separately.

    Provider status (Conductor): WayForPay, Stripe, and PayPal are live external PSPs on /api/payments/{wayforpay|stripe|paypal}/webhook. Credit balance and native token settle in-process inside createCheckout (no PSP webhook). nft_gate may appear in ring-config.json fee tables — it is not a PaymentProcessorId on Conductor. Stripe Connect marketplace payouts remain backlog (see below). Prefer Payment integration for clone env wiring.

    Rail vs processor (comparison)

    Buyers pick a rail. Conductor persists a processor on the ledger row. Card rail resolves to WayForPay or Stripe via getPaymentProvider(purpose) — never from UI brand strings.

    Buyer rail (PaymentRail)Typical processor (PaymentProcessorId)Settlement
    cardwayforpay or stripeExternal webhook after HPP / Checkout
    paypalpaypalOrders v2 capture webhook (+ Subscriptions lifecycle for recurring membership)
    credit_balancecredit_balanceSynchronous in createCheckout
    native_tokennative_tokenSynchronous on-chain transfer → treasury
    Previous / UI habitRing equivalent
    Hard-coded WayForPay SDK in a pagePaymentConductor.createCheckout + followCheckoutResult
    Opening WFP HPP as a GET query URLCheckoutRedirect.mode = form_post (POST fields)
    Clone-local stripe-promotion-servicePlatform stripe.processor.ts + purpose handler
    Legacy /api/store/payments/wayforpay/webhook or /api/news/promotion/wayforpay-webhookStill may delegate — canonical is /api/payments/wayforpay/webhook
    “Top up buys RING”wallet_topup credits fiat points 1:1 via creditBalanceService.addFiatUsd — not on-chain RING

    Payment purposes

    PurposeHandler familyTypical processorsEntry
    store_orderhandlers/store-order*.tsWayForPay, Stripe, credit, native token, PayPalPOST /api/store/payments/{wayforpay|stripe|token|credit|paypal|card}
    membership_upgradehandlers/membership-upgrade*.ts (+ PayPal subscription handler)WayForPay, Stripe, PayPal, native tokenMembership payment actions / POST /api/membership/payment/*
    news_promotionhandlers/news-promotion*.tsWayForPay, Stripe, PayPalNews promotion submit
    wallet_topuphandlers/wallet-topup*.tsWayForPay, Stripe, PayPal

    Additional typed purposes (project_order, task_escrow, collective_order_slot, scheduled_service_slot) share the same Conductor + dispatcher pattern with matching handlers under lib/payments/conductor/handlers/.

    wallet_topup ≠ on-chain RING. public_pool_contribution converts charged fiat to pledged native via the Token Desk oracle (nativeUi = fiatMajor / nativePerMainCurrency) — never 1:1. See Public Pools.

    Recommended path

    Use Cursor skill international-payment-systems-specialist (or UA WayForPay desk commerce-ua-dropshipping-woocommerce-wayforpay-guru) with this starter prompt — review the plan before mutating env/secrets:

    “Configure Ring PaymentConductor for this clone: set PAYMENT_DEFAULT_PROCESSOR and purpose overrides, enable rails in ring-config.json payment.gateways, wire WayForPay/Stripe/PayPal credentials from env.local.template, register canonical webhooks under /api/payments/*/webhook, and smoke-test store + wallet_topup. Do not invent routes or fee contracts.”

    Inputs checklist: clone domain / public base URL · merchant PSP dashboard access · ring-config.json payment block · secrets only in env (never MDX) · pause for approval before writing production secrets.

    Operator walkthrough (non-agent): Payment integration · WayForPay.

    Manual path

    Why this matters for your clone

    • One money desk — store, membership, news, wallet top-up, and DAO jar chip-ins share ledger + webhook path.
    • Swap PSP without rewriting UI — change PAYMENT_DEFAULT_PROCESSOR / purpose env + gateways.*.enabled.
    • Zero-fee internal rails — credit and native token when enabled (PAYMENT_STORE_ALLOW_CREDIT, PAYMENT_STORE_ALLOW_TOKEN / public flags).
    • Admin visibility — GET /api/admin/users/[id]/payments lists ledger rows (defaults include membership_upgrade + wallet_topup).
    • Membership + store PayPal — live when NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true, PAYPAL_* set, and gateways.paypal.enabled. Recurring membership uses Subscriptions v1 — see SubscriptionConductor.

    Gateway fee rates (ring-config.json defaults)

    GatewayFee % (config)Fixed (config)CurrencyConductor status
    WayForPay2.5%—UAHLive webhook

    Implementation surface

    Root: lib/payments/conductor/, lib/payments/processors/, lib/payments/payment.config.ts.

    ModulePathRole
    Conductorlib/payments/conductor/payment-conductor.tscreateCheckout (+ normalizeCheckoutResult), handleWebhook
    Types / redirectlib/payments/conductor/types.tsPaymentPurpose, PaymentRail, PaymentProcessorId, CheckoutRedirect
    Client handofflib/payments/checkout-redirect.tsfollowCheckoutRedirect / followCheckoutResult
    Configlib/payments/payment.config.tsgetPaymentProvider, isRailEnabled, credit currency allow-list
    Processors

    Frequently asked questions

    Impact

    Will credit top-up mint RING?

    No. wallet_topup adds fiat credit points. On-chain RING buy paths are Token Desk / confidential onramp — not this purpose.

    Will swapping PAYMENT_DEFAULT_PROCESSOR rewrite store UI?

    No. UI follows rails + CheckoutRedirect. Processor is resolved server-side per purpose.

    Migration

    Do I keep legacy WayForPay webhook URLs?

    Prefer canonical /api/payments/wayforpay/webhook. Legacy store/news webhook routes may still delegate — point new PSP dashboard entries at the canonical path.

    What env name accepts UAH store orders when points are USD?

    PAYMENT_CREDIT_BALANCE_ACCEPTED_ORDER_CURRENCIES (comma-separated), or store.creditBalanceAcceptedOrderCurrencies in config — not the older PAYMENT_CREDIT_ACCEPT_ORDER_CURRENCY spelling.

    Ops

    Why did WayForPay checkout “open but fail”?

    HPP requires form POST (form_post). Opening the URL as GET breaks the session.

    What happens when a client retries with the same idempotency key?

    A paid row replays (same txHash, no second charge); an in-flight row returns 409 IDEMPOTENCY_IN_FLIGHT. Never retry with a fresh key after a timeout — reuse the intent key.

    Who can refund a native payment?

    Platform admin for any native row; a vendor can refund store_order native payments of their own store (order vendorSettlements match). Refunds are full-amount, idempotent, and mark only payment_transactions refunded.

    Where do admins see membership/top-up rows?

    GET /api/admin/users/[id]/payments (platform admin). Defaults focus on membership_upgrade and wallet_topup.

    Related documentation

    Related documentation

    Payments Overview

    Prerequisite: high-level rails before Conductor purpose details.

    Payment Gateway Integration

    Next-step: operator PSP dashboard and env wiring for a clone.

    Public Pools & DAO Jars

    Same-workflow: public_pool_contribution card jar, desk FX, and builder payout.

    WayForPay Payment Integration

    Deep-dive: WayForPay env, HMAC, and orderReference prefixes.

    1. Docs
    2. /Features
    3. /PaymentConductor

    Updated Aug 22, 20269 min listen

    PaymentConductor

    PaymentConductor is Ring's config-driven payment layer. One ledger (payment_transactions) and one webhook dispatcher serve store, membership, news promotion, wallet credit top-up, and public pool (DAO jar) card/PayPal chip-ins. Browser UIs never open a PSP by brand — they follow Conductor CheckoutRedirect via followCheckoutResult (lib/payments/checkout-redirect.ts). Deep types and sequences: PaymentConductor architecture.

    Use Founder / Developer tabs in the docs sidebar to filter this page. audience frontmatter controls in-page blocks; sidebar article visibility is curated separately.

    Provider status (Conductor): WayForPay, Stripe, and PayPal are live external PSPs on /api/payments/{wayforpay|stripe|paypal}/webhook. Credit balance and native token settle in-process inside createCheckout (no PSP webhook). nft_gate may appear in ring-config.json fee tables — it is not a PaymentProcessorId on Conductor. Stripe Connect marketplace payouts remain backlog (see below). Prefer Payment integration for clone env wiring.

    Rail vs processor (comparison)

    Buyers pick a rail. Conductor persists a processor on the ledger row. Card rail resolves to WayForPay or Stripe via getPaymentProvider(purpose) — never from UI brand strings.

    Buyer rail (PaymentRail)Typical processor (PaymentProcessorId)Settlement
    cardwayforpay or stripeExternal webhook after HPP / Checkout
    paypalpaypalOrders v2 capture webhook (+ Subscriptions lifecycle for recurring membership)
    credit_balancecredit_balanceSynchronous in createCheckout
    native_tokennative_tokenSynchronous on-chain transfer → treasury
    Previous / UI habitRing equivalent
    Hard-coded WayForPay SDK in a pagePaymentConductor.createCheckout + followCheckoutResult
    Opening WFP HPP as a GET query URLCheckoutRedirect.mode = form_post (POST fields)
    Clone-local stripe-promotion-servicePlatform stripe.processor.ts + purpose handler
    Legacy /api/store/payments/wayforpay/webhook or /api/news/promotion/wayforpay-webhookStill may delegate — canonical is /api/payments/wayforpay/webhook
    “Top up buys RING”wallet_topup credits fiat points 1:1 via creditBalanceService.addFiatUsd — not on-chain RING

    Payment purposes

    PurposeHandler familyTypical processorsEntry
    store_orderhandlers/store-order*.tsWayForPay, Stripe, credit, native token, PayPalPOST /api/store/payments/{wayforpay|stripe|token|credit|paypal|card}
    membership_upgradehandlers/membership-upgrade*.ts (+ PayPal subscription handler)WayForPay, Stripe, PayPal, native tokenMembership payment actions / POST /api/membership/payment/*
    news_promotionhandlers/news-promotion*.tsWayForPay, Stripe, PayPalNews promotion submit
    wallet_topuphandlers/wallet-topup*.tsWayForPay, Stripe, PayPal

    Additional typed purposes (project_order, task_escrow, collective_order_slot, scheduled_service_slot) share the same Conductor + dispatcher pattern with matching handlers under lib/payments/conductor/handlers/.

    wallet_topup ≠ on-chain RING. public_pool_contribution converts charged fiat to pledged native via the Token Desk oracle (nativeUi = fiatMajor / nativePerMainCurrency) — never 1:1. See Public Pools.

    Recommended path

    Use Cursor skill international-payment-systems-specialist (or UA WayForPay desk commerce-ua-dropshipping-woocommerce-wayforpay-guru) with this starter prompt — review the plan before mutating env/secrets:

    “Configure Ring PaymentConductor for this clone: set PAYMENT_DEFAULT_PROCESSOR and purpose overrides, enable rails in ring-config.json payment.gateways, wire WayForPay/Stripe/PayPal credentials from env.local.template, register canonical webhooks under /api/payments/*/webhook, and smoke-test store + wallet_topup. Do not invent routes or fee contracts.”

    Inputs checklist: clone domain / public base URL · merchant PSP dashboard access · ring-config.json payment block · secrets only in env (never MDX) · pause for approval before writing production secrets.

    Operator walkthrough (non-agent): Payment integration · WayForPay.

    Manual path

    Why this matters for your clone

    • One money desk — store, membership, news, wallet top-up, and DAO jar chip-ins share ledger + webhook path.
    • Swap PSP without rewriting UI — change PAYMENT_DEFAULT_PROCESSOR / purpose env + gateways.*.enabled.
    • Zero-fee internal rails — credit and native token when enabled (PAYMENT_STORE_ALLOW_CREDIT, PAYMENT_STORE_ALLOW_TOKEN / public flags).
    • Admin visibility — GET /api/admin/users/[id]/payments lists ledger rows (defaults include membership_upgrade + wallet_topup).
    • Membership + store PayPal — live when NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true, PAYPAL_* set, and gateways.paypal.enabled. Recurring membership uses Subscriptions v1 — see SubscriptionConductor.

    Gateway fee rates (ring-config.json defaults)

    GatewayFee % (config)Fixed (config)CurrencyConductor status
    WayForPay2.5%—UAHLive webhook

    Implementation surface

    Root: lib/payments/conductor/, lib/payments/processors/, lib/payments/payment.config.ts.

    ModulePathRole
    Conductorlib/payments/conductor/payment-conductor.tscreateCheckout (+ normalizeCheckoutResult), handleWebhook
    Types / redirectlib/payments/conductor/types.tsPaymentPurpose, PaymentRail, PaymentProcessorId, CheckoutRedirect
    Client handofflib/payments/checkout-redirect.tsfollowCheckoutRedirect / followCheckoutResult
    Configlib/payments/payment.config.tsgetPaymentProvider, isRailEnabled, credit currency allow-list
    Processors

    Frequently asked questions

    Impact

    Will credit top-up mint RING?

    No. wallet_topup adds fiat credit points. On-chain RING buy paths are Token Desk / confidential onramp — not this purpose.

    Will swapping PAYMENT_DEFAULT_PROCESSOR rewrite store UI?

    No. UI follows rails + CheckoutRedirect. Processor is resolved server-side per purpose.

    Migration

    Do I keep legacy WayForPay webhook URLs?

    Prefer canonical /api/payments/wayforpay/webhook. Legacy store/news webhook routes may still delegate — point new PSP dashboard entries at the canonical path.

    What env name accepts UAH store orders when points are USD?

    PAYMENT_CREDIT_BALANCE_ACCEPTED_ORDER_CURRENCIES (comma-separated), or store.creditBalanceAcceptedOrderCurrencies in config — not the older PAYMENT_CREDIT_ACCEPT_ORDER_CURRENCY spelling.

    Ops

    Why did WayForPay checkout “open but fail”?

    HPP requires form POST (form_post). Opening the URL as GET breaks the session.

    What happens when a client retries with the same idempotency key?

    A paid row replays (same txHash, no second charge); an in-flight row returns 409 IDEMPOTENCY_IN_FLIGHT. Never retry with a fresh key after a timeout — reuse the intent key.

    Who can refund a native payment?

    Platform admin for any native row; a vendor can refund store_order native payments of their own store (order vendorSettlements match). Refunds are full-amount, idempotent, and mark only payment_transactions refunded.

    Where do admins see membership/top-up rows?

    GET /api/admin/users/[id]/payments (platform admin). Defaults focus on membership_upgrade and wallet_topup.

    Related documentation

    Related documentation

    Payments Overview

    Prerequisite: high-level rails before Conductor purpose details.

    Payment Gateway Integration

    Next-step: operator PSP dashboard and env wiring for a clone.

    Public Pools & DAO Jars

    Same-workflow: public_pool_contribution card jar, desk FX, and builder payout.

    WayForPay Payment Integration

    Deep-dive: WayForPay env, HMAC, and orderReference prefixes.

    1. Docs
    2. /Features
    3. /PaymentConductor

    Updated Aug 22, 20269 min listen

    WalletConductor
    initiateTopUp
    native_token_onramphandlers/native-token-onramp*.tsWayForPay / Stripe (PayPal unsupported)WalletConductor initiateNativeOnramp (confidential+)
    public_pool_contributionhandlers/public-pool-contribution.tsWayForPay, Stripe, PayPalPOST /api/public-pools/[slug]/card-checkout
    1. 1

      Pick default card processor

      Set PAYMENT_DEFAULT_PROCESSOR=wayforpay (UA/UAH-first) or stripe (USD-first). Optional purpose overrides: PAYMENT_STORE_PROCESSOR, PAYMENT_MEMBERSHIP_PROCESSOR, PAYMENT_NEWS_PROCESSOR, PAYMENT_WALLET_TOPUP_PROCESSOR, PAYMENT_NATIVE_TOKEN_ONRAMP_PROCESSOR, PAYMENT_PUBLIC_POOL_CONTRIBUTION_PROCESSOR (blank → default).

    2. 2

      Enable gateways and rails in config

      In ring-config.json, set payment.cardPaymentProcessor, payment.supportedMethods, and payment.gateways.*.enabled. Fee % / feeFixedCents there are clone config defaults for display/estimation — not a guarantee of the PSP’s live schedule.

    3. 3

      Wire credentials

      From env.local.template: WAYFORPAY_MERCHANT_ACCOUNT + WAYFORPAY_SECRET_KEY (+ WAYFORPAY_MERCHANT_PASSWORD for recurring/regularApi); or STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET; for PayPal PAYPAL_CLIENT_ID / PAYPAL_CLIENT_SECRET / PAYPAL_WEBHOOK_ID (+ membership plan ids when using Subscriptions). There is no WAYFORPAY_MERCHANT_ID.

    4. 4

      Register canonical webhooks

      Point each PSP dashboard to:

      EndpointProvider verify
      POST /api/payments/wayforpay/webhookWayForPay HMAC → dispatchWayForPayWebhook
      POST /api/payments/stripe/webhookSTRIPE_WEBHOOK_SECRET → dispatchStripeWebhook
      POST /api/payments/paypal/webhookTransmission signature + PAYPAL_WEBHOOK_ID → dispatchPayPalWebhook

      Apply data/migrations/004_payment_transactions.sql before production ledger use.

    5. 5

      Smoke checkout handoff

      Create a checkout through Conductor; UI must call followCheckoutResult / followCheckoutRedirect. Expect navigate (Stripe/PayPal/invoice URL) or form_post (WayForPay HPP). Confirm ledger row → handler fulfillment after webhook (or sync mark-paid for credit/token).

    Stripe2.9%$0.30 (feeFixedCents: 30)USDLive webhook
    Credit balance0%—main/fiat pointsLive sync
    Native token0%—token symbolLive sync
    PayPal2.9%$0.30USDLive Orders (+ membership Subscriptions)

    Quick setup sketch

    lib/payments/processors/{wayforpay,stripe,paypal,credit-balance,native-token}.processor.ts
    Per-rail checkout
    Dispatcherlib/payments/conductor/webhook-dispatcher.tsPurpose routing + PayPal Subscriptions branch
    Handlerslib/payments/conductor/handlers/*.tsFulfillment after paid

    PaymentProcessorId: wayforpay | stripe | paypal | credit_balance | native_token.

    Webhook ingress (verified)

    Route fileDispatch
    app/api/payments/wayforpay/webhook/route.tsdispatchWayForPayWebhook
    app/api/payments/stripe/webhook/route.tsdispatchStripeWebhook
    app/api/payments/paypal/webhook/route.tsdispatchPayPalWebhook

    Store routes (verified)

    RouteRailNotes
    POST /api/store/payments/wayforpaycardcreateCheckout({ purpose: 'store_order' })
    POST /api/store/payments/stripecardStripe Checkout (navigate)
    POST /api/store/payments/cardcardAlias of card path
    POST /api/store/payments/tokennative_tokenRequires PAYMENT_STORE_ALLOW_TOKEN=true
    POST /api/store/payments/creditcredit_balanceSync debit; currency allow-list via PAYMENT_CREDIT_BALANCE_ACCEPTED_ORDER_CURRENCIES / config
    POST /api/store/payments/paypalpaypalOrders v2; credentials + gateways.paypal.enabled

    Native token — checkout, idempotency, refunds (verified)

    Store checkout UI. The token rail is enabled in features/store/components/checkout/payment-step.tsx via getClientStorePaymentRails(): NEXT_PUBLIC_PAYMENT_STORE_ALLOW_TOKEN === 'true' and payment.gateways.native_token.enabled !== false. Server-side gate stays isRailEnabled('store_order','native_token') (PAYMENT_STORE_ALLOW_TOKEN=true). The checkout flow posts placeAndPayStoreOrder (server action) or POST /api/store/payments/token — both route through PaymentConductor.createCheckout({ purpose: 'store_order', rail: 'native_token' }) → createNativeTokenCheckout (balance check → treasury SPL transfer → payment_transactions createPending → markPaid).

    Membership native pay. POST /api/membership/payment/token is the single chokepoint (UI: components/membership/ring-payment-modal.tsx). Gate: isRailEnabled('membership_upgrade','native_token') reads isPaymentMethodEnabled('native_token') (payment.supportedMethods SSOT) — not the store flag, so membership native works with PAYMENT_STORE_ALLOW_TOKEN unset. Composition (ledger parity with card):

    PathMoney movementLedger
    Soft launch (no Membership program)PaymentConductor.createCheckout({ purpose:'membership_upgrade', rail:'native_token', metadata:{ tokenAmount } }) → treasury SPLpayment_transactions (paid) then SubscriptionConductor ledger-only via metadata.tx_hash — no second transfer, role upgrade included
    Deployed (on-chain RingMembership)createOnchainSubscription (program deducts)payment_transactions row written manually with txSignature
    membership_fee one-shotTreasury SPL (custom toAddress allowed)payment_transactions row; no subscription_ledger
    subscription_renewalPricing default amount (custom amount is not honored)subscription_ledger + payment_transactions row

    Idempotency contract (same as desk orders / NFT listings / public-pool contributions). Clients generate one UUID per payment intent (crypto.randomUUID() in checkout-client.tsx / ring-payment-modal.tsx) and reuse it on retry. payment_transactions rows carry a top-level idempotency_key; before charging, findByIdempotencyKey(userId, purpose, key) replays a paid row (same txHash, zero treasury movement) or returns 409 IDEMPOTENCY_IN_FLIGHT for an in-flight row. Old clients without a key keep the previous behavior.

    Native refunds. POST /api/admin/payments/[orderReference]/refund — platform admin (any native payment) or vendor who owns the store order (order vendorSettlements ∩ getVendorEntities). Idempotent by orderReference (already-refunded replays refundTxHash); full-amount only; treasury→user via transferTokenFromTreasury; ledger gets status refunded + refundTxHash in processor_payload. Transfer failure keeps the row paid (retryable). Minimal scope — no subscription_ledger / order-status flip.

    Ledger precision. Native-token rows store amount_minor in token precision (tokenAmount × 1e6, currency = token symbol); the main-currency stamp is skipped for token-denominated rows (fx_rate remains valid) — see stampMainCurrencyFx.

    Wallet top-up (verified)

    initiateCreditTopupPayment (app/_actions/wallet.ts) → WalletConductor.initiateTopUp → purpose wallet_topup → webhook wallet-topup*.ts → creditBalanceService.addFiatUsd (1:1 points). Distinct from native_token spend and from confidential native_token_onramp.

    Public pool jar (verified)

    POST /api/public-pools/[slug]/card-checkout → public_pool_contribution → desk FX → settlePublicPoolCardContribution. Native SPL chip-ins stay on contributeToPool (not Conductor).

    Membership PayPal (verified)

    POST /api/membership/payment/paypal → SubscriptionConductor provider paypal: recurring → Subscriptions v1 lifecycle webhooks; one-shot → Conductor Orders capture → recordPaidSubscription. UI: components/membership/payment-modal.tsx when public PayPal flag is on.

    Ledger

    data/migrations/004_payment_transactions.sql. Rows via DatabaseService / payment transaction service — result contract { success, data, error }.

    PaymentConductor architecture

    Deep-dive: CheckoutRedirect DTO, dispatcher sequences, and API route map.

    SubscriptionConductor

    Next-step: PayPal Subscriptions v1 for recurring membership — and native_token provider soft-launch vs on-chain paths.

    Admin API

    Next-step: admin native refund endpoint and payments tab.

    WalletConductor

    Same-workflow: initiateTopUp / onramp entry points into Conductor purposes.

    Wallet & Credit System

    See-also: credit top-up vs Token Desk oracle (jar FX shares desk rate).

    Ring Oracle

    Depends-on: Ring Oracle supplies desk, FX, and credit rates Conductor rails consume.

    Ring ERP

    Next-step: ERP hub cockpits after payment lands.

    Inventory & Stock

    Next-step: store paid handlers call commitSaleForOrder for stock invariant.

    Commissions & Settlements

    Same-workflow: processSettlements after paid store order.

    json
    
    {
      "payment": {
        "cardPaymentProcessor": "wayforpay",
        "supportedMethods": ["wayforpay", "credit_balance", "native_token"],
        "gateways": {
          "wayforpay": { "enabled": true, "feePercent": 2.5, "currency": "UAH" },
          "stripe": { "enabled": false, "feePercent": 2.9, "feeFixedCents": 30, "currency": "USD" },
          "paypal": { "enabled": false, "feePercent": 2.9, "feeFixedCents": 30, "currency": "USD" }
        }
      }
    }
    WalletConductor
    initiateTopUp
    native_token_onramphandlers/native-token-onramp*.tsWayForPay / Stripe (PayPal unsupported)WalletConductor initiateNativeOnramp (confidential+)
    public_pool_contributionhandlers/public-pool-contribution.tsWayForPay, Stripe, PayPalPOST /api/public-pools/[slug]/card-checkout
    1. 1

      Pick default card processor

      Set PAYMENT_DEFAULT_PROCESSOR=wayforpay (UA/UAH-first) or stripe (USD-first). Optional purpose overrides: PAYMENT_STORE_PROCESSOR, PAYMENT_MEMBERSHIP_PROCESSOR, PAYMENT_NEWS_PROCESSOR, PAYMENT_WALLET_TOPUP_PROCESSOR, PAYMENT_NATIVE_TOKEN_ONRAMP_PROCESSOR, PAYMENT_PUBLIC_POOL_CONTRIBUTION_PROCESSOR (blank → default).

    2. 2

      Enable gateways and rails in config

      In ring-config.json, set payment.cardPaymentProcessor, payment.supportedMethods, and payment.gateways.*.enabled. Fee % / feeFixedCents there are clone config defaults for display/estimation — not a guarantee of the PSP’s live schedule.

    3. 3

      Wire credentials

      From env.local.template: WAYFORPAY_MERCHANT_ACCOUNT + WAYFORPAY_SECRET_KEY (+ WAYFORPAY_MERCHANT_PASSWORD for recurring/regularApi); or STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET; for PayPal PAYPAL_CLIENT_ID / PAYPAL_CLIENT_SECRET / PAYPAL_WEBHOOK_ID (+ membership plan ids when using Subscriptions). There is no WAYFORPAY_MERCHANT_ID.

    4. 4

      Register canonical webhooks

      Point each PSP dashboard to:

      EndpointProvider verify
      POST /api/payments/wayforpay/webhookWayForPay HMAC → dispatchWayForPayWebhook
      POST /api/payments/stripe/webhookSTRIPE_WEBHOOK_SECRET → dispatchStripeWebhook
      POST /api/payments/paypal/webhookTransmission signature + PAYPAL_WEBHOOK_ID → dispatchPayPalWebhook

      Apply data/migrations/004_payment_transactions.sql before production ledger use.

    5. 5

      Smoke checkout handoff

      Create a checkout through Conductor; UI must call followCheckoutResult / followCheckoutRedirect. Expect navigate (Stripe/PayPal/invoice URL) or form_post (WayForPay HPP). Confirm ledger row → handler fulfillment after webhook (or sync mark-paid for credit/token).

    Stripe2.9%$0.30 (feeFixedCents: 30)USDLive webhook
    Credit balance0%—main/fiat pointsLive sync
    Native token0%—token symbolLive sync
    PayPal2.9%$0.30USDLive Orders (+ membership Subscriptions)

    Quick setup sketch

    lib/payments/processors/{wayforpay,stripe,paypal,credit-balance,native-token}.processor.ts
    Per-rail checkout
    Dispatcherlib/payments/conductor/webhook-dispatcher.tsPurpose routing + PayPal Subscriptions branch
    Handlerslib/payments/conductor/handlers/*.tsFulfillment after paid

    PaymentProcessorId: wayforpay | stripe | paypal | credit_balance | native_token.

    Webhook ingress (verified)

    Route fileDispatch
    app/api/payments/wayforpay/webhook/route.tsdispatchWayForPayWebhook
    app/api/payments/stripe/webhook/route.tsdispatchStripeWebhook
    app/api/payments/paypal/webhook/route.tsdispatchPayPalWebhook

    Store routes (verified)

    RouteRailNotes
    POST /api/store/payments/wayforpaycardcreateCheckout({ purpose: 'store_order' })
    POST /api/store/payments/stripecardStripe Checkout (navigate)
    POST /api/store/payments/cardcardAlias of card path
    POST /api/store/payments/tokennative_tokenRequires PAYMENT_STORE_ALLOW_TOKEN=true
    POST /api/store/payments/creditcredit_balanceSync debit; currency allow-list via PAYMENT_CREDIT_BALANCE_ACCEPTED_ORDER_CURRENCIES / config
    POST /api/store/payments/paypalpaypalOrders v2; credentials + gateways.paypal.enabled

    Native token — checkout, idempotency, refunds (verified)

    Store checkout UI. The token rail is enabled in features/store/components/checkout/payment-step.tsx via getClientStorePaymentRails(): NEXT_PUBLIC_PAYMENT_STORE_ALLOW_TOKEN === 'true' and payment.gateways.native_token.enabled !== false. Server-side gate stays isRailEnabled('store_order','native_token') (PAYMENT_STORE_ALLOW_TOKEN=true). The checkout flow posts placeAndPayStoreOrder (server action) or POST /api/store/payments/token — both route through PaymentConductor.createCheckout({ purpose: 'store_order', rail: 'native_token' }) → createNativeTokenCheckout (balance check → treasury SPL transfer → payment_transactions createPending → markPaid).

    Membership native pay. POST /api/membership/payment/token is the single chokepoint (UI: components/membership/ring-payment-modal.tsx). Gate: isRailEnabled('membership_upgrade','native_token') reads isPaymentMethodEnabled('native_token') (payment.supportedMethods SSOT) — not the store flag, so membership native works with PAYMENT_STORE_ALLOW_TOKEN unset. Composition (ledger parity with card):

    PathMoney movementLedger
    Soft launch (no Membership program)PaymentConductor.createCheckout({ purpose:'membership_upgrade', rail:'native_token', metadata:{ tokenAmount } }) → treasury SPLpayment_transactions (paid) then SubscriptionConductor ledger-only via metadata.tx_hash — no second transfer, role upgrade included
    Deployed (on-chain RingMembership)createOnchainSubscription (program deducts)payment_transactions row written manually with txSignature
    membership_fee one-shotTreasury SPL (custom toAddress allowed)payment_transactions row; no subscription_ledger
    subscription_renewalPricing default amount (custom amount is not honored)subscription_ledger + payment_transactions row

    Idempotency contract (same as desk orders / NFT listings / public-pool contributions). Clients generate one UUID per payment intent (crypto.randomUUID() in checkout-client.tsx / ring-payment-modal.tsx) and reuse it on retry. payment_transactions rows carry a top-level idempotency_key; before charging, findByIdempotencyKey(userId, purpose, key) replays a paid row (same txHash, zero treasury movement) or returns 409 IDEMPOTENCY_IN_FLIGHT for an in-flight row. Old clients without a key keep the previous behavior.

    Native refunds. POST /api/admin/payments/[orderReference]/refund — platform admin (any native payment) or vendor who owns the store order (order vendorSettlements ∩ getVendorEntities). Idempotent by orderReference (already-refunded replays refundTxHash); full-amount only; treasury→user via transferTokenFromTreasury; ledger gets status refunded + refundTxHash in processor_payload. Transfer failure keeps the row paid (retryable). Minimal scope — no subscription_ledger / order-status flip.

    Ledger precision. Native-token rows store amount_minor in token precision (tokenAmount × 1e6, currency = token symbol); the main-currency stamp is skipped for token-denominated rows (fx_rate remains valid) — see stampMainCurrencyFx.

    Wallet top-up (verified)

    initiateCreditTopupPayment (app/_actions/wallet.ts) → WalletConductor.initiateTopUp → purpose wallet_topup → webhook wallet-topup*.ts → creditBalanceService.addFiatUsd (1:1 points). Distinct from native_token spend and from confidential native_token_onramp.

    Public pool jar (verified)

    POST /api/public-pools/[slug]/card-checkout → public_pool_contribution → desk FX → settlePublicPoolCardContribution. Native SPL chip-ins stay on contributeToPool (not Conductor).

    Membership PayPal (verified)

    POST /api/membership/payment/paypal → SubscriptionConductor provider paypal: recurring → Subscriptions v1 lifecycle webhooks; one-shot → Conductor Orders capture → recordPaidSubscription. UI: components/membership/payment-modal.tsx when public PayPal flag is on.

    Ledger

    data/migrations/004_payment_transactions.sql. Rows via DatabaseService / payment transaction service — result contract { success, data, error }.

    PaymentConductor architecture

    Deep-dive: CheckoutRedirect DTO, dispatcher sequences, and API route map.

    SubscriptionConductor

    Next-step: PayPal Subscriptions v1 for recurring membership — and native_token provider soft-launch vs on-chain paths.

    Admin API

    Next-step: admin native refund endpoint and payments tab.

    WalletConductor

    Same-workflow: initiateTopUp / onramp entry points into Conductor purposes.

    Wallet & Credit System

    See-also: credit top-up vs Token Desk oracle (jar FX shares desk rate).

    Ring Oracle

    Depends-on: Ring Oracle supplies desk, FX, and credit rates Conductor rails consume.

    Ring ERP

    Next-step: ERP hub cockpits after payment lands.

    Inventory & Stock

    Next-step: store paid handlers call commitSaleForOrder for stock invariant.

    Commissions & Settlements

    Same-workflow: processSettlements after paid store order.

    json
    
    {
      "payment": {
        "cardPaymentProcessor": "wayforpay",
        "supportedMethods": ["wayforpay", "credit_balance", "native_token"],
        "gateways": {
          "wayforpay": { "enabled": true, "feePercent": 2.5, "currency": "UAH" },
          "stripe": { "enabled": false, "feePercent": 2.9, "feeFixedCents": 30, "currency": "USD" },
          "paypal": { "enabled": false, "feePercent": 2.9, "feeFixedCents": 30, "currency": "USD" }
        }
      }
    }
    WalletConductor
    initiateTopUp
    native_token_onramphandlers/native-token-onramp*.tsWayForPay / Stripe (PayPal unsupported)WalletConductor initiateNativeOnramp (confidential+)
    public_pool_contributionhandlers/public-pool-contribution.tsWayForPay, Stripe, PayPalPOST /api/public-pools/[slug]/card-checkout
    1. 1

      Pick default card processor

      Set PAYMENT_DEFAULT_PROCESSOR=wayforpay (UA/UAH-first) or stripe (USD-first). Optional purpose overrides: PAYMENT_STORE_PROCESSOR, PAYMENT_MEMBERSHIP_PROCESSOR, PAYMENT_NEWS_PROCESSOR, PAYMENT_WALLET_TOPUP_PROCESSOR, PAYMENT_NATIVE_TOKEN_ONRAMP_PROCESSOR, PAYMENT_PUBLIC_POOL_CONTRIBUTION_PROCESSOR (blank → default).

    2. 2

      Enable gateways and rails in config

      In ring-config.json, set payment.cardPaymentProcessor, payment.supportedMethods, and payment.gateways.*.enabled. Fee % / feeFixedCents there are clone config defaults for display/estimation — not a guarantee of the PSP’s live schedule.

    3. 3

      Wire credentials

      From env.local.template: WAYFORPAY_MERCHANT_ACCOUNT + WAYFORPAY_SECRET_KEY (+ WAYFORPAY_MERCHANT_PASSWORD for recurring/regularApi); or STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET; for PayPal PAYPAL_CLIENT_ID / PAYPAL_CLIENT_SECRET / PAYPAL_WEBHOOK_ID (+ membership plan ids when using Subscriptions). There is no WAYFORPAY_MERCHANT_ID.

    4. 4

      Register canonical webhooks

      Point each PSP dashboard to:

      EndpointProvider verify
      POST /api/payments/wayforpay/webhookWayForPay HMAC → dispatchWayForPayWebhook
      POST /api/payments/stripe/webhookSTRIPE_WEBHOOK_SECRET → dispatchStripeWebhook
      POST /api/payments/paypal/webhookTransmission signature + PAYPAL_WEBHOOK_ID → dispatchPayPalWebhook

      Apply data/migrations/004_payment_transactions.sql before production ledger use.

    5. 5

      Smoke checkout handoff

      Create a checkout through Conductor; UI must call followCheckoutResult / followCheckoutRedirect. Expect navigate (Stripe/PayPal/invoice URL) or form_post (WayForPay HPP). Confirm ledger row → handler fulfillment after webhook (or sync mark-paid for credit/token).

    Stripe2.9%$0.30 (feeFixedCents: 30)USDLive webhook
    Credit balance0%—main/fiat pointsLive sync
    Native token0%—token symbolLive sync
    PayPal2.9%$0.30USDLive Orders (+ membership Subscriptions)

    Quick setup sketch

    lib/payments/processors/{wayforpay,stripe,paypal,credit-balance,native-token}.processor.ts
    Per-rail checkout
    Dispatcherlib/payments/conductor/webhook-dispatcher.tsPurpose routing + PayPal Subscriptions branch
    Handlerslib/payments/conductor/handlers/*.tsFulfillment after paid

    PaymentProcessorId: wayforpay | stripe | paypal | credit_balance | native_token.

    Webhook ingress (verified)

    Route fileDispatch
    app/api/payments/wayforpay/webhook/route.tsdispatchWayForPayWebhook
    app/api/payments/stripe/webhook/route.tsdispatchStripeWebhook
    app/api/payments/paypal/webhook/route.tsdispatchPayPalWebhook

    Store routes (verified)

    RouteRailNotes
    POST /api/store/payments/wayforpaycardcreateCheckout({ purpose: 'store_order' })
    POST /api/store/payments/stripecardStripe Checkout (navigate)
    POST /api/store/payments/cardcardAlias of card path
    POST /api/store/payments/tokennative_tokenRequires PAYMENT_STORE_ALLOW_TOKEN=true
    POST /api/store/payments/creditcredit_balanceSync debit; currency allow-list via PAYMENT_CREDIT_BALANCE_ACCEPTED_ORDER_CURRENCIES / config
    POST /api/store/payments/paypalpaypalOrders v2; credentials + gateways.paypal.enabled

    Native token — checkout, idempotency, refunds (verified)

    Store checkout UI. The token rail is enabled in features/store/components/checkout/payment-step.tsx via getClientStorePaymentRails(): NEXT_PUBLIC_PAYMENT_STORE_ALLOW_TOKEN === 'true' and payment.gateways.native_token.enabled !== false. Server-side gate stays isRailEnabled('store_order','native_token') (PAYMENT_STORE_ALLOW_TOKEN=true). The checkout flow posts placeAndPayStoreOrder (server action) or POST /api/store/payments/token — both route through PaymentConductor.createCheckout({ purpose: 'store_order', rail: 'native_token' }) → createNativeTokenCheckout (balance check → treasury SPL transfer → payment_transactions createPending → markPaid).

    Membership native pay. POST /api/membership/payment/token is the single chokepoint (UI: components/membership/ring-payment-modal.tsx). Gate: isRailEnabled('membership_upgrade','native_token') reads isPaymentMethodEnabled('native_token') (payment.supportedMethods SSOT) — not the store flag, so membership native works with PAYMENT_STORE_ALLOW_TOKEN unset. Composition (ledger parity with card):

    PathMoney movementLedger
    Soft launch (no Membership program)PaymentConductor.createCheckout({ purpose:'membership_upgrade', rail:'native_token', metadata:{ tokenAmount } }) → treasury SPLpayment_transactions (paid) then SubscriptionConductor ledger-only via metadata.tx_hash — no second transfer, role upgrade included
    Deployed (on-chain RingMembership)createOnchainSubscription (program deducts)payment_transactions row written manually with txSignature
    membership_fee one-shotTreasury SPL (custom toAddress allowed)payment_transactions row; no subscription_ledger
    subscription_renewalPricing default amount (custom amount is not honored)subscription_ledger + payment_transactions row

    Idempotency contract (same as desk orders / NFT listings / public-pool contributions). Clients generate one UUID per payment intent (crypto.randomUUID() in checkout-client.tsx / ring-payment-modal.tsx) and reuse it on retry. payment_transactions rows carry a top-level idempotency_key; before charging, findByIdempotencyKey(userId, purpose, key) replays a paid row (same txHash, zero treasury movement) or returns 409 IDEMPOTENCY_IN_FLIGHT for an in-flight row. Old clients without a key keep the previous behavior.

    Native refunds. POST /api/admin/payments/[orderReference]/refund — platform admin (any native payment) or vendor who owns the store order (order vendorSettlements ∩ getVendorEntities). Idempotent by orderReference (already-refunded replays refundTxHash); full-amount only; treasury→user via transferTokenFromTreasury; ledger gets status refunded + refundTxHash in processor_payload. Transfer failure keeps the row paid (retryable). Minimal scope — no subscription_ledger / order-status flip.

    Ledger precision. Native-token rows store amount_minor in token precision (tokenAmount × 1e6, currency = token symbol); the main-currency stamp is skipped for token-denominated rows (fx_rate remains valid) — see stampMainCurrencyFx.

    Wallet top-up (verified)

    initiateCreditTopupPayment (app/_actions/wallet.ts) → WalletConductor.initiateTopUp → purpose wallet_topup → webhook wallet-topup*.ts → creditBalanceService.addFiatUsd (1:1 points). Distinct from native_token spend and from confidential native_token_onramp.

    Public pool jar (verified)

    POST /api/public-pools/[slug]/card-checkout → public_pool_contribution → desk FX → settlePublicPoolCardContribution. Native SPL chip-ins stay on contributeToPool (not Conductor).

    Membership PayPal (verified)

    POST /api/membership/payment/paypal → SubscriptionConductor provider paypal: recurring → Subscriptions v1 lifecycle webhooks; one-shot → Conductor Orders capture → recordPaidSubscription. UI: components/membership/payment-modal.tsx when public PayPal flag is on.

    Ledger

    data/migrations/004_payment_transactions.sql. Rows via DatabaseService / payment transaction service — result contract { success, data, error }.

    PaymentConductor architecture

    Deep-dive: CheckoutRedirect DTO, dispatcher sequences, and API route map.

    SubscriptionConductor

    Next-step: PayPal Subscriptions v1 for recurring membership — and native_token provider soft-launch vs on-chain paths.

    Admin API

    Next-step: admin native refund endpoint and payments tab.

    WalletConductor

    Same-workflow: initiateTopUp / onramp entry points into Conductor purposes.

    Wallet & Credit System

    See-also: credit top-up vs Token Desk oracle (jar FX shares desk rate).

    Ring Oracle

    Depends-on: Ring Oracle supplies desk, FX, and credit rates Conductor rails consume.

    Ring ERP

    Next-step: ERP hub cockpits after payment lands.

    Inventory & Stock

    Next-step: store paid handlers call commitSaleForOrder for stock invariant.

    Commissions & Settlements

    Same-workflow: processSettlements after paid store order.

    json
    
    {
      "payment": {
        "cardPaymentProcessor": "wayforpay",
        "supportedMethods": ["wayforpay", "credit_balance", "native_token"],
        "gateways": {
          "wayforpay": { "enabled": true, "feePercent": 2.5, "currency": "UAH" },
          "stripe": { "enabled": false, "feePercent": 2.9, "feeFixedCents": 30, "currency": "USD" },
          "paypal": { "enabled": false, "feePercent": 2.9, "feeFixedCents": 30, "currency": "USD" }
        }
      }
    }