Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
PaymentConductor is Ring Platform's config-driven payment layer. One PostgreSQL ledger (payment_transactions) and one webhook dispatcher serve store checkout, membership upgrades, news promotion, and wallet credit top-up.
Operator setup: Payment integration · WayForPay
Use Founder / Developer tabs in the docs sidebar to filter this page.
PSP dashboards and env vars
Purposes and rails
Live wallet_topup via Card / PayPal
form_post vs navigate; returnUrl vs serviceUrl
payment_transactions before the PSP page opens.order_reference is unique.redirect object. Switching a clone to Stripe is an env flip (PAYMENT_*_PROCESSOR=stripe), not a new top-up screen.PaymentConductor is Ring Platform's config-driven payment layer. One PostgreSQL ledger (payment_transactions) and one webhook dispatcher serve store checkout, membership upgrades, news promotion, and wallet credit top-up.
Operator setup: Payment integration · WayForPay
Use Founder / Developer tabs in the docs sidebar to filter this page.
PSP dashboards and env vars
Purposes and rails
Live wallet_topup via Card / PayPal
form_post vs navigate; returnUrl vs serviceUrl
payment_transactions before the PSP page opens.order_reference is unique.redirect object. Switching a clone to Stripe is an env flip (PAYMENT_*_PROCESSOR=stripe), not a new top-up screen.PaymentConductor is Ring Platform's config-driven payment layer. One PostgreSQL ledger (payment_transactions) and one webhook dispatcher serve store checkout, membership upgrades, news promotion, and wallet credit top-up.
Operator setup: Payment integration · WayForPay
Use Founder / Developer tabs in the docs sidebar to filter this page.
PSP dashboards and env vars
Purposes and rails
Live wallet_topup via Card / PayPal
form_post vs navigate; returnUrl vs serviceUrl
payment_transactions before the PSP page opens.order_reference is unique.redirect object. Switching a clone to Stripe is an env flip (PAYMENT_*_PROCESSOR=stripe), not a new top-up screen.normalizeCheckoutResult(result) — always applied at the end of PaymentConductor.createCheckoutlib/payments/checkout-redirect.ts → followCheckoutRedirect / followCheckoutResultWayForPay HPP rejects GET query-string “payment URLs” (Bad Request / This page requires only POST data). Build fields in lib/payments/wayforpay-hpp.ts.
| Module | Path | Responsibility |
|---|---|---|
| Conductor | lib/payments/conductor/payment-conductor.ts | createCheckout (+ normalize), handleWebhook, transaction lookup |
| Types | lib/payments/conductor/types.ts | Purposes, rails, CheckoutRedirect, normalizeCheckoutResult |
| Client handoff | lib/payments/checkout-redirect.ts | Unbranded followCheckoutResult |
| WFP HPP | lib/payments/wayforpay-hpp.ts | Sign + form_post fields |
| Dispatcher | lib/payments/conductor/webhook-dispatcher.ts | Verify PSP payloads; route to purpose handlers |
| Order references | lib/payments/order-reference.ts | Build / parse idempotent orderReference strings |
| Ledger service | lib/payments/payment-transaction-service.ts | CRUD + listByUserId on payment_transactions |
| Config | lib/payments/payment.config.ts | getPaymentProvider, isRailEnabled, webhook URL helpers |
| Purpose | Handler file |
|---|---|
store_order | conductor/handlers/store-order.ts (+ Stripe: store-order-stripe.ts) |
membership_upgrade | conductor/handlers/membership-upgrade.ts (+ Stripe: membership-upgrade-stripe.ts) |
news_promotion | conductor/handlers/news-promotion.ts |
wallet_topup | conductor/handlers/wallet-topup.ts (+ Stripe / PayPal capture handlers) |
native_token_onramp | conductor/handlers/native-token-onramp.ts (+ Stripe: native-token-onramp-stripe.ts) |
| Processor | File | Browser mode |
|---|---|---|
| WayForPay | processors/wayforpay.processor.ts | HPP form_post (or invoice navigate) |
| Stripe | processors/stripe.processor.ts | navigate (Checkout Session URL) |
| PayPal | processors/paypal.processor.ts | navigate (Orders v2 approve URL) |
| Internal credit | processors/internal-credit.processor.ts | Sync settle (no redirect) |
| Native token | processors/native-token.processor.ts | Sync settle (no redirect) |
Processor resolution in createCheckout:
rail === 'internal_credit' / 'native_token' → that processormetadata.processor if paypal | stripe | wayforpaygetPaymentProvider(purpose) (PAYMENT_*_PROCESSOR / PAYMENT_DEFAULT_PROCESSOR)Legacy helpers (wayforpay-service.ts, wayforpay-store-service.ts) remain for specialized form building; new route entry points go through the conductor.
CreateCheckoutContext carries purpose, optional rail, userId, amount, currency, returnUrl, metadata, and purpose-specific fields (orderId, articleId, targetRole, etc.).
lib/payments/payment.config.ts:
getPaymentProvider(purpose) — WayForPay vs Stripe from env overridesisRailEnabled(purpose, rail) — credit / token gatesgetDefaultStoreCurrencySymbol() — PAYMENT_FIAT_CURRENCY (default USD)getWebhookUrl(provider) — ${site}/api/payments/${provider}/webhookEnv keys per purpose: PAYMENT_STORE_PROCESSOR, PAYMENT_MEMBERSHIP_PROCESSOR, PAYMENT_NEWS_PROCESSOR, PAYMENT_WALLET_TOPUP_PROCESSOR, PAYMENT_NATIVE_TOKEN_ONRAMP_PROCESSOR. Default: PAYMENT_DEFAULT_PROCESSOR (wayforpay | stripe | paypal).
WayForPay env SSOT (no WAYFORPAY_MERCHANT_ID):
| Purpose | Pattern | Example |
|---|---|---|
store_order | store_{orderId}_{timestamp} | store_ord_abc_1717000000000 |
membership_upgrade | membership_{userId}_{timestamp} | membership_user123_1717000000000 |
membership_upgrade (legacy) | ring_{userId}_{timestamp} | still accepted |
news_promotion | news-promo-{base64url(articleId)}-{timestamp} | news-promo-YWJj-1717000000000 |
wallet_topup | wallettopup_{userId}_{timestamp} | wallettopup_user123_1717000000000 |
buildOrderReference / parseOrderReference in order-reference.ts. Duplicate webhook delivery is safe: ledger enforces unique order_reference.
payment_transactions)Migration: data/migrations/004_payment_transactions.sql
JSON data fields (via payment-transaction-service.ts): purpose, processor, rail, order_reference, entity_type, entity_id, user_id, amount_minor, currency, status, status_history, processor_payload, paid_at.
createPending returns existing row if order_reference already exists. Admin list: paymentTransactionService.listByUserId (default purposes: membership_upgrade, wallet_topup).
WayForPay — dispatchWayForPayWebhook: parse orderReference → verify signature → purpose handler. Wallet: transactionStatus === 'Approved' before addFiatUsd.
Stripe — dispatchStripeWebhook: verify stripe-signature → read metadata.purpose → Stripe purpose handlers.
PayPal — dispatchPayPalWebhook with transmission headers → Orders capture by purpose or Subscriptions lifecycle (BILLING.SUBSCRIPTION.*, PAYMENT.SALE.COMPLETED) via membership-paypal-subscription.ts.
Canonical routes: app/api/payments/wayforpay/webhook/route.ts, app/api/payments/stripe/webhook/route.ts, app/api/payments/paypal/webhook/route.ts (when enabled).
serviceUrl / Stripe endpoint must be public HTTPS. Localhost cannot receive PSP callbacks — use a tunnel or staging for settlement tests.
| Method | Path | Role |
|---|---|---|
POST | /api/payments/wayforpay/webhook | Unified WayForPay callback |
POST | /api/payments/stripe/webhook | Stripe signed events |
POST | /api/store/payments/wayforpay | Store redirect via conductor (store_order) |
POST | /api/store/payments/stripe | Store Stripe Checkout via conductor |
POST | /api/store/payments/token | Store native-token rail (PAYMENT_STORE_ALLOW_TOKEN=true) |
POST | /api/store/payments/paypal | Store PayPal Orders v2 (NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL) |
POST | /api/store/payments/card | Store card alias → WayForPay |
GET | /api/store/payments/[orderId]/status | Poll status |
POST | /api/store/payments/credit | Internal credit |
POST | /api/membership/payment/paypal | Membership PayPal via SubscriptionConductor (Subscriptions v1 / Orders) |
POST | /api/payments/paypal/webhook | PayPal Orders + Subscriptions webhooks |
GET | /api/admin/users/[id]/payments | Admin Payments tab |
POST | /api/membership/payment/token | RING membership |
POST | /api/news/promotion/submit | News promotion checkout |
Deprecated aliases (delegate to canonical webhook):
app/api/store/payments/wayforpay/webhook/route.tsapp/api/news/promotion/wayforpay-webhook/route.tsWalletConductor.initiateTopUp / initiateCreditTopupPayment → PaymentConductor.createCheckout({ purpose: 'wallet_topup' }) → processor from env or metadata.processor → UI followCheckoutResult(result.redirect) → webhook credits via creditBalanceService.addFiatUsd. Amount gate: 25–2000. Card tab omits processor (env SSOT); PayPal tab sets processor=paypal.
initiateMembershipPayment card path → PaymentConductor.createCheckout({ purpose: 'membership_upgrade' }). PaymentModal tabs: native-token, card, PayPal (flag-gated). Recurring PayPal: SubscriptionConductor. Manage UI: /membership/manage.
stripe-signature + STRIPE_WEBHOOK_SECRETWAYFORPAY_MERCHANT_PASSWORD for regularApi only — never expose to clientreturnUrl query params for fulfillment
WAYFORPAY_MERCHANT_ACCOUNT=...
WAYFORPAY_SECRET_KEY=...
WAYFORPAY_MERCHANT_PASSWORD=... # regularApi / recurring
WAYFORPAY_DOMAIN=ring-platform.org
WAYFORPAY_API_URL=https://api.wayforpay.com/apinormalizeCheckoutResult(result) — always applied at the end of PaymentConductor.createCheckoutlib/payments/checkout-redirect.ts → followCheckoutRedirect / followCheckoutResultWayForPay HPP rejects GET query-string “payment URLs” (Bad Request / This page requires only POST data). Build fields in lib/payments/wayforpay-hpp.ts.
| Module | Path | Responsibility |
|---|---|---|
| Conductor | lib/payments/conductor/payment-conductor.ts | createCheckout (+ normalize), handleWebhook, transaction lookup |
| Types | lib/payments/conductor/types.ts | Purposes, rails, CheckoutRedirect, normalizeCheckoutResult |
| Client handoff | lib/payments/checkout-redirect.ts | Unbranded followCheckoutResult |
| WFP HPP | lib/payments/wayforpay-hpp.ts | Sign + form_post fields |
| Dispatcher | lib/payments/conductor/webhook-dispatcher.ts | Verify PSP payloads; route to purpose handlers |
| Order references | lib/payments/order-reference.ts | Build / parse idempotent orderReference strings |
| Ledger service | lib/payments/payment-transaction-service.ts | CRUD + listByUserId on payment_transactions |
| Config | lib/payments/payment.config.ts | getPaymentProvider, isRailEnabled, webhook URL helpers |
| Purpose | Handler file |
|---|---|
store_order | conductor/handlers/store-order.ts (+ Stripe: store-order-stripe.ts) |
membership_upgrade | conductor/handlers/membership-upgrade.ts (+ Stripe: membership-upgrade-stripe.ts) |
news_promotion | conductor/handlers/news-promotion.ts |
wallet_topup | conductor/handlers/wallet-topup.ts (+ Stripe / PayPal capture handlers) |
native_token_onramp | conductor/handlers/native-token-onramp.ts (+ Stripe: native-token-onramp-stripe.ts) |
| Processor | File | Browser mode |
|---|---|---|
| WayForPay | processors/wayforpay.processor.ts | HPP form_post (or invoice navigate) |
| Stripe | processors/stripe.processor.ts | navigate (Checkout Session URL) |
| PayPal | processors/paypal.processor.ts | navigate (Orders v2 approve URL) |
| Internal credit | processors/internal-credit.processor.ts | Sync settle (no redirect) |
| Native token | processors/native-token.processor.ts | Sync settle (no redirect) |
Processor resolution in createCheckout:
rail === 'internal_credit' / 'native_token' → that processormetadata.processor if paypal | stripe | wayforpaygetPaymentProvider(purpose) (PAYMENT_*_PROCESSOR / PAYMENT_DEFAULT_PROCESSOR)Legacy helpers (wayforpay-service.ts, wayforpay-store-service.ts) remain for specialized form building; new route entry points go through the conductor.
CreateCheckoutContext carries purpose, optional rail, userId, amount, currency, returnUrl, metadata, and purpose-specific fields (orderId, articleId, targetRole, etc.).
lib/payments/payment.config.ts:
getPaymentProvider(purpose) — WayForPay vs Stripe from env overridesisRailEnabled(purpose, rail) — credit / token gatesgetDefaultStoreCurrencySymbol() — PAYMENT_FIAT_CURRENCY (default USD)getWebhookUrl(provider) — ${site}/api/payments/${provider}/webhookEnv keys per purpose: PAYMENT_STORE_PROCESSOR, PAYMENT_MEMBERSHIP_PROCESSOR, PAYMENT_NEWS_PROCESSOR, PAYMENT_WALLET_TOPUP_PROCESSOR, PAYMENT_NATIVE_TOKEN_ONRAMP_PROCESSOR. Default: PAYMENT_DEFAULT_PROCESSOR (wayforpay | stripe | paypal).
WayForPay env SSOT (no WAYFORPAY_MERCHANT_ID):
| Purpose | Pattern | Example |
|---|---|---|
store_order | store_{orderId}_{timestamp} | store_ord_abc_1717000000000 |
membership_upgrade | membership_{userId}_{timestamp} | membership_user123_1717000000000 |
membership_upgrade (legacy) | ring_{userId}_{timestamp} | still accepted |
news_promotion | news-promo-{base64url(articleId)}-{timestamp} | news-promo-YWJj-1717000000000 |
wallet_topup | wallettopup_{userId}_{timestamp} | wallettopup_user123_1717000000000 |
buildOrderReference / parseOrderReference in order-reference.ts. Duplicate webhook delivery is safe: ledger enforces unique order_reference.
payment_transactions)Migration: data/migrations/004_payment_transactions.sql
JSON data fields (via payment-transaction-service.ts): purpose, processor, rail, order_reference, entity_type, entity_id, user_id, amount_minor, currency, status, status_history, processor_payload, paid_at.
createPending returns existing row if order_reference already exists. Admin list: paymentTransactionService.listByUserId (default purposes: membership_upgrade, wallet_topup).
WayForPay — dispatchWayForPayWebhook: parse orderReference → verify signature → purpose handler. Wallet: transactionStatus === 'Approved' before addFiatUsd.
Stripe — dispatchStripeWebhook: verify stripe-signature → read metadata.purpose → Stripe purpose handlers.
PayPal — dispatchPayPalWebhook with transmission headers → Orders capture by purpose or Subscriptions lifecycle (BILLING.SUBSCRIPTION.*, PAYMENT.SALE.COMPLETED) via membership-paypal-subscription.ts.
Canonical routes: app/api/payments/wayforpay/webhook/route.ts, app/api/payments/stripe/webhook/route.ts, app/api/payments/paypal/webhook/route.ts (when enabled).
serviceUrl / Stripe endpoint must be public HTTPS. Localhost cannot receive PSP callbacks — use a tunnel or staging for settlement tests.
| Method | Path | Role |
|---|---|---|
POST | /api/payments/wayforpay/webhook | Unified WayForPay callback |
POST | /api/payments/stripe/webhook | Stripe signed events |
POST | /api/store/payments/wayforpay | Store redirect via conductor (store_order) |
POST | /api/store/payments/stripe | Store Stripe Checkout via conductor |
POST | /api/store/payments/token | Store native-token rail (PAYMENT_STORE_ALLOW_TOKEN=true) |
POST | /api/store/payments/paypal | Store PayPal Orders v2 (NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL) |
POST | /api/store/payments/card | Store card alias → WayForPay |
GET | /api/store/payments/[orderId]/status | Poll status |
POST | /api/store/payments/credit | Internal credit |
POST | /api/membership/payment/paypal | Membership PayPal via SubscriptionConductor (Subscriptions v1 / Orders) |
POST | /api/payments/paypal/webhook | PayPal Orders + Subscriptions webhooks |
GET | /api/admin/users/[id]/payments | Admin Payments tab |
POST | /api/membership/payment/token | RING membership |
POST | /api/news/promotion/submit | News promotion checkout |
Deprecated aliases (delegate to canonical webhook):
app/api/store/payments/wayforpay/webhook/route.tsapp/api/news/promotion/wayforpay-webhook/route.tsWalletConductor.initiateTopUp / initiateCreditTopupPayment → PaymentConductor.createCheckout({ purpose: 'wallet_topup' }) → processor from env or metadata.processor → UI followCheckoutResult(result.redirect) → webhook credits via creditBalanceService.addFiatUsd. Amount gate: 25–2000. Card tab omits processor (env SSOT); PayPal tab sets processor=paypal.
initiateMembershipPayment card path → PaymentConductor.createCheckout({ purpose: 'membership_upgrade' }). PaymentModal tabs: native-token, card, PayPal (flag-gated). Recurring PayPal: SubscriptionConductor. Manage UI: /membership/manage.
stripe-signature + STRIPE_WEBHOOK_SECRETWAYFORPAY_MERCHANT_PASSWORD for regularApi only — never expose to clientreturnUrl query params for fulfillment
WAYFORPAY_MERCHANT_ACCOUNT=...
WAYFORPAY_SECRET_KEY=...
WAYFORPAY_MERCHANT_PASSWORD=... # regularApi / recurring
WAYFORPAY_DOMAIN=ring-platform.org
WAYFORPAY_API_URL=https://api.wayforpay.com/apinormalizeCheckoutResult(result) — always applied at the end of PaymentConductor.createCheckoutlib/payments/checkout-redirect.ts → followCheckoutRedirect / followCheckoutResultWayForPay HPP rejects GET query-string “payment URLs” (Bad Request / This page requires only POST data). Build fields in lib/payments/wayforpay-hpp.ts.
| Module | Path | Responsibility |
|---|---|---|
| Conductor | lib/payments/conductor/payment-conductor.ts | createCheckout (+ normalize), handleWebhook, transaction lookup |
| Types | lib/payments/conductor/types.ts | Purposes, rails, CheckoutRedirect, normalizeCheckoutResult |
| Client handoff | lib/payments/checkout-redirect.ts | Unbranded followCheckoutResult |
| WFP HPP | lib/payments/wayforpay-hpp.ts | Sign + form_post fields |
| Dispatcher | lib/payments/conductor/webhook-dispatcher.ts | Verify PSP payloads; route to purpose handlers |
| Order references | lib/payments/order-reference.ts | Build / parse idempotent orderReference strings |
| Ledger service | lib/payments/payment-transaction-service.ts | CRUD + listByUserId on payment_transactions |
| Config | lib/payments/payment.config.ts | getPaymentProvider, isRailEnabled, webhook URL helpers |
| Purpose | Handler file |
|---|---|
store_order | conductor/handlers/store-order.ts (+ Stripe: store-order-stripe.ts) |
membership_upgrade | conductor/handlers/membership-upgrade.ts (+ Stripe: membership-upgrade-stripe.ts) |
news_promotion | conductor/handlers/news-promotion.ts |
wallet_topup | conductor/handlers/wallet-topup.ts (+ Stripe / PayPal capture handlers) |
native_token_onramp | conductor/handlers/native-token-onramp.ts (+ Stripe: native-token-onramp-stripe.ts) |
| Processor | File | Browser mode |
|---|---|---|
| WayForPay | processors/wayforpay.processor.ts | HPP form_post (or invoice navigate) |
| Stripe | processors/stripe.processor.ts | navigate (Checkout Session URL) |
| PayPal | processors/paypal.processor.ts | navigate (Orders v2 approve URL) |
| Internal credit | processors/internal-credit.processor.ts | Sync settle (no redirect) |
| Native token | processors/native-token.processor.ts | Sync settle (no redirect) |
Processor resolution in createCheckout:
rail === 'internal_credit' / 'native_token' → that processormetadata.processor if paypal | stripe | wayforpaygetPaymentProvider(purpose) (PAYMENT_*_PROCESSOR / PAYMENT_DEFAULT_PROCESSOR)Legacy helpers (wayforpay-service.ts, wayforpay-store-service.ts) remain for specialized form building; new route entry points go through the conductor.
CreateCheckoutContext carries purpose, optional rail, userId, amount, currency, returnUrl, metadata, and purpose-specific fields (orderId, articleId, targetRole, etc.).
lib/payments/payment.config.ts:
getPaymentProvider(purpose) — WayForPay vs Stripe from env overridesisRailEnabled(purpose, rail) — credit / token gatesgetDefaultStoreCurrencySymbol() — PAYMENT_FIAT_CURRENCY (default USD)getWebhookUrl(provider) — ${site}/api/payments/${provider}/webhookEnv keys per purpose: PAYMENT_STORE_PROCESSOR, PAYMENT_MEMBERSHIP_PROCESSOR, PAYMENT_NEWS_PROCESSOR, PAYMENT_WALLET_TOPUP_PROCESSOR, PAYMENT_NATIVE_TOKEN_ONRAMP_PROCESSOR. Default: PAYMENT_DEFAULT_PROCESSOR (wayforpay | stripe | paypal).
WayForPay env SSOT (no WAYFORPAY_MERCHANT_ID):
| Purpose | Pattern | Example |
|---|---|---|
store_order | store_{orderId}_{timestamp} | store_ord_abc_1717000000000 |
membership_upgrade | membership_{userId}_{timestamp} | membership_user123_1717000000000 |
membership_upgrade (legacy) | ring_{userId}_{timestamp} | still accepted |
news_promotion | news-promo-{base64url(articleId)}-{timestamp} | news-promo-YWJj-1717000000000 |
wallet_topup | wallettopup_{userId}_{timestamp} | wallettopup_user123_1717000000000 |
buildOrderReference / parseOrderReference in order-reference.ts. Duplicate webhook delivery is safe: ledger enforces unique order_reference.
payment_transactions)Migration: data/migrations/004_payment_transactions.sql
JSON data fields (via payment-transaction-service.ts): purpose, processor, rail, order_reference, entity_type, entity_id, user_id, amount_minor, currency, status, status_history, processor_payload, paid_at.
createPending returns existing row if order_reference already exists. Admin list: paymentTransactionService.listByUserId (default purposes: membership_upgrade, wallet_topup).
WayForPay — dispatchWayForPayWebhook: parse orderReference → verify signature → purpose handler. Wallet: transactionStatus === 'Approved' before addFiatUsd.
Stripe — dispatchStripeWebhook: verify stripe-signature → read metadata.purpose → Stripe purpose handlers.
PayPal — dispatchPayPalWebhook with transmission headers → Orders capture by purpose or Subscriptions lifecycle (BILLING.SUBSCRIPTION.*, PAYMENT.SALE.COMPLETED) via membership-paypal-subscription.ts.
Canonical routes: app/api/payments/wayforpay/webhook/route.ts, app/api/payments/stripe/webhook/route.ts, app/api/payments/paypal/webhook/route.ts (when enabled).
serviceUrl / Stripe endpoint must be public HTTPS. Localhost cannot receive PSP callbacks — use a tunnel or staging for settlement tests.
| Method | Path | Role |
|---|---|---|
POST | /api/payments/wayforpay/webhook | Unified WayForPay callback |
POST | /api/payments/stripe/webhook | Stripe signed events |
POST | /api/store/payments/wayforpay | Store redirect via conductor (store_order) |
POST | /api/store/payments/stripe | Store Stripe Checkout via conductor |
POST | /api/store/payments/token | Store native-token rail (PAYMENT_STORE_ALLOW_TOKEN=true) |
POST | /api/store/payments/paypal | Store PayPal Orders v2 (NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL) |
POST | /api/store/payments/card | Store card alias → WayForPay |
GET | /api/store/payments/[orderId]/status | Poll status |
POST | /api/store/payments/credit | Internal credit |
POST | /api/membership/payment/paypal | Membership PayPal via SubscriptionConductor (Subscriptions v1 / Orders) |
POST | /api/payments/paypal/webhook | PayPal Orders + Subscriptions webhooks |
GET | /api/admin/users/[id]/payments | Admin Payments tab |
POST | /api/membership/payment/token | RING membership |
POST | /api/news/promotion/submit | News promotion checkout |
Deprecated aliases (delegate to canonical webhook):
app/api/store/payments/wayforpay/webhook/route.tsapp/api/news/promotion/wayforpay-webhook/route.tsWalletConductor.initiateTopUp / initiateCreditTopupPayment → PaymentConductor.createCheckout({ purpose: 'wallet_topup' }) → processor from env or metadata.processor → UI followCheckoutResult(result.redirect) → webhook credits via creditBalanceService.addFiatUsd. Amount gate: 25–2000. Card tab omits processor (env SSOT); PayPal tab sets processor=paypal.
initiateMembershipPayment card path → PaymentConductor.createCheckout({ purpose: 'membership_upgrade' }). PaymentModal tabs: native-token, card, PayPal (flag-gated). Recurring PayPal: SubscriptionConductor. Manage UI: /membership/manage.
stripe-signature + STRIPE_WEBHOOK_SECRETWAYFORPAY_MERCHANT_PASSWORD for regularApi only — never expose to clientreturnUrl query params for fulfillment
WAYFORPAY_MERCHANT_ACCOUNT=...
WAYFORPAY_SECRET_KEY=...
WAYFORPAY_MERCHANT_PASSWORD=... # regularApi / recurring
WAYFORPAY_DOMAIN=ring-platform.org
WAYFORPAY_API_URL=https://api.wayforpay.com/api