Guides

Guide - Subscription Billing

Maya subscription management for organizations in booki-api — plans, payment history, webhook handling, and the checkout flow.

Organization owners subscribe to a paid plan via Maya. Without an active subscription, organizations default to the free plan. Payment history is tracked in the SubscriptionPayment collection for billing records and analytics.


Data Model

ISubscriptionPayment

FieldTypeDescription
userIdObjectIdOwner user who holds the subscription
customerIdstringMaya checkout ID or customer ref
subscriptionIdstringInternal subscription ID
invoiceIdstringMaya payment ID
paymentIntentIdstring?Maya payment ID
amountnumberPayment amount (in smallest currency unit)
currencystringe.g. "php", "usd"
billingPeriodStartDateStart of the billing period
billingPeriodEndDateEnd of the billing period
seatsnumberNumber of branches/seats paid for
pricePerSeatnumberCost per seat
interval"monthly" | "annually"Billing interval
paymentMethodstring?Payment method used
statusstringpending, paid, failed, refunded
receiptUrlstring?Maya receipt reference URL
failureReasonstring?Reason for payment failure
metadataobject?{ name, serviceName }

User Model Extension

Owner users have a mayaCustomerId field on their user document. It is created automatically when the first subscription checkout is initiated.


Subscription States

StateMeaning
trialIn trial period
activePaid and current
suspendedPayment failed or manually suspended
cancelledSubscription ended

Plans

PlanRequires subscriptionFeatures
freeNoLimited (platform-specific)
proYes — active Maya subscriptionFull feature access

All new organizations default to the free plan.


Subscription Checkout Flow

1. Owner initiates subscription
        ↓
2. POST /api/v1/maya/create-subscription-checkout
   → API creates Maya checkout session
   → Returns checkout URL
        ↓
3. Owner completes payment on Maya-hosted page
        ↓
4. Frontend calls POST /api/v1/maya/verify-session to confirm
        ↓
5. Maya fires webhook to POST /api/v1/maya/webhook
        ↓
6. API processes event, records subscription payment

API Endpoints

Get Current Subscription

GET /api/v1/organizations/billing/subscription
Authorization: Bearer <owner_token>

Response — active plan:

{
  "id": "sub_1234567890",
  "plan": "pro",
  "status": "active",
  "billingInterval": "monthly",
  "currentPeriodEnd": "2026-04-15T00:00:00.000Z",
  "trialEnd": null,
  "cancelAtPeriodEnd": false,
  "amount": 999,
  "currency": "php"
}

Response — no subscription (free plan):

{
  "plan": "free",
  "status": "active",
  "billingInterval": "monthly",
  "currentPeriodEnd": null,
  "trialEnd": null,
  "cancelAtPeriodEnd": false
}

Get Payment History

GET /api/v1/subscription/my-payments
Authorization: Bearer <owner_token>
Query: search, page (default 1), limit (default 10), status ("pending"|"paid"|"failed"|"refunded")

Response (200):

{
  "stats": {
    "totalPaid": 5995,
    "totalPending": 0,
    "totalFailed": 999,
    "totalRefunded": 0
  },
  "payments": {
    "data": [
      {
        "_id": "507f1f77bcf86cd799439011",
        "amount": 999,
        "currency": "php",
        "billingPeriodStart": "2026-02-15T00:00:00.000Z",
        "billingPeriodEnd": "2026-03-15T00:00:00.000Z",
        "seats": 1,
        "pricePerSeat": 999,
        "interval": "monthly",
        "status": "paid",
        "receiptUrl": "https://payments.maya.ph/receipts/...",
        "metadata": { "name": "Codi Solutions", "serviceName": "Photo Studio" },
        "createdAt": "2026-02-15T00:00:00.000Z"
      }
    ],
    "page": 1,
    "limit": 10,
    "total": 6,
    "totalPages": 1
  }
}

Get Single Payment

GET /api/v1/subscription/payments/:id
Authorization: Bearer <owner_token>

Returns the full payment record. Returns 403 Forbidden if the payment belongs to a different user.


Maya Webhook

The webhook handler listens at POST /api/v1/maya/webhook.

EventHandling
payment.completedProcesses completed checkout; records subscription payment
payment.failedMarks subscription payment as failed
payment.expiredHandles expired checkout sessions
payment.cancelledHandles cancelled payment sessions

Configure the webhook endpoint URL in the Maya developer portal and set MAYA_SECRET_KEY in .env.


Frontend Integration

// Check subscription status
const subscription = await $fetch(
  "/api/v1/organizations/billing/subscription",
  {
    headers: { Authorization: `Bearer ${token}` },
  },
);

if (subscription.plan === "free") {
  // Show upgrade prompt
} else {
  // Show plan details, next billing date, manage button
}
// Paginated payment history
const history = await $fetch("/api/v1/subscription/my-payments", {
  headers: { Authorization: `Bearer ${token}` },
  params: { page: 1, limit: 10, status: "paid" },
});