← All documentation

Payments

This module is deliberately thin, and thinner than you probably expect. It does three things:

  1. Makes the team the customer that gets billed
  2. Works out where a receipt should go
  3. Gives you a panel to choose a payment company and enter its keys

Everything else about billing is the Pay gem, and Pay documents it. Your application talks to Pay directly. There is no wrapper here to learn.


The team is the customer

Somebody can belong to several teams and can leave, so a subscription attached to a person walks out of the door with them — and a team’s access would depend on which of its members happened to have paid.

So billing is wired to the organization automatically, by the module itself:

organization.payment_processor              # Pay's customer object
organization.payment_processor.subscribed?  # Pay's API, documented by Pay
organization.payment_processor.subscription
organization.billing_email                  # where a receipt goes
organization.email                          # the same address, under the name Pay reads
organization.sync_billing_details           # push the name and address to the processor

Do not wire this up yourself, and never wire it to an account. Making the wrong version impossible is the point, rather than a convention somebody has to remember.


Where a receipt goes

billing_email is the address of the person who created the team, and it moves only when ownership does.

Precisely: the earliest membership whose role is "owner", falling back to the earliest membership of any role. Adding a second owner does not move the receipts, and nor does anybody joining later. If the founder’s membership goes, the next-earliest owner takes it over. The fallback is what leaves a team mid-handover somewhere to send a failed-payment notice — the moment it most needs one.

Note the coupling: “owner” here means a membership whose role is the literal string "owner". This is the one place in SparrowKit where a role’s value means anything. If your application names its top role something else, billing_email falls through to the earliest member — which is still the person who set the team up. See Teams and tenancy.

organization.email is the same address under the name Pay reads for it. Both are defined for you; leave them alone.


Keeping the processor’s copy current

Your payment company holds its own copy of the team’s name and address, taken when the customer was created — and that copy, not the one in your database, is where a receipt is actually sent.

It is kept up to date for you. A seat changing hands, the billing account correcting its own address, and a team being renamed each push the change across.

Only for a team that already has a customer at your payment company. That guard matters more than it sounds: the underlying update creates a customer when there is none, so syncing without checking would open an account at your payment company for every team on the system, the first time anybody was seated in one.

If your application decides who pays by a rule of its own, say so after making the change:

organization.sync_billing_details

The work happens on a background job, on whatever queue you have configured. If it fails — your payment company is down, or the queue drops it — the processor keeps the address it had, and nothing retries beyond your queue’s own policy.


Choosing a processor

The dropdown is whatever Pay is willing to run — Stripe, Paddle, Braintree, Lemon Squeezy, and any other Pay supports. Nothing in SparrowKit names a payment company, so one Pay adds later appears here without a release from us.

Until you choose one, no team can be given a customer record. Pay allows several at once and will not pick one for you.

Changing the processor reloads the page, because each one asks for different keys. Nothing is saved until you press Save.


Keys

The panel asks for exactly the credentials Pay reads for the processor you chose, worked out from Pay itself rather than from a list we maintain. Required fields come first; the ones you can skip are marked Optional.

They are written to the processor’s own top-level key in your credentials — stripe:, paddle_billing: and so on — because that is where Pay looks them up. Putting them anywhere else would produce a saved key that does nothing.

A few names worth explaining:

Private key. The secret half. It never leaves your server.

Public key. The one that is safe in a browser. It goes in the page that collects a card.

Signing secret. From the webhook endpoint you created at your payment company, not from its API keys page. It is what proves an incoming message came from them rather than from anybody who found the URL.

Context (optional). Only for a key issued to a business account that acts on behalf of several others. An ordinary account key ignores it entirely.

Receive test events (optional). Off means this application acts only on real events and ignores anything from your payment company’s test mode. That is what you want in production, where a test event should never move a subscription. On while you are wiring it up.


Who may see and change billing

Nothing to set. It is the same question as everything else: billing belongs to a team, and who may read or change it is decided by the role somebody holds in that team — in your own controller, where you can read and test it.

before_action :require_billing_manager

def require_billing_manager
  head :forbidden unless current_account&.role_in(current_organization) == "owner"
end

Reading which plan you are on and changing the card do not have to be the same rule, and SparrowKit has no view on whether they are.


What is not here

This is the section worth reading twice, because it is where expectations usually sit.

There is no plan catalogue, no billing status stored anywhere, no checkout flow, no billing screens, and nothing that sends a billing notification.

An earlier version of SparrowKit copied the plan and status into columns and kept them current from webhooks. It was removed, because a cached second answer can be wrong and Pay already knows the right one. Ask Pay.

Plans, prices and what they cost live at your payment company, where you already configured them. Restating them here would be two places to change and a guarantee they drift.

What a plan unlocks is your application’s policy, written the same way as any other rule about who may do what.

Webhooks are received and verified by Pay. There is nothing of ours to mount and nothing to configure beyond the signing secret above.


Development

Allow Pay’s test processor lets subscriptions be created with no payment company behind them, for tests and local work. Off unless you say otherwise, deliberately: an application that quietly fell back to it would take no money and say nothing about it.