Storeware Docs

Charging merchants

Plans, one time charges and charges by use, approved by the merchant and collected on their Storeware bill.

How it works

Storeware bills merchants for your app on its own monthly statement, charged to the card they already pay Storeware with. You never handle a card, and the merchant never leaves Storeware. Every charge is approved by a person in the shop before anything is owed.

  • Plans are charged in advance for each month or year, from the day they are approved, or from the end of a trial.
  • Moving to a plan that costs as much or more a day takes effect at once, with a credit for unused time on the old one. A cheaper plan starts when the paid period ends.
  • Uninstalling ends the plan at once, with a credit for unused time.
  • Prices are the ones you write for each currency. Storeware never converts them, and a merchant keeps the price they approved until they change plan.
  • A statement that is not paid leaves the plan past_due. After 14 days it is frozen, and 30 days after that it ends. Read the status and pause what you provide when it is frozen.

Declaring plans

storeware.app.toml
[billing]
mode = "managed"

[[billing.plans]]
key = "free"
name = "Free"

[[billing.plans]]
key = "pro"
name = "Pro"
trial_days = 14
features = ["Unlimited size guides"]
prices = { usd = 900, gbp = 800, eur = 900, inr = 75000 }

With mode = "managed", the merchant picks a plan on the install screen and can change it from your app's page in their admin. Choosing a plan there is their approval.

Asking for a charge

With mode = "api", or to sell something once, ask with your app's token. The answer carries a confirmationUrl: send the merchant there. They approve or decline and come back to your returnUrl, which must be https on your app's own origin.

POST /api/v1/app-billing/subscriptions
{ "plan": "pro", "returnUrl": "https://yourapp.example/billing/done" }

POST /api/v1/app-billing/purchases
{
  "name": "Import of 10,000 products",
  "prices": { "usd": 4900, "gbp": 3900, "eur": 4500, "inr": 399000 },
  "returnUrl": "https://yourapp.example/billing/done"
}

GET /api/v1/app-billing
POST /api/v1/app-billing/subscriptions/{id}/cancel

With the SDK, the same calls are on the client for a shop:

const client = await app.clientFor(storeId);
const asked = await client.billing.requestSubscription({
  plan: 'pro',
  returnUrl: 'https://yourapp.example/billing/done',
});
// Send the merchant to asked.confirmationUrl.
  • A charge nobody answers expires after 48 hours. A shop may have five waiting at once.
  • Send idempotencyKey to make a request safe to repeat.
  • Asking for the plan the shop is already on returns it, with no confirmation needed.
  • Cancelling ends the plan when its paid period ends.

Charging by use

Give a plan usage with a cap and your terms, and report what each shop uses on it. Usage is billed at the end of each period, beside the next period of the plan, and never comes to more than the cap. The merchant sees the terms and the cap when they approve the plan, and can set a lower limit later.

storeware.app.toml
[[billing.plans]]
key = "labels"
name = "Labels"
prices = { usd = 500, gbp = 400, eur = 500, inr = 40000 }
usage = { cap = { usd = 5000, gbp = 4000, eur = 5000, inr = 400000 }, terms = "2 cents a label" }
POST /api/v1/app-billing/usage
{ "amountMinor": 2, "description": "Label for order 1042", "idempotencyKey": "label-1042" }
  • Amounts are in the plan's currency, which GET /api/v1/app-billing tells you, with the cap and what is used so far.
  • A record that would pass the cap is refused with 402 and app_usage_cap_reached.
  • app_usage.cap_approaching.v1 comes at four fifths of the cap and app_usage.cap_reached.v1 at the cap.

Credits

Credit a shop for a problem or a goodwill gesture and it comes off their next statement, and out of your earnings when that is paid. A credit can come to no more than the shop paid your app in the last year, less what it has already been credited.

POST /api/v1/app-billing/credits
{ "amountMinor": 500, "description": "Sorry for the outage on 3 March" }

From your own systems, use your developer token: POST /api/v1/developer/apps/{slug}/credits with the storeId.

Changing a price

  • A lower price in a new release reaches existing subscribers at their next renewal, on its own.
  • A higher price never does: subscribers keep what they approved. Ask them to approve the plan at its new price with POST /api/v1/app-billing/subscriptions, and it takes effect when they say yes, with a credit for the unused part of the old one.

Hearing about it

app_subscription.created.v1, app_subscription.updated.v1, app_subscription.canceled.v1, app_subscription.declined.v1, app_purchase.accepted.v1, app_purchase.declined.v1 and the two app_usage topics go to your webhook URL whether or not you subscribe, and only to you. With the SDK, handle them with router.onBilling.

Test charges

A test charge runs the whole lifecycle, approval, statement and payment, and no money moves. Charges are tests on your own shops and development stores, for a draft app, when you send "test": true, while your payout account is not ready, and for every app until Storeware opens paid billing to it.

On this page