Configure your payment account
Being rolled out. The interface described here is the one of the next release: it may still change, and may not be enabled on your tenant yet.
What it is for
The BillingAccount manifest describes your payment account: the name your customers see,
which Stripe or PayPal accounts the money goes to, what appears on your invoices, and which
application may call the payment API. There is only one per tenant, named default.
Prerequisites
- A session on the tenant (
sng tenant use acme) and the right to apply manifests (policyreadwriteoradmin). - A Stripe account and/or a PayPal Business account in your name.
- The tenant directory initialised (
sng iam init), to declare the application that will call the API. See Machine identities.
1. Store your Stripe and PayPal keys in secrets
Your keys never go in the manifest. Store them in secrets of your tenant, like your other secrets (see Create and read a secret), with these key names:
| Provider | Secret keys | Where to find them |
|---|---|---|
| Stripe | secretKey, publishableKey | Stripe dashboard, Developers → API keys |
| PayPal | clientId, clientSecret | PayPal developer dashboard, Apps & Credentials |
# Stripe: paste the keys when the terminal asks (nothing stays in the shell history)
read -rs STRIPE_SECRET_KEY && read -rs STRIPE_PUBLISHABLE_KEY
sng secret set stripe-keys \
--data secretKey="$STRIPE_SECRET_KEY" \
--data publishableKey="$STRIPE_PUBLISHABLE_KEY"
# PayPal
read -rs PAYPAL_CLIENT_ID && read -rs PAYPAL_CLIENT_SECRET
sng secret set paypal-keys \
--data clientId="$PAYPAL_CLIENT_ID" \
--data clientSecret="$PAYPAL_CLIENT_SECRET"
Start with your Stripe test keys (sk_test_…) and the PayPal sandbox, with mode: test
in the manifest; switch to live once the flow is validated.
2. Declare the application that will call the API
The payment API accepts an application token of your organisation. Declare a machine
application (client_credentials flow):
apiVersion: iam.stackngo.io/v1alpha1
kind: IAMApplication
metadata:
name: shop-backend
spec:
displayName: Shop (server)
grantTypes: [client_credentials]
token:
expireInHours: 1
Its credentials (clientId, clientSecret) are made available to your Apps under the name
shop-backend-oidc; see Authentication for your applications.
3. Write the BillingAccount
apiVersion: billing.stackngo.io/v1alpha1
kind: BillingAccount
metadata:
name: default # one account per tenant, always "default"
spec:
displayName: Acme
slug: acme # https://pay.dev.stackngo.fr/acme/…
defaultCurrency: EUR
locale: fr-FR
identity:
applications:
- name: shop-backend # an IAMApplication of your space
kind: service # client_credentials token
scopes: [carts, charges, subscriptions, refunds, invoices:read]
providers:
- type: stripe
mode: test # test or live
default: true
secretRef: { name: stripe-keys }
webhook: { autoRegister: true }
- type: paypal
mode: test
secretRef: { name: paypal-keys }
webhook: { autoRegister: true }
billingModes: [one_time, subscription, usage]
invoicing:
legalName: Acme SAS
legalForm: SAS
siren: "123456789"
siret: "12345678900012"
vatNumber: FR32123456789
rcs: RCS Paris B 123 456 789
shareCapital: "10 000 €"
address:
line1: 10 rue de la Paix
postalCode: "75002"
city: Paris
country: FR
companySize: pme # pme, eti or ge
numberPrefix: ACME # ACME-2026-000001
numberingReset: yearly # yearly or never
paymentTermsDays: 30
legalMentions: "TVA acquittée sur les débits."
latePenaltyText: "Pénalités de retard : 3 fois le taux d'intérêt légal."
recoveryFeeText: "Indemnité forfaitaire pour frais de recouvrement : 40 €."
facturxProfile: EN16931 # EN16931 or BASIC
exemptionReasons: # reason printed on the invoice, per VAT category
E: "Exonération de TVA, article 261-4-4° du CGI"
branding:
logoURL: https://www.acme.example/logo.svg
primaryColor: "#1F4FD8"
accentColor: "#F2A900"
fontFamily: Inter
carts:
defaultTTL: 24h
maxTTL: 168h
maxLines: 100
allowedReturnURLs:
- https://shop.acme.example/
dunning:
retryDelays: [24h, 72h, 168h] # retries after a failed payment
finalAction: unpaid # unpaid or cancel
sng apply billing/shop-backend.yml billing/account.yml
The fields
| Field | Role | Notes |
|---|---|---|
displayName | name shown on the payment page, the portal and invoices | |
slug | your payment page address: pay.dev.stackngo.fr/<slug> | 2 to 63 characters, a-z, 0-9, -; unique on the platform |
defaultCurrency, locale | default currency and language | EUR, fr-FR |
identity.applications[] | applications allowed to call the API | name: an IAMApplication of the tenant; kind: service; scopes (below) |
providers[] | your Stripe and PayPal accounts | type (stripe, paypal), mode (test, live), default, secretRef.name |
providers[].webhook.autoRegister | the platform itself registers, with your keys, the notification address at Stripe or PayPal | recommended: true |
billingModes | allowed modes among one_time, subscription, usage | a cart in another mode is rejected (403) |
invoicing | the issuer of your invoices: legal name, legal form, SIREN, SIRET, VAT number, RCS, share capital, address, company size, numbering, payment terms, mentions, late penalties and recovery fee, Factur-X profile, VAT exemption reasons | frozen into each invoice when it is issued |
branding | logo, colours (#RRGGBB), font | payment page and portal |
carts.defaultTTL, carts.maxTTL | lifetime of an unpaid cart | 24 h by default; a cart cannot ask for more than maxTTL |
carts.maxLines | maximum number of lines per cart | |
carts.allowedReturnURLs | accepted return addresses (success_url, cancel_url, return_url) | |
dunning.retryDelays | delays of the new attempts after a failed payment | for example [24h, 72h, 168h] |
dunning.finalAction | what happens after the last retry | unpaid (the subscription stays, unpaid) or cancel (it is cancelled) |
Scopes of an application:
| Scope | Allows |
|---|---|
carts | create, read and delete carts |
charges | register a customer, send them a payment method setup link, charge them |
subscriptions | read, change and cancel subscriptions |
refunds | refund |
invoices:read | read customers, payments, invoices, reports; create a customer portal link |
4. Get a token and check the account
TOKEN=$(curl -s -X POST https://auth.dev.stackngo.fr/oauth/token \
-d grant_type=client_credentials -d client_id="$CLIENT_ID" -d client_secret="$CLIENT_SECRET" \
| jq -r .access_token)
curl -s https://billing-api.dev.stackngo.fr/v1/account -H "Authorization: Bearer $TOKEN"
This token is used as is with the payment API (no tenant session needed).
What you get
{
"slug": "acme",
"display_name": "Acme",
"default_currency": "EUR",
"billing_modes": ["one_time", "subscription", "usage"],
"providers": [
{ "type": "stripe", "mode": "test", "default": true, "loaded": true },
{ "type": "paypal", "mode": "test", "default": false, "loaded": true }
],
"carts": { "default_ttl_seconds": 86400, "max_ttl_seconds": 604800, "max_lines": 100 },
"invoicing": { "legal_name": "Acme SAS", "number_prefix": "ACME", "facturx_profile": "EN16931" },
"ready": true
}
(excerpt) ready: true: the account is ready to collect payments. loaded: true: the
provider keys have been read and checked with Stripe or PayPal.
What you can expect
- The account is ready a few moments after
sng applyif the keys are valid. - Your keys are checked with the provider before being used: a revoked or wrong key keeps the account from being ready, without breaking payments already made.
- Changing your keys: update the secret (
sng secret set stripe-keys …); the new key is picked up within minutes, without re-applying the manifest. - Changing
invoicingnever changes an invoice already issued. - Deleting the
BillingAccountdisables payment collection; your invoices are kept and readable for 10 years.
If it does not work
| What you see | Cause | Fix |
|---|---|---|
sng apply rejects a second BillingAccount | one account per tenant | keep metadata.name: default and edit it |
ready: false, loaded: false on a provider | secret missing, key missing (secretKey…), key rejected by the provider, or test key with mode: live | check the secret with sng secret describe stripe-keys (the expected keys must be listed) and the mode |
403 on every call | the token's application is not declared in identity.applications, or the account is not ready | add it and re-apply; wait for ready: true |
403 on a single route | missing scope (for example refunds) | add the scope to the application |
sng apply rejects the slug | already taken, or characters not allowed | pick another one |
See also Payments troubleshooting.