Connect payment providers
Turn on payments with Stripe, Braintree, the App Store or Google Play, and how purchases are attributed to your users.
An app connects one provider account or store listing. Create it with apps create; the response carries webhook_url and webhook_instructions for the one step that may need doing by hand. Credentials are encrypted at rest.
| Provider | --provider |
Needs | Attribution to a user |
|---|---|---|---|
| Stripe | stripe |
secret_key (sk_… or restricted rk_…; webhook_secret is filled in when the endpoint is registered automatically) |
checkout create, or metadata.app_user_id / client_reference_id |
| Braintree | braintree |
merchant_id, public_key, private_key, environment |
receipts submit with the subscription id, or customer_id_is_app_user_id |
| App Store | app_store |
bundle_id (+ app_apple_id for production) |
receipts submit, or appAccountToken |
| Google Play | play_store |
service_account_json, package_name |
receipts submit, or obfuscatedAccountId |
Stripe
npx -y @salesdogg/cli apps create --provider stripe --credentials.secret_key "$STRIPE_SECRET_KEY"
npx -y @salesdogg/cli apps import-products <app_id>
When the API is reachable over public HTTPS, SalesDogg registers its webhook endpoint in your Stripe account itself. Renewals and trial conversions are recorded when the invoice is paid (invoice.paid), not when Stripe rolls the period over, so a declined renewal is a billing_issue and never a renewal. Refunds of subscription charges are recorded as refund events with the amount; Stripe keeps the subscription running, so access is unchanged.
Sell with a hosted checkout attributed to your user:
npx -y @salesdogg/cli checkout create --app-user-id user_1 --product-id <product_id> --success-url https://example.com/ok
Developing against a local API? Forward Stripe's events with the Stripe CLI; the exact stripe listen command is in the app's webhook_instructions.
Braintree
npx -y @salesdogg/cli apps create --provider braintree --credentials.merchant_id ... --credentials.public_key ... --credentials.private_key ... --credentials.environment sandbox
environment is sandbox (default) or production. Cancelled subscriptions expire at the paid-through date through a background sweep.
App Store
npx -y @salesdogg/cli apps create --provider app_store --config.bundle_id com.example.app
Point App Store Server Notifications V2 at the app's webhook_url. Notifications are verified against Apple's root certificates. Register products with their App Store product ids. To look purchases up at Apple (needed by the RevenueCat import), add an In-App Purchase key: issuer_id, key_id and private_key.
Google Play
npx -y @salesdogg/cli apps create --provider play_store --config.package_name com.example.app --credentials.service_account_json "$(cat service-account.json)"
Real-time developer notifications arrive through a Pub/Sub push subscription to the app's webhook_url. Purchases are acknowledged automatically. Register subscription products as productId:basePlanId (for example pro:monthly) and one-time products as productId.
Going live
The Free plan is sandbox only: Stripe test mode, the Braintree sandbox, App Store sandbox and TestFlight purchases, Google Play license testers and the simulator. Connecting live credentials needs a paid plan:
- a live Stripe key (
sk_live_…orrk_live_…), - a Braintree account with
environment: production, - an App Store app with
app_apple_id, which is what lets Apple's production notifications in.
On Free, those requests answer 402 with type: payment_required, code: plan_required, and a link in error.details.upgrade_url:
{
"error": {
"type": "payment_required",
"code": "plan_required",
"message": "Live Stripe keys need a paid plan. This organization is on Free, which is sandbox only.",
"hint": "Show the user this link to choose a plan and pay (no sign-in needed): https://… . When they have paid, run the same request again. Test keys (sk_test_...) work on Free.",
"details": { "upgrade_url": "https://…/upgrade/…", "plan": "free", "plans": [ … ] }
}
}
The link opens a page where a person chooses Starter or Pro, monthly or annually, and pays with Stripe Checkout. It needs no sign-in, it can only be used to pay for that organization, and it is valid for 7 days. Agents should show it to the user, wait until they have paid, then repeat the request. npx -y @salesdogg/cli billing get returns the current plan and a fresh link at any time.
Google Play cannot be told apart at setup: a live Play purchase on Free is recorded as usual (your customer keeps access) and billing get shows live_data_since, the moment real payments started arriving.
Receipts from your app
Mobile SDKs report purchases with POST /v1/receipts using the public key; the server verifies them with the store and returns the user's entitlements. When a receipt that belongs to one signed-in user is submitted for another, the project's restore behavior decides: transfer (default) moves it and records a transfer event; keep_with_original refuses with 409 receipt_already_in_use. Change it with npx -y @salesdogg/cli settings update --restore-behavior keep_with_original.
Adding a provider
Implement ProviderAdapter in apps/api/src/providers/types.ts: verify the provider's notifications and translate them into state snapshots. Event detection, attribution, idempotency and entitlements are shared.