Skip to main content
This guide covers everything you need to integrate RMZ’s subscription and billing system into your application. Whether you are building a storefront with recurring products or a SaaS platform that needs subscription management, this guide will walk you through the full flow.

Prerequisites

  • An RMZ store with an RMZ+ subscription plan
  • At least one subscription product created in your store dashboard
  • API keys from Dashboard > Settings > API Keys
  • A webhook endpoint to receive subscription events

Overview

RMZ subscriptions support:
  • Multiple billing cycles: monthly, quarterly, semi-annual, annual, biennial, and more
  • Free trials: optional trial periods before the first charge
  • Auto-renewal: automatic charging via saved payment cards
  • Dunning: retry logic for failed payments (up to 3 attempts over 7 days)
  • Plan changes: upgrades (immediate with proration) and downgrades (at next period)
  • Customer portal: hosted self-service portal for subscription management

Subscription Lifecycle

created → trialing → active → renewed (repeats)
             │           ↓
             │       past_due ──► expired
             │           ↓
             │         active (if payment recovered)
             │           ↕
             │         paused ──► active (on unpause/resume)

             └──► canceled / expired (from trialing)

active / trialing / past_due / paused → canceled (immediate)
                                      or cancel_at_period_end → expired

canceled → active (resume, if the paid period has not yet ended)
expired  → (terminal, no further transitions)
Allowed state transitions (enforced server-side by SubscriptionStatus::canTransitionTo()):
FromAllowed Next States
trialingactive, past_due, canceled, expired
activepast_due, paused, canceled, expired
past_dueactive, canceled, expired
pausedactive, canceled, expired
canceledactive (resume while period still valid)
expired— (terminal)

Status Reference

StatusAccess GrantedDescription
trialingYesCustomer is in a free trial period
activeYesSubscription is active and paid
past_dueYesPayment failed, retries in progress (grace period)
pausedNoSubscription is on hold (“freeze time”). Access is suspended while paused and auto-renewal is halted. At pause time, the days remaining in the current period are banked in metadata.paused_remaining_days. When the subscription is unpaused back to active, those banked days are added to the new period end, so the customer does not lose any paid time.
canceledNoSubscription was canceled immediately. Can be resumed back to active while the originally paid period has not yet ended.
expiredNoSubscription period ended or all retries exhausted. Terminal state.

Storefront Flow

If you are building a custom storefront (headless), customers purchase subscription products through the standard checkout flow.

1. Display Subscription Products

Fetch products with type subscription and display their variants:
const { data: product } = await sdk.products.getBySlug('pro-plan');

// Each variant represents a billing cycle
product.subscription_variants.forEach(variant => {
  console.log(`${variant.durationText}: ${variant.price} SAR`);
  // e.g., "شهر: 49 SAR", "سنه: 399 SAR"
});

2. Add to Cart and Checkout

// Add the subscription variant to the cart
await sdk.cart.addItem(product.id, 1, { variant_id: selectedVariant.id });

// Create checkout
const checkout = await sdk.checkout.create({
  payment_method: 'card'
});

// Redirect to payment
window.location.href = checkout.redirect_url;

3. After Purchase

Once payment completes, a subscription is automatically created. Listen for the subscription.created webhook to provision access in your system.

4. View Customer Subscriptions

Authenticated customers can view their subscriptions:
const { data: subscriptions } = await sdk.orders.getSubscriptions();

subscriptions.forEach(sub => {
  console.log(`Status: ${sub.status}`);
  console.log(`Renews: ${sub.current_period_end}`);
  console.log(`Auto-renew: ${sub.auto_renew}`);
});

SaaS Integration Flow

If you are building a SaaS product and want to use RMZ for subscription billing, use the Merchant API to create checkout sessions programmatically.

1. Create a Checkout Session

When a user wants to subscribe in your application, create a checkout session on your server:
// Your server-side code
const response = await fetch(
  "https://merchant-api.rmz.gg/shawarma/subscriptions/checkout-sessions",
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.RMZ_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      product_id: 102,
      variant_id: 15,
      customer: {
        country_code: user.countryCode,
        phone: user.phone,
        firstName: user.firstName,
        lastName: user.lastName,
        email: user.email
      },
      metadata: {
        external_user_id: user.id,
        plan: "pro"
      },
      success_url: "https://yourapp.com/subscription/success",
      cancel_url: "https://yourapp.com/pricing"
    })
  }
);

const { data } = await response.json();
// Redirect user to data.checkout_url

2. Handle the Webhook

Set up a webhook to handle subscription events:
app.post("/webhooks/rmz", async (req, res) => {
  const { event, event_id, data } = req.body;
  const subscription = data.subscription;
  const metadata = subscription.metadata; // Your custom data from checkout session

  switch (event) {
    case "subscription.created":
      // Use metadata.external_user_id to link RMZ subscription to your user
      await provisionAccess(metadata.external_user_id, subscription.id);
      break;

    case "subscription.renewed":
      await extendAccess(metadata.external_user_id, subscription.current_period_end);
      break;

    case "subscription.canceled":
    case "subscription.expired":
      await revokeAccess(metadata.external_user_id);
      break;

    case "subscription.past_due":
      await showPaymentWarning(metadata.external_user_id);
      break;
  }

  res.status(200).json({ received: true });
});

3. Manage Subscriptions via API

Cancel, extend, or query subscriptions from your server:
// Cancel at end of period
await fetch(`https://merchant-api.rmz.gg/shawarma/subscriptions/${subId}/cancel`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.RMZ_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ effective: "end_of_period" })
});

// Extend by 7 days (courtesy)
await fetch(`https://merchant-api.rmz.gg/shawarma/subscriptions/${subId}/extend`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.RMZ_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ days: 7 })
});

Webhook Events Reference

Subscribe to these events to keep your system in sync:
EventWhen to Use
subscription.createdProvision access for new subscribers
subscription.activatedUpgrade trial users to full access
subscription.renewedConfirm recurring payment and extend access
subscription.renewal_failedAlert customer about payment issues
subscription.past_dueShow warning banners, send dunning emails
subscription.expiredRevoke access
subscription.canceledTrigger retention flows
subscription.updatedHandle plan changes and extensions
See the full Webhook Events documentation for payload examples.

Customer Billing Portal

The billing portal is a hosted page where customers can self-manage their subscriptions. You do not need to build any subscription management UI.

Create a Portal Session

// Server-side: generate a portal URL for the customer
const response = await fetch(
  "https://merchant-api.rmz.gg/shawarma/portal-sessions",
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.RMZ_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      customer: {
        country_code: "966",
        phone: "512345678"
      },
      return_url: "https://yourapp.com/account"
    })
  }
);

const { data } = await response.json();
// Redirect customer to data.url

Portal Capabilities

In the portal, customers can:
  • View all their subscriptions and status
  • Cancel a subscription (end of period or immediately)
  • Change subscription plan (upgrade or downgrade)
  • Update their payment method
  • View payment history and invoices
See the Portal Sessions API documentation for details.

Auto-Renewal and Dunning

When a customer has a saved payment card and auto_renew is enabled, RMZ automatically charges the card before the subscription period ends.

Renewal Process

  1. 3 days before period end: RMZ attempts to charge the saved card
  2. If payment succeeds: Subscription is renewed, subscription.renewed webhook fires
  3. If payment fails: Subscription enters past_due, retry schedule begins

Retry Schedule

AttemptTimingWebhook Event
1stImmediately (at period end)subscription.renewal_failed
2nd3 days after first failuresubscription.renewal_failed
3rd7 days after first failuresubscription.renewal_failed
FinalAfter 3rd failuresubscription.expired
During the retry period, the subscription remains in past_due status. Access is still granted during this grace period to avoid disrupting the customer.
Use the subscription.renewal_failed webhook to send the customer an email asking them to update their payment method. Include a link to the billing portal.

Managing Subscriptions via API

List Subscriptions

curl -X GET "https://merchant-api.rmz.gg/shawarma/subscriptions" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Get Subscription Details

curl -X GET "https://merchant-api.rmz.gg/shawarma/subscriptions/501" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Cancel a Subscription

curl -X POST "https://merchant-api.rmz.gg/shawarma/subscriptions/501/cancel" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"effective": "end_of_period"}'

Extend a Subscription

curl -X POST "https://merchant-api.rmz.gg/shawarma/subscriptions/501/extend" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"days": 7}'
See the full Merchant API Subscriptions documentation for all available endpoints.

Best Practices

Use webhooks for state changes

Never rely solely on API polling. Subscribe to webhook events and process them to keep your system in sync with subscription state.

Handle idempotency

Your webhook handler may receive the same event more than once. Use the X-RMZ-REQUEST-ID header to deduplicate.

Graceful degradation for past_due

Do not immediately revoke access when a subscription enters past_due. The customer still has access during the retry period.

Use end_of_period cancellation

Default to end_of_period cancellation to preserve the customer experience. Only use immediate when necessary (e.g., policy violations).

Next Steps

Webhook Events

Full list of subscription webhook events with payload examples.

Merchant API

Complete API reference for subscription management.

Billing Portal

Create portal sessions for customer self-service.

Webhook Integration Guide

Learn how to set up and verify webhooks.