Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesPaymentsPayments

Payments

Provider integrations, transaction lifecycle, refunds, and webhook dispatch for member-facing money flow inside a gym.

What & why

Payments is the B2B2C money rail: a member pays the gym, the gym keeps the cash, FitKit only collects platform fees on the side (see platform-billing/). Every charge runs through a per-org payment_provider_configs row holding encrypted credentials for the org’s chosen gateway. Multiple Israeli acquirers are supported in parallel; the org picks one.

Persona impact:

PersonaSurface
MemberBuy a plan, register a card, see receipts (/dashboard/plans, /buy/courses/[id]). Cancellation + refund request UI.
Owner / AdminConfigure provider, issue refunds, close manual-refund tasks, review cancellation requests, see analytics (/dashboard/payments).
Platform (FitKit)Observability only — money never lands in a FitKit account on this rail.

Capabilities

  • Hosted-page checkout (Cardcom LowProfile, iCredit, Meshulam, Tranzila, Morning) — see Providers.
  • Tokenised recurring charges driven by FitKit (no native recurring deals on Cardcom).
  • Refunds split by capability: automatic (single API call) vs manual (opens a manual_refund task the owner closes with the credit-doc number).
  • Verify-on-return fallback when a provider webhook never arrives (POST /organizations/:orgId/payments/verify-return).
  • Tax-document linkage via payment_provider_clients (Morning) + invoicing_configs (GreenInvoice plug-in scaffold).
  • Card-on-file registration by an admin on behalf of a member (POST /members/:id/register-card).

Providers

Discovered from apps/api/src/payments/providers/. Registered in PaymentsModule.onModuleInit.

ProviderAdapterWebhook URL formSignature validationRefund capabilityNotes
cardcomcardcom.provider.tspath /webhooks/payments/cardcom/:orgIdnone — Cardcom does not sign; trust comes from re-fetching GetLpResult server-sidemanualDrives platform-billing too.
icrediticredit.provider.tspath /webhooks/payments/icredit/:orgIdGroupPrivateToken body field equals stored credentialmanualRivhit-backed credit document.
meshulammeshulam.provider.tspath /webhooks/payments/meshulam/:orgIdwebhookKey body field equals stored apiKeymanualLight-API iframe tokenisation.
morningmorning.provider.tsquery /webhooks/payments/morning?org=… (single statically-registered URL per business)TBD — temporary accept; controller dumps headers for debuggingmanualAuto-issues חשבונית מס/קבלה.
tranzilatranzila.provider.tspath /webhooks/payments/tranzila/:orgIdstub — always valid (HMAC SHA256 TODO)manualAdapter is mostly a stub; not production-validated.
test(enum value only, no adapter)n/an/an/aReserved for stubbed e2e seeding.

All providers are manual for refunds today. The framework distinguishes automatic | manual (apps/api/src/payments/services/payment.service.ts:411); flipping a provider once we’ve verified its refund API end-to-end is a single return-value change.

  • subscriptions-plans/ — owns the subscription state machine and cancellation requests. Payments fires the lifecycle transitions via webhook handlers.
  • platform-billing/ — separate money rail for FitKit’s own monthly fee. Uses the same CardcomProvider adapter but a different DB schema and a single shared terminal (PLATFORM_BILLING_* env).
  • courses/ — course checkouts share the hosted-payment plumbing; the webhook activates a course_entitlements row instead of a subscription.
  • platform-tiers/ — the automated_billing feature is gated to tier pro; the @RequiresFeature guard fronts plan CRUD.
  • webhooks/ — the Clerk webhook lives here; payment provider webhooks live in apps/api/src/payments/controllers/payment-webhook.controller.ts (registered under PaymentsModule, not WebhooksModule).

Status

Production: Cardcom (live + platform billing terminal), iCredit, Meshulam. Beta / debug: Morning (signature TBD — see morning.provider.ts:941), Tranzila (signature stub). Refund automation: nothing in automatic yet — all providers open a manual task.

Gaps

  • FIT-133 — webhook idempotency hardening: current logic short-circuits on status === 'completed' per row, but no row-level advisory lock; concurrent webhook + verify-return can race on the period-advance update (webhook-processing.service.ts:257).
  • FIT-134 — capability flip: validate Cardcom RefundDeal so its capability can move from 'manual''automatic'.
  • FIT-136 — failed renewal retry tuning: RECURRING_INTERVAL_DAYS = [3, 7, 14] is hardcoded in recurring-charge.service.ts:21; needs per-org override and a max-retries CTA path.
  • Morning webhook signaturemorning.provider.ts:941 returns true regardless. Header capture is in place (payment-webhook.controller.ts:101); finalise the scheme before scaling Morning usage.
  • Tranzila adapter — signature + invoicing methods are stubs (tranzila.provider.ts:135, tranzila.provider.ts:155). Treat as alpha.

See also docs/_archive/product/payments-prd.md (historical context, stale on the manual-refund task design) and docs/_archive/plans/cardcom-production-terminal.md (Cardcom rollout plan).