MisarMisar Docs
MisarMailMisar.BlogMisarReachMisarPostMisar.DevMisarSEOMisar PlatformMisar SSO
Wallet

Create Top-up Session

POST /io/wallet/topup-session — create a Stripe Checkout session to buy wallet credits. 1 credit = $1.

Creates a Stripe Checkout session for a whole-dollar credit top-up and returns the hosted checkout URL. Redirect the user to that URL to pay. Credits are granted when Stripe confirms the payment via the webhook — not when this endpoint returns.

POST https://api.misar.io/io/wallet/topup-session

Service key only

This endpoint requires x-wallet-service-key. Your backend creates the session; the browser never holds the service key.

Authentication

x-wallet-service-key: <WALLET_SERVICE_KEY>
Content-Type: application/json

Body parameters

user_idstringbodyrequired

The Misar SSO user id the credits will be granted to once payment completes.

amountDollarsintegerbodyrequired

Whole-dollar top-up amount. Must be an integer in the range 10–100000 ($10 minimum, $100,000 maximum per transaction). Because 1 credit = $1, this is also the number of credits the user receives.

productstringbodyrequired

The originating product slug (e.g. blog, mail, reach). Recorded on the session for attribution.

returnUrlstringbodyrequired

The URL Stripe returns the user to after checkout (success or cancel).

Request

curl -X POST "https://api.misar.io/io/wallet/topup-session" \
  -H "x-wallet-service-key: $WALLET_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "usr_123",
    "amountDollars": 25,
    "product": "blog",
    "returnUrl": "https://www.misar.blog/dashboard/billing"
  }'

Response

{
  "url": "https://checkout.stripe.com/c/pay/cs_live_...",
  "baseCredits": 250,
  "bonusCredits": 15,
  "totalCredits": 265,
  "bonusPercent": 6,
  "tierLabel": "6% bonus credits"
}
urlstring

The Stripe-hosted Checkout URL. Redirect the user here to complete payment.

baseCreditsnumber

Base credits = amountDollars (1 credit = $1).

bonusCreditsnumber

Extra credits from the applicable volume-discount tier, rounded to 2 decimals.

totalCreditsnumber

baseCredits + bonusCredits. A preview only — the actual grant is floored to a whole credit at payment time.

bonusPercentnumber

The bonus percentage applied.

tierLabelstring

Display label of the applied tier, e.g. "6% bonus credits".

Credit preview is informational

The *Credits fields let your UI show "you'll get N credits" before checkout. They do not determine the grant — the webhook recomputes credits from the Stripe-confirmed amount and never trusts client- or session-supplied values.

Billing details, tax, and invoices

Every checkout session collects the buyer's compliance details and produces a receipt:

  • Billing address — always required at checkout (billing_address_collection: required). The billing country is recorded on the resulting ledger entry.
  • Tax ID collection — business buyers can attach their VAT/GST/tax ID at checkout; it appears on the generated invoice.
  • Stripe invoice — when the tax flag is set, an invoice is generated for the top-up (invoice_creation). Its hosted receipt URL is stored on the ledger entry and exposed as receipt_url on List Transactions.
  • Automatic tax — when the wallet service runs with WALLET_STRIPE_TAX_ENABLED=1, Stripe Tax computes and charges tax on top of the credit price (tax_behavior: exclusive). Tax never changes the credits granted — the webhook derives credits from amount_total − amount_tax.

Automatic tax and invoicing are flag-gated

automatic_tax and invoice_creation are only sent to Stripe when the WALLET_STRIPE_TAX_ENABLED=1 environment flag is set, because Stripe Tax must first be activated in the Stripe dashboard (origin address and tax registrations configured) and invoice generation carries a per-invoice fee. Billing-address collection and tax-ID collection are always on regardless of the flag; with the flag off there is no invoice and the ledger's invoice columns stay NULL.

Validation

amountDollars must be an integer within [10, 100000]. Non-integer or out-of-range values are rejected with 400 before any Stripe session is created:

  • "amount_dollars must be an integer"
  • "Minimum top-up is $10"
  • "Maximum top-up per transaction is $100,000"

Rate limit

50 write ops/user/60 s. Exceeding the limit returns 429 with a Retry-After header.

Credits are granted on payment, not on session creation

A successful response only means a checkout session was created. The user's balance increases only after Stripe fires checkout.session.completed to the wallet webhook, which credits floor(amount_total / 100 + bonus) — the base credits plus the applicable volume-discount bonus.