name: payment-domain-knowledge description: Payment processing domain model for Perkcord — our role in the ecosystem, provider relationships, credential ownership, PCI scope, and merchant vs platform responsibilities. compatibility: opencode metadata: audience: maintainers repo: perkcord
Purpose
Establish a shared mental model for how Perkcord relates to the payment processing ecosystem. This skill should be loaded when working on payment provider integrations, checkout flows, credential management, onboarding, or PCI-related decisions.
Perkcord's role in the ecosystem
Perkcord is a software platform, not a payment facilitator (PayFac) and not a merchant of record. We are analogous to WooCommerce, Shopify (without Shopify Payments), or a SaaS billing tool.
What we do
- Build the checkout UI and subscribe flow
- Collect a payment token (never raw card data) via provider-hosted or provider-JS tokenization
- Send that token to the payment gateway using the guild owner's credentials
- Listen for webhooks to update entitlement state
- Sync Discord roles based on entitlement state
What we do NOT do
- Hold merchant accounts
- Go through underwriting
- Bear liability for chargebacks (the merchant does)
- Set or control transaction fee structures
- Have a direct relationship with acquiring banks or card networks
- Store, process, or transmit raw card data
The players
| Role | Who | Example |
|---|---|---|
| Card networks | Set the rules | Visa, Mastercard, Amex, Discover |
| Issuing bank | Customer's bank (issues their card) | Chase, Capital One |
| Acquiring bank | Merchant's bank (receives funds) | Provided via the ISO/processor |
| Payment gateway | Technology that routes transactions | NMI, Authorize.Net, Stripe |
| ISO / Processor | Bundles merchant account + gateway | The "high-risk provider" a client works with, or Stripe directly |
| Merchant | Entity legally selling goods/services | The guild owner / community operator |
| Perkcord | Software platform automating checkout + entitlements | Us |
Credential ownership
Every set of provider credentials belongs to the merchant (the guild owner), not to Perkcord.
The guild owner:
- Applies for a merchant account (through an ISO, processor, or directly with Stripe)
- Goes through underwriting (business verification, financial history, risk assessment)
- Gets approved and receives API credentials from their provider dashboard
- Provides those credentials to Perkcord (via the admin onboarding wizard)
Current state: credentials are stored as deployment-level environment variables (one merchant per deployment). This works for single-tenant / white-label deployments. Target state (see #163): credentials will be stored encrypted per-guild in Convex so each guild owner can configure their own merchant account. See reference/credentials-by-provider.md for the specific values each provider requires.
PCI compliance scope
PCI DSS requires anyone in the payment card chain to prove security compliance via a Self-Assessment Questionnaire (SAQ). The type depends on how much card data your system touches.
| SAQ Level | Questions | When it applies | Our providers |
|---|---|---|---|
| SAQ-A | ~22 | Card data never enters your page DOM. Entire form is hosted by the provider (redirect, iframe, or provider lightbox). | Stripe (hosted checkout), NMI (hosted URL), Authorize.Net with AcceptUI.js |
| SAQ A-EP | ~191 | Card data enters your page DOM (custom form fields) but JS sends it directly to the provider — never hits your server. | Historical Accept.js-only custom forms (not current Perkcord flow) |
| SAQ-C | ~160 | Card data passes through your server but you don't store it. | Not applicable to us |
| SAQ-D | ~329 | You store or fully process card data yourself. | Not applicable to us |
Current posture: all shipping Perkcord payment collection paths are SAQ-A. Stripe uses hosted checkout, NMI uses a hosted checkout URL, and Authorize.Net uses AcceptUI.js hosted lightbox tokenization. The old Accept.js custom-form posture is now historical context only.
Invariants (from AGENTS.md)
- Never store raw card data. Always tokenize.
- Never log secrets, OAuth tokens, or full webhook bodies with PII.
- Tokenization approach per provider: Stripe handles it via hosted checkout; Authorize.Net via AcceptUI.js hosted lightbox; NMI via hosted checkout (Collect.js is a future option, not currently implemented).
Fee structure (not our concern)
Transaction fees are negotiated between the merchant and their ISO/processor. Perkcord does not set, control, or participate in these fees. We charge our own separate SaaS fee for the platform.
Typical high-risk ranges (for awareness, not something we configure):
- Transaction fee: 2.5%–5%+ per transaction
- Per-transaction flat fee: $0.10–$0.30
- Monthly gateway fee: $10–$50/mo
- Chargeback fee: $15–$100 per chargeback
- Rolling reserve: 5–10% held for 6 months
Authorize.Net recurring billing rule
When working on Authorize.Net subscriptions, do not treat ARB as an instant first-payment mechanism.
- ARB is batch-based, not real-time. Same-day starts are processed on the next overnight batch, so subscription creation alone is not proof of payment.
- The recommended Perkcord pattern is: real-time first charge now, then recurring renewals next cycle.
- Model the first payment as a customer-initiated transaction (CIT) and later renewals as stored-credential / recurring merchant-initiated transactions (MIT).
- Do not grant access from
subscription.createdalone unless the product explicitly supports a provisional-access state. - Do not synthesize provider payment-success events just to make access feel instant; if provisional access is ever needed, track it as a separate internal state with its own audit trail.
Recommended implementation shape
- Tokenize card data with AcceptUI.js (preferred - SAQ-A, hosted lightbox, compatible with the ARB flow) or Accept Hosted for one-time-only flows.
- Run a real-time first
createTransactionRequest/authCaptureTransaction. - Create or fetch the stored customer payment profile from that successful charge.
- Create the ARB subscription for future renewals, with
startDatealigned to the next billing period instead of "today". - Persist the provider identifier that the eventual webhook will actually send back, so post-charge recovery paths still have a durable entitlement anchor.
- Keep payment-provider events audit-pure: real provider events come from provider responses/webhooks, not internal convenience writes.
Sharp edges
- End-of-month monthly starts must clamp to the last valid day of the target month.
- A successful immediate charge can be followed by profile-creation or ARB-setup failure; the checkout flow must avoid surfacing that as an unpaid failure while still preserving a webhook-reachable provider link.
- If the product wants "charge later, access now," use an explicit provisional-access design or ARB trial semantics; do not blur that with a paid entitlement.
Provider reference files
For provider-specific details (credentials, APIs, sandbox setup, tokenization approach), see:
reference/credentials-by-provider.md— What credentials each provider requires and who provides themreference/stripe.md— Stripe-specific integration detailsreference/authorize-net.md— Authorize.Net-specific integration detailsreference/nmi.md— NMI-specific integration details
When to load this skill
- Working on checkout or subscribe flow code
- Adding or modifying a payment provider integration
- Working on the admin onboarding wizard or credential management
- Making PCI-related architecture decisions
- Answering questions about who owns what in the payment chain
- Debugging webhook or transaction failures