Cashier (Payments)
Cashier is a multi-gateway billing plugin for Nimbus, modeled on Laravel Cashier but gateway-agnostic. One app can register several payment gateways (Stripe, Razorpay, PayU, …), choose a default, select a gateway per request, verify webhooks with signature checks, and gate access behind a paywall.
Installation
Register the plugin in bin/server.go. With FromEnv it auto-registers any gateway whose credentials are present in the environment:
import (
"github.com/CodeSyncr/nimbus/plugins/cashier"
"github.com/CodeSyncr/nimbus/plugins/cashier/events"
)
app.Use(cashier.NewPlugin(cashier.Config{
FromEnv: true, // register gateways whose env keys are set
Default: "razorpay", // default gateway
OnWebhook: func(e cashier.WebhookEvent) error {
switch events.Normalize(e) {
case events.PaymentSucceeded: // grant paywall access
case events.SubscriptionCancelled: // revoke access
}
return nil
},
}))
The plugin also runs migrations for its cashier_transactions and cashier_subscriptions tables, and binds cashier, cashier.manager, and cashier.paywall into the container.
Gateways & environment variables
| Gateway | Region | Env vars |
|---|---|---|
| Stripe | International | STRIPE_KEY, STRIPE_WEBHOOK_SECRET |
| Razorpay | India | RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET, RAZORPAY_WEBHOOK_SECRET |
| PayU | India | PAYU_MERCHANT_KEY, PAYU_MERCHANT_SALT, PAYU_PAYMENT_URL |
The default gateway is Config.Default, else PAYMENTS_DEFAULT_GATEWAY, else the first registered. Register gateways explicitly instead of FromEnv when you need custom config:
m := cashier.NewGatewayManager()
m.Register(gateways.NewRazorpay(gateways.RazorpayConfig{KeyID: id, KeySecret: secret, WebhookSecret: whs}))
m.Register(gateways.NewStripe(gateways.StripeConfig{SecretKey: sk, WebhookSecret: whs}))
m.SetDefault("razorpay")
app.Use(cashier.NewPlugin(cashier.Config{Manager: m}))
Starting a payment
Resolve the facade from the container and create a charge. Pass an empty gateway name to use the default, or a specific name to route to a chosen gateway:
cash := app.Container.MustMake("cashier").(*cashier.Cashier)
// Default gateway (e.g. Razorpay in India):
charge, err := cash.Charge(ctx, "", cashier.ChargeParams{
Amount: 49900, // smallest unit — paise for INR, cents for USD
Currency: "INR",
Reference: "order_123",
CustomerEmail: "user@example.com",
})
// A specific gateway for an international customer:
charge, err = cash.Charge(ctx, "stripe", cashier.ChargeParams{
Mode: "subscription", PriceID: "price_abc",
SuccessURL: "https://app/ok", CancelURL: "https://app/cancel",
})
Stripe returns charge.RedirectURL (redirect the browser). Razorpay/PayU return an order/txn id the frontend opens via the checkout widget (see views/checkout.nimbus).
Webhooks
The plugin mounts a signature-verified endpoint per gateway at /payments/<gateway>/webhook (configurable via WebhookPrefix). Each gateway verifies its own signature (Stripe/Razorpay HMAC-SHA256; PayU SHA-512 hash) before your OnWebhook runs. Point each provider's dashboard webhook at the matching URL and set the corresponding *_WEBHOOK_SECRET. Use events.Normalize(evt) to branch on canonical events across all gateways.
Paywall
Grant access on a successful payment and gate routes with RequirePlan (returns HTTP 402 when denied). The default store is in-memory; implement EntitlementStore for a database-backed paywall.
pw := app.Container.MustMake("cashier.paywall").(*cashier.Paywall)
// In OnWebhook, on events.PaymentSucceeded:
pw.Grant(userID, "pro", time.Now().Add(30*24*time.Hour))
// Gate a route group:
subject := func(c *http.Context) string { return currentUserID(c) }
app.Router.Group("/pro", pw.RequirePlan("pro", subject)).Get("/report", handler)
// Anywhere:
if pw.HasAccess(userID, "pro") { /* ... */ }
Adding a gateway
Drop a new file in plugins/cashier/gateways/ implementing contracts.PaymentGateway (Name, CreateCharge, VerifyPayment, VerifyWebhook) and register it in the manager. PhonePe, Cashfree, Paytm, and PayPal slot in the same way.