Ramadan Last 10 Nights Campaign

Used by thousands of students worldwide - help us reach 150 members

... / 150 members...
APIBilling & Subscriptions

Billing & Subscriptions

/stripe

Stripe-backed subscription management (checkout, cancel, reactivate, plan changes), payment methods, invoices, and OCR page-credit usage tracking. Every call acts on the Stripe customer attached to the authenticated user. Plans: $5/month (1,000 OCR pages per billing period), $10/month (2,000 pages) and $50/month (10,000 pages); pages beyond the plan allowance are billed as overage at $0.02 per page. Most errors in this group are returned as plain text, not JSON.

POST/stripe/create-checkout-session

Create a subscription checkout session

Auth required

Creates a Stripe Checkout session in `subscription` mode (card payments only, quantity 1) for the authenticated user's Stripe customer and returns the hosted checkout URL to redirect the user to. After checkout Stripe redirects to `<Origin>/profile` on success and `<Origin>/support` on cancel, where `<Origin>` is the value of the request's `Origin` header. The handler does not validate `priceId` against the known plans; it is passed straight to Stripe. Page credits are derived from the price ID: the $5/month plan grants 1,000 pages, $10/month grants 2,000 pages and $50/month grants 10,000 pages per billing period. A price ID that is not one of the configured plan prices grants 0 page credits.

Body parameters

priceIdrequired
string
Stripe Price ID of the plan to subscribe to. Live plan prices: `price_1PpFguId9CfEnWZpxaleTN3u` ($5/month, 1,000 pages), `price_1PpFh2Id9CfEnWZpVxMTmMJv` ($10/month, 2,000 pages), `price_1PpFh7Id9CfEnWZpB4p2Il7o` ($50/month, 10,000 pages).

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Stripe rejected the request, e.g. missing or unknown `priceId` (plain text: "Internal Server Error. Unable to complete request.")
Request
curl -X POST "https://app.ummahspot.com/stripe/create-checkout-session" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"priceId":"string"}'
Response · 200
{
  "id": "cs_test_a1B2c3D4e5F6g7H8i9J0",
  "url": "https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4e5F6g7H8i9J0"
}
GET/stripe/subscription

Get the current subscription

Auth required

Lists the Stripe subscriptions of the authenticated user's customer across all statuses (including canceled ones) and returns details of the first one Stripe returns. If the customer has never had a subscription the response is just `{ "active": false }`. `active` is true only when the subscription status is exactly `active`. `plan.amount` is in major currency units (Stripe's `unit_amount` divided by 100) and `plan.name` falls back to "Standard Plan" when the Stripe price has no nickname. Use `subscription_id` with the cancel, reactivate and update endpoints.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Stripe lookup failed (plain text: "Failed to retrieve subscription information")
Request
curl -X GET "https://app.ummahspot.com/stripe/subscription" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "active": true,
  "status": "active",
  "current_period_end": "2026-10-18T14:03:22.000Z",
  "cancel_at_period_end": false,
  "subscription_id": "sub_1234567890abcdef",
  "plan": {
    "id": "price_1PpFguId9CfEnWZpxaleTN3u",
    "name": "Standard Plan",
    "amount": 5,
    "currency": "usd",
    "interval": "month"
  }
}
POST/stripe/subscription/cancel

Cancel a subscription at period end

Auth required

Sets `cancel_at_period_end: true` on the subscription. The subscription is not terminated immediately: it stays usable, and the user keeps their remaining OCR page credits, until `current_period_end`. The handler verifies that the subscription belongs to the authenticated user's Stripe customer before changing it. Can be undone with the reactivate endpoint while the period is still running.

Body parameters

subscriptionIdrequired
string
Stripe subscription ID, as returned in `subscription_id` by `GET /stripe/subscription`.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Invalid token, or the subscription belongs to a different Stripe customer (plain text: "Unauthorized to cancel this subscription")
  • 500Missing or unknown `subscriptionId`, or another Stripe error (plain text: "Failed to cancel subscription")
Request
curl -X POST "https://app.ummahspot.com/stripe/subscription/cancel" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subscriptionId":"string"}'
Response · 200
{
  "success": true,
  "canceled": true,
  "current_period_end": "2026-10-18T14:03:22.000Z"
}
POST/stripe/subscription/reactivate

Reactivate a subscription pending cancellation

Auth required

Sets `cancel_at_period_end: false` on a subscription that was previously scheduled to cancel, so it renews normally. The handler verifies that the subscription belongs to the authenticated user's Stripe customer before changing it.

Body parameters

subscriptionIdrequired
string
Stripe subscription ID, as returned in `subscription_id` by `GET /stripe/subscription`.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Invalid token, or the subscription belongs to a different Stripe customer (plain text: "Unauthorized to reactivate this subscription")
  • 500Missing or unknown `subscriptionId`, or another Stripe error (plain text: "Failed to reactivate subscription")
Request
curl -X POST "https://app.ummahspot.com/stripe/subscription/reactivate" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subscriptionId":"string"}'
Response · 200
{
  "success": true,
  "active": true,
  "current_period_end": "2026-10-18T14:03:22.000Z"
}
POST/stripe/subscription/update

Change the subscription plan

Auth required

Swaps the price on the subscription's first item to `newPriceId` (upgrade or downgrade). The handler verifies that the subscription belongs to the authenticated user's Stripe customer first; `newPriceId` is not validated against the known plans. The page-credit allowance is not changed by this call itself: when Stripe notifies the backend of the subscription update, the allowance of the current billing period is set to the new plan's credits (1,000 / 2,000 / 10,000 pages) while pages already used in the period are kept.

Body parameters

subscriptionIdrequired
string
Stripe subscription ID, as returned in `subscription_id` by `GET /stripe/subscription`.
newPriceIdrequired
string
Stripe Price ID of the plan to switch to (see `POST /stripe/create-checkout-session` for the plan price IDs).

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Invalid token, or the subscription belongs to a different Stripe customer (plain text: "Unauthorized to update this subscription")
  • 500Missing or unknown `subscriptionId` / `newPriceId`, or another Stripe error (plain text: "Failed to update subscription")
Request
curl -X POST "https://app.ummahspot.com/stripe/subscription/update" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subscriptionId":"string","newPriceId":"string"}'
Response · 200
{
  "success": true,
  "updated": true,
  "subscription_id": "sub_1234567890abcdef"
}
GET/stripe/payment-methods

List saved cards

Auth required

Lists the card payment methods attached to the authenticated user's Stripe customer. `is_default` is derived from the payment method's Stripe metadata (`metadata.is_default === "true"`), not from the customer's default payment method setting, and `POST /stripe/payment-methods/set-default` does not write that metadata.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Stripe lookup failed (plain text: "Failed to retrieve payment methods")
Request
curl -X GET "https://app.ummahspot.com/stripe/payment-methods" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "payment_methods": [
    {
      "id": "pm_1234567890abcdef",
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2028,
      "is_default": false
    }
  ]
}
POST/stripe/payment-methods/create-setup-intent

Create a SetupIntent for adding a card

Auth required

Creates a Stripe SetupIntent (card only) for the authenticated user's Stripe customer and returns its client secret. Confirm it client-side with Stripe.js / the Stripe mobile SDK to attach a new card to the customer. Takes no request body.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Stripe error (plain text: "Failed to create setup intent")
Request
curl -X POST "https://app.ummahspot.com/stripe/payment-methods/create-setup-intent" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "clientSecret": "seti_1234567890abcdef_secret_AbCdEfGhIjKlMnOp"
}
GET/stripe/subscriptions/count

Count active subscriptions platform-wide

Public

Returns the total number of subscriptions with status `active` in the Stripe account. This is a platform-wide figure, not specific to any user, and the route is intentionally public because the site-wide campaign banner shows it to every visitor. The count is cached on the server for 5 minutes, so the value can be up to 5 minutes old; requests arriving while the cache is being refreshed share a single refresh. If the refresh from Stripe fails and a previously cached value exists, that cached value is returned instead of an error.

Errors

  • 500Stripe lookup failed and no cached count is available yet (plain text: "Failed to retrieve subscription count")
Request
curl -X GET "https://app.ummahspot.com/stripe/subscriptions/count"
Response · 200
{
  "success": true,
  "active_subscriptions": 128
}
POST/stripe/payment-methods/set-default

Set the default payment method

Auth required

Sets the customer's `invoice_settings.default_payment_method` in Stripe, which is the card used for subscription renewals and overage invoices. The handler verifies that the payment method is attached to the authenticated user's Stripe customer first.

Body parameters

paymentMethodIdrequired
string
Stripe payment method ID (`pm_...`), as returned by `GET /stripe/payment-methods`.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Invalid token, or the payment method belongs to a different Stripe customer (plain text: "Unauthorized to update this payment method")
  • 500Missing or unknown `paymentMethodId`, or another Stripe error (plain text: "Failed to set default payment method")
Request
curl -X POST "https://app.ummahspot.com/stripe/payment-methods/set-default" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"paymentMethodId":"string"}'
Response · 200
{
  "success": true
}
DELETE/stripe/payment-methods/:id

Remove a saved card

Auth required

Detaches the payment method from the authenticated user's Stripe customer. The handler verifies that the payment method is attached to that customer first.

Path parameters

id
string
Stripe payment method ID (`pm_...`)

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Invalid token, or the payment method belongs to a different Stripe customer (plain text: "Unauthorized to delete this payment method")
  • 500Unknown payment method ID or another Stripe error (plain text: "Failed to delete payment method")
Request
curl -X DELETE "https://app.ummahspot.com/stripe/payment-methods/:id" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true
}
GET/stripe/billing-history

List recent invoices

Auth required

Returns the 10 most recent Stripe invoices of the authenticated user's customer (fixed limit, no pagination). This includes subscription invoices as well as the separate invoices created for OCR overage charges. `amount_paid` is in major currency units (Stripe's amount divided by 100).

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Stripe lookup failed (plain text: "Failed to retrieve billing history")
Request
curl -X GET "https://app.ummahspot.com/stripe/billing-history" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "invoices": [
    {
      "id": "in_1234567890abcdef",
      "amount_paid": 5,
      "currency": "usd",
      "status": "paid",
      "created": "2026-09-18T14:03:22.000Z",
      "invoice_pdf": "https://pay.stripe.com/invoice/acct_123/test_abc/pdf",
      "period_start": "2026-08-18T14:03:22.000Z",
      "period_end": "2026-09-18T14:03:22.000Z"
    }
  ]
}
GET/stripe/usage

Get OCR page-credit usage for the current billing period

Auth required

Returns the page-credit status of the current billing period plus a summary of unresolved failed overage payments. `tierCredits` is the plan allowance (1,000 pages for $5/month, 2,000 for $10/month, 10,000 for $50/month; 0 for an unrecognised price), `remainingCredits` is `max(0, tierCredits - pagesUsed)` and `isInOverage` is true once `pagesUsed` exceeds `tierCredits`. Pages beyond the allowance are charged immediately at OCR time at 2 cents ($0.02) per page with a 2 cent minimum, on a separate Stripe invoice; `overageChargedCents` is the amount successfully collected so far in this period. Credits reset when a subscription renewal invoice is paid. A subscription that was canceled but is still inside its paid period still counts as a subscription. If there is no usage record for the period yet, one is created on the fly from the Stripe subscription. Users without a subscription get `hasSubscription: false` with zeroed counters and null dates. Users with the `editor` or `admin` role bypass billing entirely: they get `hasSubscription: true`, `isAdmin: true`, and unlimited credits (`tierCredits` and `remainingCredits` are serialised as `null` in JSON). As a side effect, previously failed overage payments whose Stripe invoice has since been paid are marked as successful.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Usage lookup failed, including when the user has no Stripe customer ID (plain text: "Failed to retrieve usage information")
Request
curl -X GET "https://app.ummahspot.com/stripe/usage" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "hasSubscription": true,
  "tierCredits": 1000,
  "pagesUsed": 1040,
  "remainingCredits": 0,
  "isInOverage": true,
  "overagePages": 40,
  "overageChargedCents": 80,
  "billingPeriodStart": "2026-09-18T14:03:22.000Z",
  "billingPeriodEnd": "2026-10-18T14:03:22.000Z",
  "priceId": "price_1PpFguId9CfEnWZpxaleTN3u",
  "hasFailedPayments": false,
  "failedPaymentCount": 0,
  "totalUnpaidCents": 0
}
POST/stripe/usage/estimate

Estimate the cost of processing a number of pages

Auth required

Dry-run calculation of how an OCR job of `pageCount` pages would be billed; nothing is charged or recorded. Pages are first covered by the remaining credits of the current period (`freePages`); the rest are `overagePages`, costed at 2 cents ($0.02) per page with a 2 cent minimum (`estimatedCostCents`). Without an active subscription the response is `{ "canProcess": false, "reason": "No active subscription", "estimatedCostCents": 0 }`. For users with the `editor` or `admin` role the response has `isAdmin: true`, all pages free, a cost of 0 and `remainingCreditsAfter` serialised as `null` (unlimited).

Body parameters

pageCountrequired
number
Number of pages to be processed. Must be at least 1.

Errors

  • 400`pageCount` missing, zero or less than 1 (JSON: `{ "error": "Invalid page count" }`)
  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Estimate failed (plain text: "Failed to estimate cost")
Request
curl -X POST "https://app.ummahspot.com/stripe/usage/estimate" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pageCount":0}'
Response · 200
{
  "canProcess": true,
  "pageCount": 150,
  "freePages": 100,
  "overagePages": 50,
  "estimatedCostCents": 100,
  "remainingCreditsAfter": 0
}
GET/stripe/usage/history

List OCR usage history

Auth required

Paginated log of the authenticated user's OCR jobs, newest first, across all billing periods. Each entry shows how many pages of the job were covered by plan credits (`freePages`) versus billed as overage (`overagePages`, `overageChargeCents`). `total` is the total number of log entries for the user. No maximum is enforced on `limit`.

Query parameters

limit
number
Number of log entries to return.Default: 10
offset
number
Number of log entries to skip.Default: 0

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Lookup failed (plain text: "Failed to retrieve usage history")
Request
curl -X GET "https://app.ummahspot.com/stripe/usage/history" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true,
  "total": 23,
  "logs": [
    {
      "jobId": "b7e1c9d2-4f3a-4e8b-9a6c-2d1f0e5a7c34",
      "pageCount": 150,
      "freePages": 100,
      "overagePages": 50,
      "overageChargeCents": 100,
      "createdAt": "2026-09-18T14:03:22.000Z"
    }
  ]
}
GET/stripe/usage/failed-payments

List unresolved failed overage payments

Auth required

Returns the overage charges of the current billing period whose payment failed and is still unpaid, newest first. Before responding, each failed entry with a Stripe invoice is re-checked against Stripe; entries whose invoice has since been paid are marked successful and dropped from the list. If the user has no active usage record (no subscription) the response is empty with zero totals. Use the `_id` of an entry with `POST /stripe/usage/retry-payment/:logId`.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Lookup failed (plain text: "Failed to retrieve failed payments")
Request
curl -X GET "https://app.ummahspot.com/stripe/usage/failed-payments" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true,
  "hasFailedPayments": true,
  "failedPaymentCount": 1,
  "totalUnpaidCents": 100,
  "failedLogs": [
    {
      "_id": "66eae1b2c3d4e5f6a7b8c9d0",
      "jobId": "b7e1c9d2-4f3a-4e8b-9a6c-2d1f0e5a7c34",
      "overagePages": 50,
      "overageChargeCents": 100,
      "stripeInvoiceId": "in_1234567890abcdef",
      "createdAt": "2026-09-18T14:03:22.000Z"
    }
  ]
}
POST/stripe/usage/retry-payment/:logId

Retry a failed overage payment

Auth required

Attempts to collect a failed OCR overage charge again. The handler verifies that the usage log belongs to the authenticated user. If the original Stripe invoice is still open it is paid; if it turns out to be paid already the log is simply marked successful; otherwise a new invoice for the same amount is created, finalised and charged to the customer's default payment method. Note that an unsuccessful retry is also returned with HTTP 200: check `success`. Possible `message` values with `success: false` are "Payment is not in failed status", "Payment failed", or the Stripe error message; with `success: true` they are "Payment successful" or "Payment was already completed". Takes no request body.

Path parameters

logId
string
Usage log ObjectId: the `_id` of an entry from `GET /stripe/usage/failed-payments`.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Invalid token, or the usage log belongs to another user (JSON: `{ "error": "Unauthorized" }`)
  • 404No usage log with this ID (JSON: `{ "error": "Payment record not found" }`)
  • 500Malformed `logId` or unexpected failure (JSON: `{ "success": false, "message": "Failed to retry payment" }`)
Request
curl -X POST "https://app.ummahspot.com/stripe/usage/retry-payment/:logId" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true,
  "message": "Payment successful"
}
GET/stripe/usage/can-upload

Check whether the user may start an OCR upload

Auth required

Combined eligibility check for OCR uploads. Returns `{ "canUpload": true }` when the user has an active subscription (or a canceled one still inside its paid period) and their unresolved failed overage payments for the current period total no more than 500 cents ($5.00). Otherwise returns `canUpload: false` with a `reason`: `"No active subscription"`, or `"UNPAID_BALANCE"` together with `unpaidBalance` (cents) and `failedPaymentCount`. Users with the `editor` or `admin` role always get `{ "canUpload": true, "isAdmin": true }`.

Errors

  • 401No bearer token provided (plain text: "Unauthorized: No token provided")
  • 403Token is invalid or expired (plain text: "Forbidden: Invalid token")
  • 500Check failed (plain text: "Failed to check upload eligibility")
Request
curl -X GET "https://app.ummahspot.com/stripe/usage/can-upload" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "canUpload": false,
  "reason": "UNPAID_BALANCE",
  "unpaidBalance": 640,
  "failedPaymentCount": 2
}