Payments

Bring your own provider keys

All third-party integrations use BYO (Bring Your Own) credentials. No keys are bundled; Calbok never touches your providers' APIs using Author or Operator-level credentials on behalf of tenants.

Stripe booking deposits (per business)

Tenants bring their own Stripe keys. The Operator does not configure payment keys in .env. Each tenant enters their Stripe publishable + secret keys in Tenant → Settings → Payments.

In the Stripe Dashboard (test or live), create a webhook endpoint at:

https://your-domain.com/webhooks/booking/{tenant}/stripe/test

(or /stripe/live for live mode). Subscribe to payment-intent events your gateway driver documents. Paste the signing secret into the tenant's gateway row.

The Diagnostics → Payment gateway configuration probe:

  • FAIL if the payment_gateways table is absent (run migrations).
  • WARN if no tenants have configured a gateway yet (expected on a fresh install).
  • OK once at least one tenant has activated a gateway.

Separate money flows: Booking deposits go straight to each business's own payment account (Stripe, PayPal, and the other gateways below). Your Calbok subscription billing (what tenants pay you) uses different keys and webhooks — never mix the two.

PayPal sandbox booking deposits (per business)

Tenants can also take deposits via PayPal (Orders v2, capture on return). Like Stripe, keys are BYO per tenant — never Operator .env.

Sandbox setup (recommended before going live):

  1. Create a REST app in the PayPal Developer Dashboard (Sandbox → Apps & Credentials).
  2. Copy the Client ID and Secret for the Sandbox app.
  3. Under the app, create a Webhook pointing at: https://your-domain.com/webhooks/booking/{tenant}/paypal/test Subscribe at least to: CHECKOUT.ORDER.APPROVED, PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED, PAYMENT.CAPTURE.REFUNDED, PAYMENT.CAPTURE.REVERSED.
  4. Copy the webhook Webhook ID.
  5. In Tenant → Settings → Payments, choose PayPal, mode test, paste Client ID / Secret / Webhook ID, set the account currency, and Save. Calbok verifies OAuth (and the webhook id when provided) before activating the row.
  6. Book a deposit-required appointment on the public booking page. When PayPal is the chosen method, the customer is redirected to PayPal sandbox; after approval they return to /{tenant}/booking/payment/return/paypal where Calbok captures the order server-side (the query string is never trusted for status).

Live mode: switch mode to live, use Live credentials + a Live webhook URL ending in /paypal/live, and re-save.

Razorpay test-mode booking deposits (per business)

Tenants in India (and accounts with international currencies enabled) can take deposits via Razorpay (Orders API + Checkout). Keys are BYO per tenant — never Operator .env. Calbok talks to Razorpay over plain HTTPS; do not install a Razorpay SDK.

Test-mode setup:

  1. In the Razorpay Dashboard, switch to Test Mode and open Account & Settings → API Keys. Generate a test key. The Key ID starts with rzp_test_ (live keys start with rzp_live_ and must be saved in live mode).
  2. Under Account & Settings → Webhooks, add: https://your-domain.com/webhooks/booking/{tenant}/razorpay/test Subscribe to: payment.captured, payment.failed, refund.processed, refund.failed, payment.dispute.created. Set a webhook secret that is different from the API key secret.
  3. In Tenant → Settings → Payments, choose Razorpay, mode test, paste Key ID, Key secret, and Webhook secret, set the account currency (INR, or another currency Razorpay has enabled on the account — not a three-decimal currency), and Save. Calbok checks the key prefix and lists one order (GET /v1/orders?count=1) before activating the row. No money moves.
  4. Book a deposit-required appointment and choose Razorpay. Checkout opens on the booking page. After payment, the page posts the signature to /{tenant}/booking/payment/callback/razorpay, which verifies it with the key secret and fetches the payment before recording the deposit.

Live mode: switch the dashboard to Live Mode, use a rzp_live_ key, point the webhook at the same path ending in /razorpay/live, and re-save in mode live.

Paystack test-mode booking deposits (per business)

Tenants in Nigeria, Ghana, South Africa, or Kenya can take deposits via Paystack. Keys are BYO per tenant — never Operator .env. Test and live use the same API host; the key prefix selects the mode.

Test-mode setup:

  1. Create a free account at Paystack and open Settings → API Keys & Webhooks.
  2. Copy the Test Public Key (pk_test_…) and Test Secret Key (sk_test_…).
  3. Set the webhook URL to: https://your-domain.com/webhooks/booking/{tenant}/paystack/test Paystack signs the raw body with the secret key (HMAC-SHA512 in X-Paystack-Signature). There is no separate webhook secret to paste.
  4. In Tenant → Settings → Payments, choose Paystack, mode test, paste both keys, set the settlement currency (NGN, GHS, ZAR, KES, or USD), and Save. Calbok checks the key prefix and a no-charge API call before activating the row. A live key saved in test mode (or the reverse) stays inactive.
  5. Book a deposit-required appointment whose location currency matches that settlement currency. The customer is redirected to Paystack; after payment they return to /{tenant}/booking/payment/return/paystack. Calbok confirms the deposit from Paystack's verify API — the reference query string is not treated as proof of payment.

Live mode: switch mode to live, paste pk_live_… / sk_live_…, point the webhook at the same path ending in /paystack/live, and re-save.

Mercado Pago test-mode booking deposits (per business)

Tenants in Argentina, Brazil, Chile, Colombia, Mexico, Peru, or Uruguay can take deposits via Mercado Pago Checkout Pro. Keys are BYO per tenant — never Operator .env. Calbok talks to Mercado Pago over plain HTTPS; do not install a Mercado Pago SDK. These keys are booking deposits only. They are not used for Operator subscription billing.

Test-mode setup:

  1. In Your integrations, create an application and open Testing credentials. Copy the Access Token. Test tokens start with TEST-. Production tokens do not (they are issued as APP_USR-…) and must be saved in live mode.
  2. Under Webhooks → Configure notifications, set the test URL to: https://your-domain.com/webhooks/booking/{tenant}/mercadopago/test Subscribe to Payments (payment). Chargebacks (topic_chargebacks_wh) are optional. Reveal the secret signature. It must be different from the access token. Calbok also sends this URL as notification_url when it creates a checkout; the application secret still signs those notifications.
  3. In Tenant → Settings → Payments, choose Mercado Pago, mode test, paste the access token and webhook secret, set the account currency (ARS, BRL, CLP, COP, MXN, PEN, or UYU), and Save. Colombia (COP) amounts must be whole pesos. Calbok checks the token prefix and calls GET /users/me before activating the row. No money moves.
  4. Book a deposit-required appointment and choose Mercado Pago. The customer is sent to the sandbox checkout (sandbox_init_point). After payment they return to /{tenant}/booking/payment/return/mercadopago, where Calbok fetches the payment and does not trust the query-string status.

Payments created with test credentials do not send webhooks. The return URL and the reconcile sweep record the deposit in test mode. Use Webhooks → Simulate in Your integrations to check the signature before going live.

Live mode: use production credentials, point the production webhook at the same path ending in /mercadopago/live, and re-save in mode live.

Mollie test-mode booking deposits (per business)

Tenants in the EEA or the UK can take deposits via Mollie. Keys are BYO per tenant — never Operator .env. Calbok talks to Mollie over plain HTTPS; do not install a Mollie SDK. These keys are booking deposits only. They are not used for Operator subscription billing.

Test-mode setup:

  1. In the Mollie Dashboard, open Developers → API keys and copy the Test API key. Test keys start with test_. Live keys start with live_ and must be saved in live mode.
  2. Calbok registers the classic webhook itself when it creates the payment (webhookUrl), as long as the site URL is publicly reachable. Mollie then POSTs the payment id, and Calbok confirms the payment with your API key. Next-gen webhooks are optional (they are still in Mollie's beta). To use them, open Developers → Webhooks, create a webhook, and set the URL to: https://your-domain.com/webhooks/booking/{tenant}/mollie/test Subscribe to payment.paid, payment.failed, payment.canceled, payment.expired, and dispute.created. Generate a signing secret. It must be different from the API key. If you leave the signing secret blank, only classic webhooks are accepted, and a request that sends X-Mollie-Signature is rejected.
  3. In Tenant → Settings → Payments, choose Mollie, mode test, paste the API key and (only if you use next-gen) the signing secret, set the account currency (for example EUR or GBP), and Save. Whole units are required for HUF, TWD, and ISK. Calbok checks the key prefix and calls GET /v2/payments?limit=1 before activating the row. No money moves.
  4. Book a deposit-required appointment and choose Mollie. The customer is sent to Mollie's checkout. After payment they return to /{tenant}/booking/payment/return/mollie, where Calbok fetches the payment and does not trust the query string.

Live mode: use the live API key and re-save in mode live. If you use next-gen webhooks, point a live webhook at the same path ending in /mollie/live and paste that webhook's signing secret. Live webhook URLs must be HTTPS.

MyFatoorah test-mode booking deposits (per business)

Tenants in Kuwait, the UAE, Bahrain, Jordan, Oman, Saudi Arabia, Qatar, or Egypt can take deposits via MyFatoorah. Keys are BYO per tenant — never Operator .env. Test and live are different API hosts. The token has no test/live prefix.

Test-mode setup:

  1. Register a demo account at registertest.myfatoorah.com (Kuwait is the usual demo country; skip bank details) and ask MyFatoorah to activate it, or copy the public test token from the API key docs. Do not commit that token into the application.
  2. In the demo portal, open Integration Settings → Webhook, enable Webhook v2, set a webhook secret, and set the endpoint to: https://your-domain.com/webhooks/booking/{tenant}/myfatoorah/test Subscribe to payment-status, refund-status, and dispute events. MyFatoorah signs an ordered field list (not the raw body) with HMAC-SHA256 in myfatoorah-signature. The secret is not the API token.
  3. In Tenant → Settings → Payments, choose MyFatoorah, mode test, paste the API token, set the country (KWT, ARE, BHR, JOR, OMN, SAU, QAT, or EGY), paste the webhook secret, and set the settlement currency to that country's currency (KWD, AED, BHD, JOD, OMR, SAR, QAR, or EGP). Save. Calbok checks the pairing and calls InitiatePayment (no charge) before activating the row. A mismatched country and currency stays inactive.
  4. Book a deposit-required appointment whose location currency matches. The customer is redirected to the MyFatoorah invoice. After payment they return to /{tenant}/booking/payment/return/myfatoorah/test. Calbok confirms the deposit from GetPaymentStatus — the paymentId query string is not treated as proof of payment. Kuwaiti, Bahraini, Omani, and Jordanian amounts use three decimal places (KWD 1.250).

Live mode: create the token in the live portal for that country, point the webhook at the same path ending in /myfatoorah/live, and re-save in mode live. Live API hosts differ by country (for example Saudi Arabia uses api-sa.myfatoorah.com).

Operator subscription billing — Stripe, PayPal, and Razorpay

This is how you (the Operator) charge tenants for Calbok. It is separate from the booking-deposit gateways above: a tenant's PayPal, Razorpay, Paystack, Mercado Pago, MyFatoorah, or Mollie keys take booking deposits, and these keys take SaaS subscriptions. Do not paste one set into the other screen. MyFatoorah and Mollie are booking deposits only.

Configure this in Admin → Settings → Billing providers. You do not need to edit .env. Values saved there override BILLING_* and the Cashier/Spike keys at runtime; anything you never save on that page keeps the .env value. Secrets are encrypted and the form never shows them again — leave the field blank to keep the saved secret. Each provider shows the webhook URL to paste into that provider's dashboard, and Test connection checks the keys without taking a payment.

  1. Choose the default provider: Offline (you mark a tenant paid by hand; no keys), Stripe, PayPal, or Razorpay.
  2. Stripe (Spike): enable it, choose test or live, and paste the publishable key, secret key, and webhook signing secret. Map each Calbok plan's monthly and yearly price id. The webhook URL on the page is the Cashier endpoint (/stripe/webhook). A Stripe subscription still uses the in-app portal at /billing.
  3. PayPal: create a REST app (sandbox first) and a webhook for the URL shown on the page (https://your-domain.com/webhooks/billing/paypal). Subscribe to BILLING.SUBSCRIPTION.*, BILLING.SUBSCRIPTION.PAYMENT.FAILED, and PAYMENT.SALE.COMPLETED. Paste the client id, secret, and webhook id, set mode to test, and map each plan's PayPal plan id (monthly and yearly). A self-signup on that plan is sent to PayPal to approve the subscription. The in-app page at /billing shows status, the next charge, and cancel.
  4. Razorpay: webhook https://your-domain.com/webhooks/billing/razorpay (shown on the page). Use a webhook secret that is not the API key secret. Test key ids start with rzp_test_ and must be saved in test mode (rzp_live_ with live). Map plan ids the same way, and set billing cycles (12 is one year of monthly billing).
  5. A nightly billing:reconcile poll heals a missed webhook. Diagnostics reads the same effective settings and warns if PayPal or Razorpay is enabled without a webhook id/secret or a plan map.

The same keys can still be supplied in .env (BILLING_PAYPAL_*, BILLING_RAZORPAY_*, Cashier STRIPE_KEY / STRIPE_SECRET / STRIPE_WEBHOOK_SECRET) before the first visit to the page. After you save the page, the saved values win.

Bank transfer / offline: enable the Offline gateway in the same Payments screen (account name, bank, IBAN/account number, optional SWIFT + instructions). Customers see transfer instructions + a unique reference; staff with mark_booking_payments_paid mark the deposit paid from the calendar Payments panel.