---
title: "Payments Overview"
description: "Founder money hub — PaymentConductor rails (card/WayForPay/Stripe, PayPal, credit, native token), purposes, dual-path setup, and FAQ. No fabricated cart REST."
locale: "en"
---
# Payments Overview

> **Info**
> **Status:** PaymentConductor is the live money SSOT for store, membership one-shots, news promotion, wallet credit top-up, confidential native onramp, and public-pool (DAO jar) card/PayPal chip-ins. Recurring membership PayPal uses [SubscriptionConductor](/docs/features/subscriptions.md) (Subscriptions v1) — not a separate cart API. Use **Founder** / **Developer** tabs in the docs sidebar to filter this page.

Ring routes buyer **rails** (`card` | `paypal` | `credit_balance` | `native_token`) through **PaymentConductor** (`lib/payments/conductor/`). The **card** rail settles via WayForPay or Stripe (`payment.cardPaymentProcessor` / purpose env) — UI never picks a PSP id. Browser handoff is Conductor **`CheckoutRedirect`** (`navigate` | `form_post`) via `followCheckoutResult`. Rates and presentment come from [Ring Oracle](/docs/features/ring-oracle.md).

| Previous / common assumption | Ring equivalent |
|------------------------------|-----------------|
| Brand-named checkout buttons (WayForPay / Stripe) in UI | **Rail** `card` → Conductor resolves processor |
| Generic `/api/cart/*` checkout REST | **No cart payment REST** — store UI uses Server Action `placeAndPayStoreOrder` → `PaymentConductor.createCheckout` |
| Optional REST by rail | `POST /api/store/payments/{card\|wayforpay\|stripe\|credit\|token\|paypal}` (same Conductor SSOT) |
| Hosted PSP “Donate” products for jars | Purpose **`public_pool_contribution`** + desk oracle — see [Public Pools](/docs/features/public-pools.md) |
| Recurring membership on PaymentConductor Orders only | One-shot Orders here; **Subscriptions v1** on [SubscriptionConductor](/docs/features/subscriptions.md) |

## Rails comparison

| Rail | Typical processor | Store entry | Gateway fee (config) | Notes |
|------|-------------------|-------------|----------------------|-------|
| `card` | WayForPay **or** Stripe | Action + `POST …/card` (alias) / `…/wayforpay` / `…/stripe` | WFP ~2.5% · Stripe ~2.9% + fixed | Primary Ukraine = WayForPay HPP (`form_post`) |
| `paypal` | PayPal Orders v2 | Action + `POST …/paypal` | ~2.9% + fixed when enabled | Needs `NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true` + `gateways.paypal.enabled` + `PAYPAL_*` |
| `credit_balance` | Internal | Action + `POST …/credit` | 0% | Fiat credit ledger; gated by `PAYMENT_STORE_ALLOW_CREDIT` / gateway |
| `native_token` | On-chain | Action + `POST …/token` | 0% | Needs `PAYMENT_STORE_ALLOW_TOKEN=true` (+ public twin for UI) |

Canonical PSP webhooks (settlement): `POST /api/payments/{wayforpay,stripe,paypal}/webhook`. Money truth for WayForPay: **`serviceUrl` Approved** settles — browser `returnUrl` is UX only.

## What PaymentConductor covers

| Purpose | Buyer outcome | Typical rails / PSP |
|---------|---------------|---------------------|
| `store_order` | Cart → paid order | card / credit / native_token / paypal |
| `membership_upgrade` | Role upgrade one-shot | card / native / paypal (recurring → SubscriptionConductor) |
| `news_promotion` | Sponsored news | card (WFP / Stripe) |
| `wallet_topup` | Fiat **credit** points | card (`PAYMENT_WALLET_TOPUP_PROCESSOR`) or paypal |
| `native_token_onramp` | Card/PayPal → treasury native | Confidential+ when `CONFIDENTIAL_TOKEN_ONRAMP=true` |
| `public_pool_contribution` | DAO jar chip-in | card / paypal → desk FX → `pledged_native_token` |

Extended purposes (`project_order`, `task_escrow`, `collective_order_slot`, `scheduled_service_slot`) share the same Conductor — details on [PaymentConductor](/docs/features/payment-conductor.md).

**Membership native pay** is `membership_upgrade` + `native_token` through PaymentConductor (soft launch: treasury SPL + ledger; deployed: on-chain `RingMembership`), then `SubscriptionConductor` ledger-only via `metadata.tx_hash` — see [SubscriptionConductor](/docs/features/subscriptions.md). Its gate is `payment.supportedMethods` (`isPaymentMethodEnabled('native_token')`), **not** `PAYMENT_STORE_ALLOW_TOKEN`.

**Idempotency:** native pay (store + membership) follows the shared contract — one client `idempotencyKey` per intent; paid rows replay (same txHash), in-flight rows return `409 IDEMPOTENCY_IN_FLIGHT`.

**Native refunds:** `POST /api/admin/payments/[orderReference]/refund` (platform admin or owning vendor; full-amount, idempotent, marks `payment_transactions` `refunded`).

  WayForPay hosted Донати, Stripe `submit_type=donate`, and PayPal Donate SDK are **not** Ring jar SSOT. Use [Public Pools](/docs/features/public-pools.md).

## Recommended path

**Skill / desk:** Cursor + Ring docs cluster — start from operator checklist [Payment Gateway Integration](/docs/customization/payment-integration.md), then deep-dive [PaymentConductor](/docs/features/payment-conductor.md).

**Starter prompt (does not mutate until you approve env/config changes):**

> Configure this clone’s PaymentConductor: set `PAYMENT_DEFAULT_PROCESSOR`, WayForPay or Stripe credentials from `env.local.template`, enable store rails (`card` + optional credit/token/PayPal flags), register `https://YOUR_HOST/api/payments/{processor}/webhook`, and verify store checkout uses `placeAndPayStoreOrder` + `CheckoutRedirect` — no `/api/cart` payment routes.

**Inputs checklist:** clone host · `ring-config.json` `payment.*` · PSP cabinet credentials · webhook URL reachable from the internet · `data/migrations/004_payment_transactions.sql` applied.

## Manual path

### Choose card processor and rails

Set `payment.cardPaymentProcessor` (`wayforpay` or `stripe`) and `payment.gateways.*.enabled` in `ring-config.json`. Optional per-purpose env: `PAYMENT_STORE_PROCESSOR`, `PAYMENT_MEMBERSHIP_PROCESSOR`, `PAYMENT_NEWS_PROCESSOR`, `PAYMENT_WALLET_TOPUP_PROCESSOR`, `PAYMENT_PUBLIC_POOL_CONTRIBUTION_PROCESSOR`, `PAYMENT_NATIVE_TOKEN_ONRAMP_PROCESSOR`.

### Fill credentials from templates

Copy WayForPay / Stripe / PayPal keys from `env.local.template` (also `docker.env.template`). WayForPay SSOT: `WAYFORPAY_MERCHANT_ACCOUNT`, `WAYFORPAY_SECRET_KEY`, `WAYFORPAY_MERCHANT_PASSWORD`, `WAYFORPAY_DOMAIN`, `WAYFORPAY_API_URL` — **no** `WAYFORPAY_MERCHANT_ID`.

### Enable optional store rails

- Credit: keep `PAYMENT_STORE_ALLOW_CREDIT` / `NEXT_PUBLIC_PAYMENT_STORE_ALLOW_CREDIT` from disabling credit.
- Native token: `PAYMENT_STORE_ALLOW_TOKEN=true` and `NEXT_PUBLIC_PAYMENT_STORE_ALLOW_TOKEN=true`.
- PayPal: `NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=true`, `gateways.paypal.enabled: true`, plus `PAYPAL_CLIENT_ID` / `PAYPAL_CLIENT_SECRET` / `PAYPAL_WEBHOOK_ID` / `PAYPAL_MODE`.

### Register webhooks and migrate ledger

Register cabinet callbacks to `/api/payments/wayforpay/webhook` (and Stripe/PayPal twins). Apply `004_payment_transactions.sql` before production traffic.

### Smoke-test browser handoff

Place a store order (card rail). Expect `CheckoutRedirect` — WayForPay HPP via **POST form**, not a GET query URL. Confirm settlement only after webhook Approved / capture — not on return URL alone.

### For founders

## Why this matters for your clone

  
- **[One ledger](/docs/features/payment-conductor.md)** — `payment_transactions` covers store, membership one-shots, news, wallet top-up, and DAO jar chip-ins.

  
- **[Ukraine-first card](/docs/features/wayforpay-integration.md)** — WayForPay HPP keeps PCI scope at the PSP; Stripe for international clones.

  
- **[Wallet credit vs RING](/docs/features/wallet.md)** — Card top-up adds fiat credit points — not on-chain RING. Desk / onramp are separate paths.

  
- **[Membership recurring](/docs/features/subscriptions.md)** — PayPal Subscriptions v1 + manage page at `/membership/manage`.

### Operator scenarios

- **Launch UA store** — WayForPay credentials + default processor; keep credit on for members; defer PayPal until `PAYPAL_*` is ready.
- **International card** — set `PAYMENT_DEFAULT_PROCESSOR=stripe` (or purpose overrides); same rails UI.
- **DAO jar fundraising** — enable public-pool card checkout; fees via `publicPools.platformFeePercentByRole` — not donation products.
- **Admin visibility** — user detail Payments tab lists ledger rows (`GET /api/admin/users/[id]/payments`).

### `ring-config.json` payment section

Symbols are examples — replace with your clone’s `store.mainCurrency` and `tokens.nativeToken.symbol`.

```json
{
  "payment": {
    "cardPaymentProcessor": "wayforpay",
    "supportedMethods": ["wayforpay", "credit_balance", "native_token"],
    "futureMethods": ["stripe", "paypal", "nft_gate"],
    "gateways": {
      "wayforpay": { "enabled": true, "feePercent": 2.5, "currency": "" },
      "stripe": { "enabled": false, "feePercent": 2.9, "feeFixedCents": 30, "currency": "" },
      "credit_balance": { "enabled": true, "feePercent": 0, "currency": "" },
      "native_token": { "enabled": true, "feePercent": 0, "currency": "", "label": "Tokens" },
      "paypal": { "enabled": false, "feePercent": 2.9, "feeFixedCents": 30, "currency": "USD" }
    }
  }
}
```

UI store rails are driven by `getClientStorePaymentRails()` (env + `gateways`) — not by inventing PSP buttons.

### For developers

## Implementation

```mermaid
sequenceDiagram
  participant UI as Checkout UI
  participant SA as placeAndPayStoreOrder
  participant PC as PaymentConductor
  participant PSP as WFP / Stripe / PayPal
  participant WH as /api/payments/*/webhook
  participant H as handlers/*
  UI->>SA: FormData rail + order payload
  SA->>PC: createCheckout(store_order)
  PC-->>UI: CheckoutRedirect
  UI->>PSP: followCheckoutResult
  PSP->>WH: capture / Approved
  WH->>H: webhook-dispatcher → fulfill
```

### Key modules

| Module | Path | Role |
|--------|------|------|
| Conductor | `lib/payments/conductor/payment-conductor.ts` | `createCheckout` by purpose + rail/processor |
| Types (rail vs PSP) | `lib/payments/conductor/types.ts` | `PaymentRail`, `PaymentPurpose`, `CheckoutRedirect` |
| Config | `lib/payments/payment.config.ts` | `getPaymentProvider`, `isRailEnabled`, purpose env map |
| Checkout handoff | `lib/payments/checkout-redirect.ts` | `followCheckoutResult` / `followCheckoutRedirect` |
| Store action | `app/_actions/store-checkout-payment.ts` | Primary store UI path |
| Client rails | `lib/ring-config-client.ts` → `getClientStorePaymentRails` | Card / credit / token / paypal toggles |
| Processors | `lib/payments/processors/*.processor.ts` | wayforpay, stripe, paypal, credit-balance, native-token |
| Dispatcher | `lib/payments/conductor/webhook-dispatcher.ts` | Purpose → handlers |
| Webhooks | `app/api/payments/{wayforpay,stripe,paypal}/webhook` | Canonical PSP callbacks |

### Verified store payment routes (not cart REST)

| Method | Path | Role |
|--------|------|------|
| — | Server Action `placeAndPayStoreOrder` | Primary checkout UI |
| `POST` | `/api/store/payments/card` | Card rail alias → wayforpay route |
| `POST` | `/api/store/payments/wayforpay` | Card via WFP processor path |
| `POST` | `/api/store/payments/stripe` | Card via Stripe |
| `POST` | `/api/store/payments/credit` | Credit rail |
| `POST` | `/api/store/payments/token` | Native token rail |
| `POST` | `/api/store/payments/paypal` | PayPal Orders v2 |
| `GET` | `/api/store/payments/[orderId]/status` | Processing poll |

There is **no** `/api/cart/...` payment surface. Cart state stays client/feature-store; money always enters via Conductor.

### Environment (excerpt)

{`PAYMENT_DEFAULT_PROCESSOR=wayforpay
PAYMENT_STORE_PROCESSOR=
PAYMENT_MEMBERSHIP_PROCESSOR=
PAYMENT_NEWS_PROCESSOR=
PAYMENT_WALLET_TOPUP_PROCESSOR=
PAYMENT_PUBLIC_POOL_CONTRIBUTION_PROCESSOR=
PAYMENT_NATIVE_TOKEN_ONRAMP_PROCESSOR=
PAYMENT_FIAT_CURRENCY=USD
PAYMENT_STORE_ALLOW_CREDIT=true
PAYMENT_STORE_ALLOW_TOKEN=true
NEXT_PUBLIC_PAYMENT_STORE_ALLOW_TOKEN=true
NEXT_PUBLIC_PAYMENT_STORE_ALLOW_PAYPAL=false
CONFIDENTIAL_TOKEN_ONRAMP=false
WAYFORPAY_MERCHANT_ACCOUNT=your_merchant
WAYFORPAY_SECRET_KEY=your_secret
WAYFORPAY_MERCHANT_PASSWORD=your_password
WAYFORPAY_DOMAIN=ring-platform.org
WAYFORPAY_API_URL=https://api.wayforpay.com/api
# STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET=
# PAYPAL_CLIENT_ID= PAYPAL_CLIENT_SECRET= PAYPAL_WEBHOOK_ID= PAYPAL_MODE=sandbox`}

Templates: `env.local.template`, `docker.env.template`, `docker-compose.template.yml`. Ledger: `data/migrations/004_payment_transactions.sql`.

## Frequently asked questions

### Impact

#### Will my clone need a separate cart payment microservice?

No. Store checkout places the order and pays through **PaymentConductor** (Server Action or the store payment routes above). There is no fabricated cart checkout REST API.

#### Does enabling PayPal change the card processor?

No. PayPal is its own **rail**. Card still resolves to WayForPay or Stripe independently.

### Migration

#### We used to document brand buttons in the UI — what now?

Switch copy and components to **rails** (`card` / `paypal` / `credit_balance` / `native_token`). Keep processor selection in config/env. See [PaymentConductor](/docs/features/payment-conductor.md).

#### How do recurring memberships migrate off one-shot Orders?

Use [SubscriptionConductor](/docs/features/subscriptions.md) (PayPal Subscriptions v1, Stripe Subscriptions, WayForPay `regularApi`, credit, native, NFT gate). PaymentConductor still owns one-shot membership upgrades and store Orders.

### Ops

#### Why did the member return from HPP without credit/order paid?

For WayForPay, settlement is **`serviceUrl` webhook Approved** — not `returnUrl`. Check cabinet webhook URL, HMAC secrets, and dispatcher logs.

#### Where do founders see payments in admin?

User detail **Payments** tab → `GET /api/admin/users/[id]/payments` (membership / wallet top-up and other ledger rows).

## Related documentation

  
- [features/payment-conductor](/docs/features/payment-conductor.md) — Deep-dive: purposes, rails vs processors, handlers, and module paths.

  
- [features/wayforpay-integration](/docs/features/wayforpay-integration.md) — Depends-on: WayForPay HPP env SSOT, HMAC, and returnUrl vs serviceUrl money truth.

  
- [features/wallet](/docs/features/wallet.md) — Same-workflow: wallet_topup credit points vs desk/onramp native paths.

  
- [features/subscriptions](/docs/features/subscriptions.md) — Next-step: recurring membership SubscriptionConductor (PayPal Subscriptions v1).

  
- [architecture/payment-conductor](/docs/architecture/payment-conductor.md) — Deep-dive: CheckoutRedirect DTO, idempotency, and webhook dispatcher.

  
- [customization/payment-integration](/docs/customization/payment-integration.md) — Next-step: operator PSP setup checklist for a new clone.

  
- [features/ring-oracle](/docs/features/ring-oracle.md) — Depends-on: main_currency / native_token rates for presentment and jar FX.

  
- [features/public-pools](/docs/features/public-pools.md) — See-also: public_pool_contribution desk oracle — not PSP donate products.
