Skip to main content

Subscriptions & Rebilling

Time: ~10 minutes | Difficulty: Intermediate

What Are Subscriptions in TagadaPay?

TagadaPay manages the entire recurring billing lifecycle — creation, automatic rebills, retries on failure, pause/resume, cancellation, and processor changes. It’s PSP-agnostic: the same subscription can be billed through Stripe today and NMI tomorrow, without touching your code.
┌────────────┐     ┌─────────────┐     ┌──────────────┐     ┌──────────┐
│  Customer   │────▶│  Subscribe  │────▶│  Auto-rebill │────▶│  Renew   │
│  pays once  │     │  (active)   │     │  every cycle │     │  forever │
└────────────┘     └─────────────┘     └──────────────┘     └──────────┘
                         │                    │
                         │              ┌─────┴──────┐
                         │              │  Declined?  │
                         │              │  → Retry    │
                         │              │  → Past due │
                         │              │  → Cancel   │
                         │              └────────────┘

                    ┌────┴────┐
                    │  Trial? │
                    │  7 days │
                    │  free   │
                    └─────────┘
Subscriptions are vaulted. The customer’s card is tokenized once. TagadaPay charges it automatically each cycle — through whichever processor your payment flow selects. You never handle raw card data or PSP-specific recurring APIs.

Subscription Lifecycle

StatusMeaning
trialingFree trial period, no charges yet
activeRecurring charges are running
past_dueLast rebill failed, retrying automatically
pausedBilling suspended, can be resumed
canceledTerminated (immediately or at period end)
create → trialing → active ⟲ (rebill each cycle)

                   past_due → retry → active (success)
                       ↓              → canceled (max retries)

                    paused → resume → active

                   canceled

Create a Subscription

import Tagada from '@tagadapay/node-sdk';

const tagada = new Tagada('your-api-key');

const { subscription } = await tagada.subscriptions.create({
  customerId: 'cus_abc123',
  priceId: 'price_monthly_2999',
  storeId: 'store_xxx',
  currency: 'USD',
  quantity: 1,
  defaultPaymentInstrumentId: 'pi_abc123',
});

console.log(subscription.id);             // 'sub_xyz789'
console.log(subscription.status);          // 'active'
console.log(subscription.nextBillingDate); // '2026-04-19T00:00:00Z'
ParameterRequiredDescription
customerIdYesThe customer to bill
priceIdYesA recurring price (from a product variant)
storeIdYesYour store
currencyYesISO currency code
defaultPaymentInstrumentIdNoCard to charge (uses customer’s default if omitted)
quantityNoNumber of units (default: 1)
paymentIdNoLink to the initial payment
billingCycleAnchorNoISO date — fix the billing day (e.g. always the 1st)
initiatePaymentNoCharge immediately on creation

Create with a Free Trial

const { subscription } = await tagada.subscriptions.create({
  customerId: 'cus_abc123',
  priceId: 'price_monthly_2999',
  storeId: 'store_xxx',
  currency: 'USD',
  defaultPaymentInstrumentId: 'pi_abc123',
  trialSettings: {
    trialPeriodDuration: 7,
    trialPeriodDurationType: 'day',
  },
});

console.log(subscription.status); // 'trialing'
console.log(subscription.trial);  // true
The customer isn’t charged until the trial ends. TagadaPay automatically transitions to active and triggers the first rebill.
Trial Duration TypeExamples
day3 days, 7 days, 14 days
week1 week, 2 weeks
month1 month, 3 months

Manual Rebill

Trigger an immediate charge on any active or past_due subscription — useful for dashboards, support tools, or retry scripts.
const result = await tagada.subscriptions.rebill('sub_xyz789');

console.log(result.success); // true
console.log(result.message); // 'Rebill triggered successfully'
What happens under the hood:
  1. TagadaPay loads the subscription’s price, currency, and payment instrument
  2. Routes the charge through your payment flow (cascade, weighted, fallback — all automatic)
  3. On success → updates nextBillingDate, status stays active
  4. On decline → enters retry logic (see below)

When to Use Manual Rebill

ScenarioExample
Customer updated cardThey fixed their card details, you want to charge immediately instead of waiting for auto-retry
Support request”Please charge me now”
Retry scriptBatch-retry all past_due subscriptions
TestingVerify billing works end-to-end

Batch Retry All Past-Due Subscriptions

const { items: subscriptions } = await tagada.subscriptions.list({
  per_page: 100,
  filters: {
    'subscriptions.status': ['past_due'],
    storeIds: ['store_xxx'],
  },
});

console.log(`Found ${subscriptions.length} past-due subscriptions`);

for (const sub of subscriptions) {
  try {
    await tagada.subscriptions.rebill(sub.id);
    console.log(`Rebilled ${sub.id}`);
  } catch (err) {
    console.log(`Failed ${sub.id}: ${err.message}`);
  }
}

Automatic Retry on Failure

When a rebill is declined, TagadaPay doesn’t immediately cancel. It retries automatically:
Rebill declined
  → Wait 24h  → Retry #1
  → Wait 48h  → Retry #2
  → Wait 96h  → Retry #3
  → Wait 168h → Retry #4
  → Wait 168h → Retry #5
  → Cancel subscription
RetryDelayTotal elapsed
124 hours1 day
248 hours3 days
396 hours7 days
4168 hours14 days
5168 hours21 days
During retries, the subscription status is past_due. After max retries, it’s automatically canceled.
Smart retry is also available — it uses failure codes (e.g. “insufficient funds”), velocity limits, and even payroll-day heuristics to pick the optimal retry time. Contact support to enable it.

Cancel a Subscription

Cancel Immediately

await tagada.subscriptions.cancel({
  subscriptionIds: ['sub_xyz789'],
});
// Status → 'canceled' right now

Cancel at Period End

await tagada.subscriptions.cancel({
  subscriptionIds: ['sub_xyz789'],
  cancelAtPeriodEnd: true,
});
// Stays 'active' until currentPeriodEnd, then auto-cancels
The customer keeps access until their billing period ends. TagadaPay handles the scheduled cancellation automatically.

Bulk Cancel

await tagada.subscriptions.cancel({
  subscriptionIds: ['sub_001', 'sub_002', 'sub_003'],
  cancelAtPeriodEnd: true,
});

Resume a Subscription

Resume a canceled, paused, or past_due subscription:
await tagada.subscriptions.resume({
  subscriptionIds: ['sub_xyz789'],
});
// Status → 'active', billing resumes
If the subscription had cancelAtPeriodEnd: true but hasn’t reached the end yet, resuming clears the cancellation flag.

Pause a Subscription

Pausing stops billing without canceling. The subscription can be resumed later.
Pause is available via the dashboard and internal API. The SDK exposes cancel + resume for the same effect — cancel at period end, then resume when ready.

Change Processor

Move a subscription from one processor to another — for example, migrating from Stripe to Checkout.com, or clearing a stuck processor.

Assign a Specific Processor

const { subscription } = await tagada.subscriptions.changeProcessor(
  'sub_xyz789',
  'proc_cko',  // Checkout.com
);

console.log(subscription.processorId); // 'proc_cko'
The next rebill will use Checkout.com instead of the previous processor.

Let TagadaPay Auto-Select

const { subscription } = await tagada.subscriptions.changeProcessor('sub_xyz789');
// No processorId → TagadaPay uses the payment flow strategy
This removes the sticky processor, so the next rebill will be routed by your payment flow (weighted, cascade, etc.).

Use Case: Migrate All Subscriptions from Stripe to NMI

const { items: subscriptions } = await tagada.subscriptions.list({
  per_page: 100,
  filters: {
    'subscriptions.status': ['active'],
    processorIds: ['proc_s1'],  // current: Stripe
  },
});

for (const sub of subscriptions) {
  await tagada.subscriptions.changeProcessor(sub.id, 'proc_nmi');
  console.log(`Migrated ${sub.id} to NMI`);
}
This is a TagadaPay superpower. With a traditional PSP, migrating subscriptions between processors means re-collecting card details. With TagadaPay’s PSP-agnostic vault, you just change the processor ID — the vaulted card works across all processors.

List & Filter Subscriptions

const { items, pageInfo } = await tagada.subscriptions.list({
  page: 1,
  per_page: 50,
  sort: 'createdAt',
  sort_order: 'desc',
  filters: {
    storeIds: ['store_xxx'],
    'subscriptions.status': ['active', 'trialing'],
  },
});

for (const sub of items) {
  console.log(`${sub.id}${sub.status} — next bill: ${sub.nextBillingDate}`);
}

Available Filters

FilterTypeExample
subscriptions.statusstring[]['active', 'past_due']
subscriptions.trialbooleantrue
storeIdsstring[]['store_xxx']
processorIdsstring[]['proc_s1', 'proc_nmi']
currencystring'USD'
customerEmailstring'jane@example.com'
price{ condition, value }{ condition: 'gte', value: [1000] }
date{ condition, value }{ condition: 'between', value: ['2026-01-01', '2026-03-01'] }
nextBillingDate{ condition, value }{ condition: 'lte', value: ['2026-03-25'] }

Retrieve a Subscription

const { subscription } = await tagada.subscriptions.retrieve('sub_xyz789');

console.log(subscription.status);                // 'active'
console.log(subscription.currentPeriodStart);     // '2026-03-01T00:00:00Z'
console.log(subscription.currentPeriodEnd);       // '2026-04-01T00:00:00Z'
console.log(subscription.nextBillingDate);        // '2026-04-01T00:00:00Z'
console.log(subscription.processorId);            // 'proc_s1'
console.log(subscription.paymentInstrumentId);    // 'pi_abc123'
console.log(subscription.cancelAtPeriodEnd);      // false

Webhook Events

Listen for subscription events via webhooks to keep your system in sync:
EventWhen it fires
subscription/createdNew subscription created
subscription/rebillSucceededRecurring charge succeeded
subscription/rebillDeclinedRecurring charge failed
subscription/pastDueSubscription entered past_due after decline
subscription/pausedSubscription paused
subscription/resumedSubscription resumed
subscription/canceledSubscription canceled immediately
subscription/cancelScheduledCancel scheduled for period end
subscription/rebillUpcomingUpcoming rebill (advance notice)

Full Example: Subscription with Trial, Rebill, and Processor Migration

import Tagada from '@tagadapay/node-sdk';

const tagada = new Tagada('your-api-key');
const storeId = 'store_xxx';

// ── 1. Create a subscription with 14-day trial ──────────────────────
const { subscription } = await tagada.subscriptions.create({
  customerId: 'cus_abc123',
  priceId: 'price_monthly_2999',
  storeId,
  currency: 'USD',
  defaultPaymentInstrumentId: 'pi_abc123',
  trialSettings: {
    trialPeriodDuration: 14,
    trialPeriodDurationType: 'day',
  },
});

console.log(`Created: ${subscription.id}${subscription.status}`);
// → "Created: sub_xyz789 — trialing"

// ── 2. Later: trial ends, auto-rebill runs ──────────────────────────
// (TagadaPay handles this automatically — no code needed)

// ── 3. Manual rebill (e.g. customer updated their card) ─────────────
const rebillResult = await tagada.subscriptions.rebill(subscription.id);
console.log(`Rebill: ${rebillResult.message}`);

// ── 4. Move subscription from Stripe to Checkout.com ────────────────
const { subscription: updated } = await tagada.subscriptions.changeProcessor(
  subscription.id,
  'proc_cko',
);
console.log(`Processor changed to: ${updated.processorId}`);

// ── 5. Customer wants to cancel at period end ───────────────────────
await tagada.subscriptions.cancel({
  subscriptionIds: [subscription.id],
  cancelAtPeriodEnd: true,
});
console.log('Cancel scheduled at period end');

// ── 6. Customer changes their mind — resume ─────────────────────────
await tagada.subscriptions.resume({
  subscriptionIds: [subscription.id],
});
console.log('Subscription resumed!');

Next Steps

Multi-PSP Routing & Vault

Connect Stripe, NMI, Checkout.com, Airwallex and route payments intelligently

Headless Payments (Frontend)

Build your own checkout UI with client-side card tokenization and 3DS

Node SDK Quick Start

Get started with stores, products, and funnels

API Reference

Full REST API documentation