A billing control layer for a product portfolio

A SaaS billing platform built around one trustworthy order model.

Connect multiple products to a shared Stripe billing contract without letting the browser choose prices, users, merchants, or return destinations.

Catalog before checkout

A SaaS billing platform keeps price identity on the server.

Every billable feature begins as an app and SKU in the Yito Pay catalog. The record identifies an immutable Stripe Price, currency, amount in minor units, payment mode, and entitlement key. Product browsers never send a trusted dollar amount or Stripe Price ID.

When a backend requests checkout, its app API key selects the app. The SKU selects the catalog entry. The authenticated product user becomes the external user ID, and the product’s own order becomes the external order ID. Yito Pay rejects an idempotency key if a retry changes any of those values.

This design gives finance and engineering one answer to a basic question: which approved product and price produced this charge? Stripe metadata aids investigation, while the Yito Pay order remains the application ledger for fulfillment.

01

One-time sales

Create payment-mode Checkout Sessions for credits, downloads, upgrades, and other single purchases.

02

Subscriptions

Track Stripe subscription status and invoice outcomes without reducing the lifecycle to a fragile paid boolean.

03

Customer portal

Create short-lived Stripe portal sessions so customers can manage payment methods, invoices, and cancellation safely.

Explicit state

Billing is a state machine, not a success flag.

A one-time order can be created, open, processing, paid, fulfilled, failed, expired, partially refunded, refunded, or disputed. A subscription can be trialing, active, past due, paused, canceled, or unpaid. Preserving these states prevents a temporary payment delay from looking like permanent success or failure.

Delayed payment methods may complete Checkout before money is available. Yito Pay keeps the order processing until the asynchronous success event arrives. Subscription access follows paid invoices and current subscription state. Refund and dispute events can revoke or restore an entitlement without editing an append-only payment fact.

SignalOrder actionProduct effect
checkout.session.completed + paidMark paidGrant mapped entitlement
async_payment_succeededMark paidGrant after delayed settlement
invoice.payment_failedKeep order historyMark subscription access past due
refund succeedsPartial or full refundRevoke on full refund
dispute createdMark disputedPause access pending outcome

Product isolation

Share a platform without sharing customer assumptions.

Each app has its own server key, allowed payment origins, return paths, callback URL, customer namespace, and catalog. A key for one product cannot read another product’s orders or request a refund for them. App authentication happens before order lookup, and public recovery links use a separate short-lived token.

Customers are currently scoped by app, which is the safer default for distinct products. A person who buys from two products can therefore have two Stripe Customer records even if the products share a merchant account. This avoids accidentally exposing one brand’s invoices or subscriptions in another brand’s portal.

Product callbacks use a per-app signing secret derived from a Worker master key. The receiving backend verifies the timestamp, raw body, and event ID, then stores the event ID before granting access. Retries deliver the same semantic event rather than issuing access twice.

App-scopedkeys, customers, orders, prices
Server-onlyrefund and portal requests
Signedentitlement callbacks with event IDs

Merchant responsibility

Accounts follow the real seller.

Products can share a Stripe account only when the legal seller, statement identity, invoice issuer, refund owner, dispute owner, and tax responsibility are aligned. Separate legal entities require separate accounts. Third-party sellers require a Stripe Connect design.

Yito Pay’s shared API does not hide those responsibilities. It makes the routing change an infrastructure concern instead of a rewrite in every product.

Start with the smallest correct boundary

One account first, more routing only when the business requires it.

The first production boundary uses one Stripe account, one Worker, one Neon schema, one webhook path, and multiple approved apps. That is enough to make checkout and fulfillment consistent while keeping operations understandable.

Additional Stripe accounts are introduced only for a verified business reason. Stripe Connect is introduced only when external sellers receive funds. The architecture leaves room for those paths, but the first implementation does not pretend to be a marketplace or payment orchestrator before that need exists.

Questions, answered

Frequently asked questions

Does every SaaS product need its own Stripe account?

No. Products can share an account when the same legal seller, billing identity, support team, refund owner, dispute owner, and tax responsibility apply. Separate business responsibility should use a separate Stripe account or Stripe Connect.

Where do product prices come from?

Prices come from the Yito Pay server-side catalog. Each app and SKU maps to an approved Stripe Price ID, amount, currency, mode, and entitlement. The browser cannot override those values.

How are subscriptions managed?

Yito Pay records Checkout, invoice, and subscription events, synchronizes subscription state, and exposes Stripe Customer Portal sessions for supported customer self-service actions.

Build on a reliable payment boundary

Give every SaaS product one secure way to charge.

Start with the integration guide, then register the app, domain, and server credentials before enabling checkout.