Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
WayForPay is Ring's primary Ukrainian PSP for hosted card checkout (including Apple Pay / Google Pay where enabled by the merchant). All new card flows go through PaymentConductor — store, membership, news promotion, and wallet credit top-up.
Use Founder / Developer tabs in the docs sidebar to filter this page.
Env SSOT: WAYFORPAY_MERCHANT_ACCOUNT, WAYFORPAY_SECRET_KEY, WAYFORPAY_MERCHANT_PASSWORD, WAYFORPAY_DOMAIN, WAYFORPAY_API_URL. There is no WAYFORPAY_MERCHANT_ID.
Money truth: credit and order fulfillment happen only when WayForPay POSTs to serviceUrl with transactionStatus === 'Approved'. The browser returnUrl is UX only — not settlement.
| Purpose | Live entry | Settled by |
|---|---|---|
store_order | POST /api/store/payments/wayforpay → PaymentConductor.createCheckout | handlers/store-order.ts |
membership_upgrade | initiateMembershipPayment (card) → createCheckout | handlers/membership-upgrade.ts |
news_promotion | News promotion submit | handlers/news-promotion.ts |
wallet_topup | WalletConductor.initiateTopUp / initiateCreditTopupPayment → createCheckout | handlers/wallet-topup.ts → creditBalanceService.addFiatUsd |
Recurring membership management uses WayForPay regularApi (lib/payments/subscription/wayforpay-regular-api.ts) with merchantAccount + merchantPassword (not the secret key).
returnUrl on every payment (e.g. back to /wallet). WayForPay’s own result page is used only if returnUrl is omitted.https://YOUR_HOST/api/payments/wayforpay/webhook so Approved payments can settle. Localhost cannot receive WayForPay callbacks (use a tunnel or staging for real credit tests)./wallet/topup Card tab uses PaymentConductor (processor from env: WayForPay or Stripe). PayPal tab on Add Credit sends processor=paypal when PayPal is configured.NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true (Subscriptions v1 via SubscriptionConductor) — separate from wallet credit PayPal.| Variable | Required for | Notes |
|---|---|---|
WAYFORPAY_MERCHANT_ACCOUNT | All checkouts | Merchant login / account id from cabinet |
WayForPay is Ring's primary Ukrainian PSP for hosted card checkout (including Apple Pay / Google Pay where enabled by the merchant). All new card flows go through PaymentConductor — store, membership, news promotion, and wallet credit top-up.
Use Founder / Developer tabs in the docs sidebar to filter this page.
Env SSOT: WAYFORPAY_MERCHANT_ACCOUNT, WAYFORPAY_SECRET_KEY, WAYFORPAY_MERCHANT_PASSWORD, WAYFORPAY_DOMAIN, WAYFORPAY_API_URL. There is no WAYFORPAY_MERCHANT_ID.
Money truth: credit and order fulfillment happen only when WayForPay POSTs to serviceUrl with transactionStatus === 'Approved'. The browser returnUrl is UX only — not settlement.
| Purpose | Live entry | Settled by |
|---|---|---|
store_order | POST /api/store/payments/wayforpay → PaymentConductor.createCheckout | handlers/store-order.ts |
membership_upgrade | initiateMembershipPayment (card) → createCheckout | handlers/membership-upgrade.ts |
news_promotion | News promotion submit | handlers/news-promotion.ts |
wallet_topup | WalletConductor.initiateTopUp / initiateCreditTopupPayment → createCheckout | handlers/wallet-topup.ts → creditBalanceService.addFiatUsd |
Recurring membership management uses WayForPay regularApi (lib/payments/subscription/wayforpay-regular-api.ts) with merchantAccount + merchantPassword (not the secret key).
returnUrl on every payment (e.g. back to /wallet). WayForPay’s own result page is used only if returnUrl is omitted.https://YOUR_HOST/api/payments/wayforpay/webhook so Approved payments can settle. Localhost cannot receive WayForPay callbacks (use a tunnel or staging for real credit tests)./wallet/topup Card tab uses PaymentConductor (processor from env: WayForPay or Stripe). PayPal tab on Add Credit sends processor=paypal when PayPal is configured.NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true (Subscriptions v1 via SubscriptionConductor) — separate from wallet credit PayPal.| Variable | Required for | Notes |
|---|---|---|
WAYFORPAY_MERCHANT_ACCOUNT | All checkouts | Merchant login / account id from cabinet |
WayForPay is Ring's primary Ukrainian PSP for hosted card checkout (including Apple Pay / Google Pay where enabled by the merchant). All new card flows go through PaymentConductor — store, membership, news promotion, and wallet credit top-up.
Use Founder / Developer tabs in the docs sidebar to filter this page.
Env SSOT: WAYFORPAY_MERCHANT_ACCOUNT, WAYFORPAY_SECRET_KEY, WAYFORPAY_MERCHANT_PASSWORD, WAYFORPAY_DOMAIN, WAYFORPAY_API_URL. There is no WAYFORPAY_MERCHANT_ID.
Money truth: credit and order fulfillment happen only when WayForPay POSTs to serviceUrl with transactionStatus === 'Approved'. The browser returnUrl is UX only — not settlement.
| Purpose | Live entry | Settled by |
|---|---|---|
store_order | POST /api/store/payments/wayforpay → PaymentConductor.createCheckout | handlers/store-order.ts |
membership_upgrade | initiateMembershipPayment (card) → createCheckout | handlers/membership-upgrade.ts |
news_promotion | News promotion submit | handlers/news-promotion.ts |
wallet_topup | WalletConductor.initiateTopUp / initiateCreditTopupPayment → createCheckout | handlers/wallet-topup.ts → creditBalanceService.addFiatUsd |
Recurring membership management uses WayForPay regularApi (lib/payments/subscription/wayforpay-regular-api.ts) with merchantAccount + merchantPassword (not the secret key).
returnUrl on every payment (e.g. back to /wallet). WayForPay’s own result page is used only if returnUrl is omitted.https://YOUR_HOST/api/payments/wayforpay/webhook so Approved payments can settle. Localhost cannot receive WayForPay callbacks (use a tunnel or staging for real credit tests)./wallet/topup Card tab uses PaymentConductor (processor from env: WayForPay or Stripe). PayPal tab on Add Credit sends processor=paypal when PayPal is configured.NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true (Subscriptions v1 via SubscriptionConductor) — separate from wallet credit PayPal.| Variable | Required for | Notes |
|---|---|---|
WAYFORPAY_MERCHANT_ACCOUNT | All checkouts | Merchant login / account id from cabinet |
WAYFORPAY_SECRET_KEY| HMAC signatures |
Server-only — never NEXT_PUBLIC_* |
WAYFORPAY_MERCHANT_PASSWORD | Recurring / regularApi | Required for subscription lifecycle |
WAYFORPAY_DOMAIN | Merchant domain match | This product: ring-platform.org |
WAYFORPAY_API_URL | API base | Default https://api.wayforpay.com/api |
Templates: env.local.template, docker.env.template, docker-compose.template.yml.
Use your clone host in white-label deployments. Do not register legacy /callback, /success, or /failure paths as the settlement endpoint.
returnUrl) — they see the wallet/store page.If the member is back on /wallet but credit is missing, the webhook did not land (wrong host, localhost, or HMAC failure) — check payment_transactions and server logs.
createCheckoutnormalizeCheckoutResultredirectpaymentUrlpaymentFields| Field | Who sets it | Role | Trust for money? |
|---|---|---|---|
returnUrl | Ring per request (WalletConductor.initiateTopUp, store/membership contexts) | Browser land-back after pay | No |
serviceUrl | Ring via getWebhookUrl('wayforpay') | Server POST from WayForPay | Yes — settlement |
Wallet credit settlement: handlers/wallet-topup.ts requires transactionStatus === 'Approved', then markPaid + creditBalanceService.addFiatUsd.
| Concern | Path |
|---|---|
| Processor (all purposes) | lib/payments/processors/wayforpay.processor.ts |
| HPP sign + fields | lib/payments/wayforpay-hpp.ts, wayforpay-hpp-types.ts |
| Unbranded browser handoff | lib/payments/checkout-redirect.ts |
| Signature verify | lib/payments/processors/wayforpay-verify.ts |
| Store form helper | lib/payments/wayforpay-store-service.ts (initiateStorePayment — called from processor only) |
| Webhook dispatcher | lib/payments/conductor/webhook-dispatcher.ts |
| Handlers | lib/payments/conductor/handlers/*.ts |
| regularApi | lib/payments/subscription/wayforpay-regular-api.ts |
| Subscription provider | lib/payments/subscription/providers/wayforpay-subscription.ts |
| Canonical webhook | app/api/payments/wayforpay/webhook/route.ts |
Caller invokes PaymentConductor.createCheckout({ purpose, userId, amount, currency, returnUrl, ... }). Conductor resolves processor (metadata.processor or getPaymentProvider(purpose)).
wayforpay.processor.ts builds orderReference, writes createPending, builds HPP fields (returnUrl + serviceUrl), returns redirect: { mode: 'form_post', ... }. Conductor runs normalizeCheckoutResult.
UI calls followCheckoutResult — hidden form POST to secure.wayforpay.com/pay (not window.location with a query string).
User pays; browser may hit returnUrl. WayForPay POSTs to /api/payments/wayforpay/webhook.
dispatchWayForPayWebhook verifies HMAC, parses purpose from orderReference, marks paid, runs the purpose handler (e.g. wallet credit).
| Purpose | Prefix pattern |
|---|---|
store_order | store_{orderId}_{ts} |
membership_upgrade | membership_{userId}_{ts} (legacy ring_ still parsed) |
news_promotion | news-promo-{base64url(articleId)}-{ts} |
wallet_topup | wallettopup_{userId}_{ts} |
UI: features/wallet/components/credit-add-fs-modal.tsx (Card | PayPal tabs) and /wallet/topup.
initiateMembershipPayment with card / wayforpay / stripe → createCheckout({ purpose: 'membership_upgrade' }). UI: components/membership/payment-modal.tsx — native-token + card live; PayPal tab when NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true → POST /api/membership/payment/paypal.
POST /api/store/payments/wayforpay → PaymentConductor.createCheckout({ purpose: 'store_order' }). No route-level bypass of the ledger. Client uses followCheckoutResult with conductor redirect.
WAYFORPAY_MERCHANT_PASSWORD + merchantAccount authenticate JSON calls to https://api.wayforpay.com/regularApi (STATUS, CHANGE, SUSPEND, RESUME, REMOVE). See wayforpay-regular-api.ts.
WAYFORPAY_SECRET_KEY.markPaid / handler guards).returnUrl query params as payment confirmation.004 applied → store + membership card + wallet top-up → inspect payment_transactions.serviceUrl will not settle credit.GET /api/admin/users/[id]/payments (UI: admin-user-detail-sheet.tsx Payments tab).SubscriptionConductor + WayForPay recurring
https://YOUR_HOST/api/payments/wayforpay/webhookWAYFORPAY_SECRET_KEY| HMAC signatures |
Server-only — never NEXT_PUBLIC_* |
WAYFORPAY_MERCHANT_PASSWORD | Recurring / regularApi | Required for subscription lifecycle |
WAYFORPAY_DOMAIN | Merchant domain match | This product: ring-platform.org |
WAYFORPAY_API_URL | API base | Default https://api.wayforpay.com/api |
Templates: env.local.template, docker.env.template, docker-compose.template.yml.
Use your clone host in white-label deployments. Do not register legacy /callback, /success, or /failure paths as the settlement endpoint.
returnUrl) — they see the wallet/store page.If the member is back on /wallet but credit is missing, the webhook did not land (wrong host, localhost, or HMAC failure) — check payment_transactions and server logs.
createCheckoutnormalizeCheckoutResultredirectpaymentUrlpaymentFields| Field | Who sets it | Role | Trust for money? |
|---|---|---|---|
returnUrl | Ring per request (WalletConductor.initiateTopUp, store/membership contexts) | Browser land-back after pay | No |
serviceUrl | Ring via getWebhookUrl('wayforpay') | Server POST from WayForPay | Yes — settlement |
Wallet credit settlement: handlers/wallet-topup.ts requires transactionStatus === 'Approved', then markPaid + creditBalanceService.addFiatUsd.
| Concern | Path |
|---|---|
| Processor (all purposes) | lib/payments/processors/wayforpay.processor.ts |
| HPP sign + fields | lib/payments/wayforpay-hpp.ts, wayforpay-hpp-types.ts |
| Unbranded browser handoff | lib/payments/checkout-redirect.ts |
| Signature verify | lib/payments/processors/wayforpay-verify.ts |
| Store form helper | lib/payments/wayforpay-store-service.ts (initiateStorePayment — called from processor only) |
| Webhook dispatcher | lib/payments/conductor/webhook-dispatcher.ts |
| Handlers | lib/payments/conductor/handlers/*.ts |
| regularApi | lib/payments/subscription/wayforpay-regular-api.ts |
| Subscription provider | lib/payments/subscription/providers/wayforpay-subscription.ts |
| Canonical webhook | app/api/payments/wayforpay/webhook/route.ts |
Caller invokes PaymentConductor.createCheckout({ purpose, userId, amount, currency, returnUrl, ... }). Conductor resolves processor (metadata.processor or getPaymentProvider(purpose)).
wayforpay.processor.ts builds orderReference, writes createPending, builds HPP fields (returnUrl + serviceUrl), returns redirect: { mode: 'form_post', ... }. Conductor runs normalizeCheckoutResult.
UI calls followCheckoutResult — hidden form POST to secure.wayforpay.com/pay (not window.location with a query string).
User pays; browser may hit returnUrl. WayForPay POSTs to /api/payments/wayforpay/webhook.
dispatchWayForPayWebhook verifies HMAC, parses purpose from orderReference, marks paid, runs the purpose handler (e.g. wallet credit).
| Purpose | Prefix pattern |
|---|---|
store_order | store_{orderId}_{ts} |
membership_upgrade | membership_{userId}_{ts} (legacy ring_ still parsed) |
news_promotion | news-promo-{base64url(articleId)}-{ts} |
wallet_topup | wallettopup_{userId}_{ts} |
UI: features/wallet/components/credit-add-fs-modal.tsx (Card | PayPal tabs) and /wallet/topup.
initiateMembershipPayment with card / wayforpay / stripe → createCheckout({ purpose: 'membership_upgrade' }). UI: components/membership/payment-modal.tsx — native-token + card live; PayPal tab when NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true → POST /api/membership/payment/paypal.
POST /api/store/payments/wayforpay → PaymentConductor.createCheckout({ purpose: 'store_order' }). No route-level bypass of the ledger. Client uses followCheckoutResult with conductor redirect.
WAYFORPAY_MERCHANT_PASSWORD + merchantAccount authenticate JSON calls to https://api.wayforpay.com/regularApi (STATUS, CHANGE, SUSPEND, RESUME, REMOVE). See wayforpay-regular-api.ts.
WAYFORPAY_SECRET_KEY.markPaid / handler guards).returnUrl query params as payment confirmation.004 applied → store + membership card + wallet top-up → inspect payment_transactions.serviceUrl will not settle credit.GET /api/admin/users/[id]/payments (UI: admin-user-detail-sheet.tsx Payments tab).SubscriptionConductor + WayForPay recurring
https://YOUR_HOST/api/payments/wayforpay/webhookWAYFORPAY_SECRET_KEY| HMAC signatures |
Server-only — never NEXT_PUBLIC_* |
WAYFORPAY_MERCHANT_PASSWORD | Recurring / regularApi | Required for subscription lifecycle |
WAYFORPAY_DOMAIN | Merchant domain match | This product: ring-platform.org |
WAYFORPAY_API_URL | API base | Default https://api.wayforpay.com/api |
Templates: env.local.template, docker.env.template, docker-compose.template.yml.
Use your clone host in white-label deployments. Do not register legacy /callback, /success, or /failure paths as the settlement endpoint.
returnUrl) — they see the wallet/store page.If the member is back on /wallet but credit is missing, the webhook did not land (wrong host, localhost, or HMAC failure) — check payment_transactions and server logs.
createCheckoutnormalizeCheckoutResultredirectpaymentUrlpaymentFields| Field | Who sets it | Role | Trust for money? |
|---|---|---|---|
returnUrl | Ring per request (WalletConductor.initiateTopUp, store/membership contexts) | Browser land-back after pay | No |
serviceUrl | Ring via getWebhookUrl('wayforpay') | Server POST from WayForPay | Yes — settlement |
Wallet credit settlement: handlers/wallet-topup.ts requires transactionStatus === 'Approved', then markPaid + creditBalanceService.addFiatUsd.
| Concern | Path |
|---|---|
| Processor (all purposes) | lib/payments/processors/wayforpay.processor.ts |
| HPP sign + fields | lib/payments/wayforpay-hpp.ts, wayforpay-hpp-types.ts |
| Unbranded browser handoff | lib/payments/checkout-redirect.ts |
| Signature verify | lib/payments/processors/wayforpay-verify.ts |
| Store form helper | lib/payments/wayforpay-store-service.ts (initiateStorePayment — called from processor only) |
| Webhook dispatcher | lib/payments/conductor/webhook-dispatcher.ts |
| Handlers | lib/payments/conductor/handlers/*.ts |
| regularApi | lib/payments/subscription/wayforpay-regular-api.ts |
| Subscription provider | lib/payments/subscription/providers/wayforpay-subscription.ts |
| Canonical webhook | app/api/payments/wayforpay/webhook/route.ts |
Caller invokes PaymentConductor.createCheckout({ purpose, userId, amount, currency, returnUrl, ... }). Conductor resolves processor (metadata.processor or getPaymentProvider(purpose)).
wayforpay.processor.ts builds orderReference, writes createPending, builds HPP fields (returnUrl + serviceUrl), returns redirect: { mode: 'form_post', ... }. Conductor runs normalizeCheckoutResult.
UI calls followCheckoutResult — hidden form POST to secure.wayforpay.com/pay (not window.location with a query string).
User pays; browser may hit returnUrl. WayForPay POSTs to /api/payments/wayforpay/webhook.
dispatchWayForPayWebhook verifies HMAC, parses purpose from orderReference, marks paid, runs the purpose handler (e.g. wallet credit).
| Purpose | Prefix pattern |
|---|---|
store_order | store_{orderId}_{ts} |
membership_upgrade | membership_{userId}_{ts} (legacy ring_ still parsed) |
news_promotion | news-promo-{base64url(articleId)}-{ts} |
wallet_topup | wallettopup_{userId}_{ts} |
UI: features/wallet/components/credit-add-fs-modal.tsx (Card | PayPal tabs) and /wallet/topup.
initiateMembershipPayment with card / wayforpay / stripe → createCheckout({ purpose: 'membership_upgrade' }). UI: components/membership/payment-modal.tsx — native-token + card live; PayPal tab when NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true → POST /api/membership/payment/paypal.
POST /api/store/payments/wayforpay → PaymentConductor.createCheckout({ purpose: 'store_order' }). No route-level bypass of the ledger. Client uses followCheckoutResult with conductor redirect.
WAYFORPAY_MERCHANT_PASSWORD + merchantAccount authenticate JSON calls to https://api.wayforpay.com/regularApi (STATUS, CHANGE, SUSPEND, RESUME, REMOVE). See wayforpay-regular-api.ts.
WAYFORPAY_SECRET_KEY.markPaid / handler guards).returnUrl query params as payment confirmation.004 applied → store + membership card + wallet top-up → inspect payment_transactions.serviceUrl will not settle credit.GET /api/admin/users/[id]/payments (UI: admin-user-detail-sheet.tsx Payments tab).SubscriptionConductor + WayForPay recurring
https://YOUR_HOST/api/payments/wayforpay/webhook