Skip to main content

Configure your payment account

Beta

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 (policy readwrite or admin).
  • 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:

ProviderSecret keysWhere to find them
StripesecretKey, publishableKeyStripe dashboard, Developers → API keys
PayPalclientId, clientSecretPayPal 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):

billing/shop-backend.yml
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​

billing/account.yml
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​

FieldRoleNotes
displayNamename shown on the payment page, the portal and invoices
slugyour payment page address: pay.dev.stackngo.fr/<slug>2 to 63 characters, a-z, 0-9, -; unique on the platform
defaultCurrency, localedefault currency and languageEUR, fr-FR
identity.applications[]applications allowed to call the APIname: an IAMApplication of the tenant; kind: service; scopes (below)
providers[]your Stripe and PayPal accountstype (stripe, paypal), mode (test, live), default, secretRef.name
providers[].webhook.autoRegisterthe platform itself registers, with your keys, the notification address at Stripe or PayPalrecommended: true
billingModesallowed modes among one_time, subscription, usagea cart in another mode is rejected (403)
invoicingthe 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 reasonsfrozen into each invoice when it is issued
brandinglogo, colours (#RRGGBB), fontpayment page and portal
carts.defaultTTL, carts.maxTTLlifetime of an unpaid cart24 h by default; a cart cannot ask for more than maxTTL
carts.maxLinesmaximum number of lines per cart
carts.allowedReturnURLsaccepted return addresses (success_url, cancel_url, return_url)
dunning.retryDelaysdelays of the new attempts after a failed paymentfor example [24h, 72h, 168h]
dunning.finalActionwhat happens after the last retryunpaid (the subscription stays, unpaid) or cancel (it is cancelled)

Scopes of an application:

ScopeAllows
cartscreate, read and delete carts
chargesregister a customer, send them a payment method setup link, charge them
subscriptionsread, change and cancel subscriptions
refundsrefund
invoices:readread 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 apply if 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 invoicing never changes an invoice already issued.
  • Deleting the BillingAccount disables payment collection; your invoices are kept and readable for 10 years.

If it does not work​

What you seeCauseFix
sng apply rejects a second BillingAccountone account per tenantkeep metadata.name: default and edit it
ready: false, loaded: false on a providersecret missing, key missing (secretKey…), key rejected by the provider, or test key with mode: livecheck the secret with sng secret describe stripe-keys (the expected keys must be listed) and the mode
403 on every callthe token's application is not declared in identity.applications, or the account is not readyadd it and re-apply; wait for ready: true
403 on a single routemissing scope (for example refunds)add the scope to the application
sng apply rejects the slugalready taken, or characters not allowedpick another one

See also Payments troubleshooting.

See also​