Customer Payments — Gateway Setup & Reports Guide
Process credit cards or record manual payments from inside the CRM. V2 hardens an already-functional charge / refund flow with AES-256-GCM credential encryption, tenant-admin reports, refund UI, and Activity-timeline logging — all without changing the Mac-app v2 read API contract.
Overview
Customer Payments is a multi-gateway processor for one-time charges. Each tenant brings their own Stripe / Square / Authorize.Net / NMI / Braintree / PayPal account. Card data is tokenized on the client (via Stripe Elements or the equivalent gateway SDK); the server only ever sees a token. Successful charges, declines, and refunds are logged to payment_attempts with the customer, agent, gateway, amount, and result.
V2 is a security + reporting hardening pass on top of an already-shipped charge flow. The processing pipeline itself is unchanged — only the credential storage, the report tooling, and the audit trail.
What's new in V2
AES-256-GCM credential encryption
Gateway credentials are AES-256-GCM encrypted at rest via CredentialsCipher — per-record random IV + auth tag, tenant_id in the AAD so a leaked ciphertext from tenant A can't be replayed as tenant B's credentials. Backward-compat readback keeps any unmigrated rows working through the transition; they lazy-upgrade on next save.
Tenant-admin reports page
New page at /tenant-admin/modules/customer-payments-reports.php. Filter by date range, agent, status, payment method, customer search. Per-agent and per-method roll-ups. CSV export of the current filter set.
Refund UI in the agent payments tab
The refund API existed but had no button. V2 adds a per-row Refund button on succeeded gateway charges. Clicking confirms the amount, prompts for an optional reason, and posts to the same processing endpoint.
Activity-timeline logging
Successful payments and refunds now write a row to interactions so they show alongside calls / emails / notes on the customer's Activity tab. The card brand + last 4 are included in the note.
CSRF + audit on credential save
The settings page now requires a valid CSRF token before mutating gateway credentials. Every gateway create / update writes a row to audit_log with the actor's user_id, action, and gateway name.
Sysadmin state viewer
New /admin/view-payments-state.php shows schema health, tenant activity, gateway encryption status (encrypted vs plaintext per row), recent attempts, and credential change history. Browser-only ops — no MySQL CLI needed.
Credential encryption
Algorithm: AES-256-GCM. Master key from .env (ENCRYPTION_KEY). Per-record 12-byte random IV. 16-byte GCM authentication tag. Tenant_id mixed into the additional-authenticated-data (AAD) so a ciphertext leaked from tenant A cannot be relabeled and replayed as tenant B's credentials.
Storage format on payment_gateway_configs.credentials_encrypted:
ENCv1:base64( IV (12 bytes) ‖ TAG (16 bytes) ‖ CIPHERTEXT )
Backward-compat: CredentialsCipher::decrypt() detects the ENCv1: prefix. If absent, it falls through to json_decode and returns the legacy plaintext as an array — V1 tenants keep working unchanged. The next time that tenant edits their gateway in the settings page, V2 saves in the modern format. State viewer's "Plaintext creds" stat counts how many rows still need this lazy upgrade.
ENCRYPTION_KEY is rotated, all existing encrypted rows become unreadable. The migration verification page round-trips the cipher on every load to surface that issue early. Don't rotate the key without a migration plan.
Charge flow
- Agent opens customer profile → Payments tab → clicks Charge Payment.
- Modal renders with card fields (Stripe Elements iframe) or a manual-entry tab (cash / check / wire / etc.).
- For card: the customer's card is tokenized client-side. The CRM never sees a PAN.
- POST to
/crm/api/payments/process.php?action=chargewith the token + amount + customer_id. - Server creates a
payment_attemptsrow with status='pending' BEFORE calling the gateway (idempotency). - Server decrypts the tenant's gateway credentials via
CredentialsCipher::decrypt()and dispatches to the matchingPaymentGatewayFactoryimplementation. - Gateway returns succeeded / declined / failed. Server marks the attempt row accordingly.
- On success: writes an
interactionsrow so the customer Activity tab shows the payment.
Refunds
Every succeeded gateway charge in the customer's Payments history shows a Refund button (admin or agent role). Clicking it:
- Confirms the refund amount with the agent (defaults to full).
- Prompts for an optional reason (recorded on both the attempt and the interaction row).
- POSTs
action=refund&transaction_id=.... - Server creates a NEW
payment_attemptsrow with negative amount + payment_method='refund'. - Server calls the gateway's refund API. On success, marks the new row succeeded and writes a refund row to
interactions. - Customer's running balance increments back by the refunded amount.
Manual payments (cash / check / wire) don't get a Refund button — to reverse a manual payment, record a new manual entry with negative amount instead. Different audit trail, same attempt log.
Reports
Tenant admins open /tenant-admin/modules/customer-payments-reports.php:
- Filters: date range (default last 30 days), agent (who processed), status, payment method, customer search (name / email / id)
- Top stats: attempts, collected, refunded, net, declined / failed
- By agent: one row per agent with attempts / succeeded / declined / collected
- By method: one row per payment method with attempts + collected
- Detail list: paginated 50/page with date, customer, agent, method, status, amount, transaction ID, failure reason
- CSV export: streams the entire filtered set as CSV — no pagination cap
Common use: end-of-month reconciliation against the gateway's own dashboard, agent commission calculations, dispute / chargeback investigation.
Mac-app contract preservation
Rubi's macOS app reads payment data through /api/v2/payments.php with 7 actions: customer_history, summary, detail, gateways, recent, stats, record_manual. V2 made zero changes to that file. The schema columns the Mac app reads (payment_attempts.*) are unchanged.
The encryption is transparent to the Mac because that v2 endpoint never returns gateway credentials — it only returns attempt history (where credentials don't appear) and accepts manual-payment records (which never touch credentials). Apple App Review can proceed unblocked.
Quick start ≈ 15 minutes
- Activate the module at RubiMine. Free.
- Sysadmin verifies schema + cipher at
/admin/run-payments-v2-migration.php. All 3 tables ✅, cipher round-trip ✅. - Plug in your gateway credentials at customer-payments-settings.php. Pick Stripe (recommended), paste your publishable + secret key, click Save → V2 encrypts before write.
- Test connection on the saved gateway. Confirms the credentials work.
- Mark it default if it's the only one, or pick which gateway to default to.
- Take a test payment from any customer's Payments tab. Use Stripe's
4242 4242 4242 4242test card if your gateway is in sandbox mode. - Confirm the Activity tab shows the payment row.
- Open the reports page at
/tenant-admin/modules/customer-payments-reports.php— filter by today, see your test payment, export CSV.
Known limitations (V2)
- No recurring billing — by design. One-time charges only. Use your gateway's native subscription tools for recurring schedules.
- No card vaulting — every charge requires a fresh tokenize. Cards aren't saved on Rubi for re-use.
- Receipt emailing isn't wired — the
payment_receiptsschema exists but no mailer integration. V2.1. - Partial-refund tracking — refunds are recorded as separate negative-amount rows; the original attempt's status doesn't flip to
partially_refundeduntil V2.1. - Webhook idempotency — Stripe webhooks land at
/api/stripe-webhook.php(separate file). Receipt processing for off-session events (chargebacks, disputes) needs explicit handling — not yet ingested intopayment_attempts. - Manual refund of manual payment — no UI button; record a negative-amount manual entry instead.
- Encryption key rotation — no automated rotation. Manual key change requires a re-encrypt migration.
Frequently asked questions
Does Rubi store credit card numbers?
What changed in V2?
What gateways are supported?
Does V2 support recurring billing?
What's the Mac-app contract?
Is there an audit log?
How much does it cost?
Ready to take payments inside the CRM?
Activate, plug in your Stripe key, and your agents are charging cards in 15 minutes. No card data on Rubi servers.