Billing & License Fulfillment¶
firmflow. integrates with Paystack to provide a seamless, automated licensing experience for firms while maintaining the strict security required for on-premise software.
Overview¶
The billing system is designed with a "Hybrid-Cloud" approach:
1. Checkout: Users initiate payments via the PaystackCheckout component.
2. Verification: Payments are verified via cryptographic signatures from Paystack.
3. Fulfillment: Licenses are generated locally using the firm's LICENSE_SECRET and stored in the firm's local database.
Components¶
1. Paystack Integration (src/lib/paystack.ts)¶
Handles communication with the Paystack API. Includes:
- verifyTransaction: Direct verification for client-side completion.
- verifyWebhookSignature: SHA-512 HMAC verification for secure server-to-server callbacks.
2. Billing Service (src/lib/billing-service.ts)¶
The core fulfillment engine. It is idempotent by design, ensuring that a single payment reference never issues multiple licenses. - Checks for existing audit logs with the same reference. - Generates a cryptographically signed license key. - Deactivates old licenses within an atomic database transaction.
Webhooks¶
firmflow. provides a secure webhook endpoint at:
POST /api/billing/webhook/paystack
Configuration¶
- Log in to your Paystack Dashboard.
- Navigate to Settings > API Keys & Webhooks.
- Set the Webhook URL to
https://your-domain.com/api/billing/webhook/paystack. - Copy your Secret Key and set it as
PAYSTACK_SECRET_KEYin your.env.
Resilience¶
The webhook handler ensures license delivery even if the user: - Closes their browser before the "Success" screen. - Experiences a network failure after payment. - Refresh the page prematurely.
Security¶
Licenses are generated using a symmetric HMAC signature. To verify a license, the system requires: - The Firm ID. - The License Secret (only known to the vendor and the firm's installation). - The License Metadata (Tier, Expiry, Seats).
Any manual tampering with the license key stored in the database will cause it to be invalidated by the integrity checker.