BillingMigration
Migrate from Stripe to Easy Billing
Mechanical mapping from Stripe's Billing API to Easy Labs.
This page is a side-by-side reference. The two APIs share the Customer / Product / Price / Subscription / Invoice / Coupon / Promotion code model, so most code is a method-name swap and a small payload translation. Differences worth knowing are called out under Object-model differences.
Subscriptions
| Stripe | Easy Labs (@easylabs/node) | Notes |
|---|---|---|
stripe.subscriptions.create({ customer, items, default_payment_method }) | easy.createSubscription({ identity_id, items, instrument_id }) | customer → identity_id; default_payment_method → instrument_id. Items use price_id + quantity in both. |
stripe.subscriptions.retrieve(id) | easy.getSubscription(id) | Returns { data: SubscriptionData }. |
stripe.subscriptions.update(id, body) | easy.updateSubscription(id, body) | cancel_at_period_end, proration_behavior, items, metadata are 1:1. |
stripe.subscriptions.cancel(id, { invoice_now }) | easy.cancelSubscription(id, { at_period_end }) | Easy treats at_period_end: false (default) as "cancel now". |
stripe.subscriptions.update(id, { pause_collection }) | easy.pauseSubscription(id, { behavior }) / easy.resumeSubscription(id) | Dedicated endpoints. behavior is void / keep_as_draft / mark_uncollectible. |
stripe.subscriptionItems.create / update / del | easy.addSubscriptionItem / updateSubscriptionItem / removeSubscriptionItem | Same shape. |
stripe.invoices.retrieveUpcoming({ subscription, subscription_items }) | easy.getSubscriptionProrationPreview(id, { items, remove_items }) | Returns the proration delta only (not a full upcoming invoice). |
stripe.subscriptionItems.createUsageRecord | easy.reportSubscriptionUsage(subscriptionId, body) | Same quantity + action (increment / set) + timestamp semantics. |
Invoices
| Stripe | Easy Labs | Notes |
|---|---|---|
stripe.invoices.create(body) | easy.createInvoice(body) | customer → buyer_id; line items pass inline as items[] rather than separate invoiceItems.create calls. |
stripe.invoices.finalizeInvoice(id) + sendInvoice(id) | easy.sendInvoice(id, { attach_pdf, cc_recipients }) | One call: finalizes and sends. |
stripe.invoices.pay(id, { payment_method }) | easy.payInvoice(id, { instrument_id }) | Use idempotency_key on retries. |
stripe.invoices.sendInvoice(id) (reminder) | easy.remindInvoice(id) | Sends a follow-up email on an already-sent invoice. |
stripe.invoices.voidInvoice(id) | easy.voidInvoice(id) | Same. |
stripe.invoices.list({ status, collection_method }) | easy.listInvoices({ status, collection_method, due_date_from, due_date_to }) | status enum is uppercase (OPEN, PAID, ...) on Easy. |
Coupons and promotion codes
| Stripe | Easy Labs | Notes |
|---|---|---|
stripe.coupons.create({ percent_off, duration, duration_in_months }) | easy.createCoupon({ percent_off, duration, duration_in_months }) | Same. Or pass amount_off + currency for fixed discounts. |
stripe.promotionCodes.create({ coupon, code }) | easy.createPromotionCode({ coupon_id, code }) | coupon → coupon_id. Same first_time_only, max_redemptions, minimum_amount. |
stripe.promotionCodes.list({ code }) then check active | easy.validatePromotionCode({ code, identity_id, amount }) | Single call returns valid, coupon, discount_preview, and reason. |
stripe.subscriptions.update(id, { discounts: [{ coupon }] }) | easy.applySubscriptionDiscount(id, { coupon_id }) or { promotion_code } | Dedicated endpoint. subscription_item_id scopes to one item. |
Customer portal
| Stripe | Easy Labs | Notes |
|---|---|---|
stripe.billingPortal.configurations.update(...) | PATCH /v1/api/customer-portal-config/ | Toggles for payment methods, subscriptions, cancellations. |
stripe.billingPortal.sessions.create({ customer, return_url }) | POST /v1/api/customer-portal/access/request-link ({ company_id, email, destination }) | Easy emails the customer a magic link instead of returning a session URL directly. For an inline same-tab handoff, use the access/handoff/create + access/handoff/exchange pair. |
Webhooks
Both platforms sign webhooks with HMAC-SHA256 over the raw body. Headers and helpers:
| Stripe | Easy Labs |
|---|---|
Stripe-Signature header + stripe.webhooks.constructEvent(body, sig, secret) | x-easy-webhook-signature: sha256=<hex> header + EasyWebhooks.constructEvent(body, sig, secret) from @easylabs/node |
customer.subscription.created / .updated / .deleted | subscription.created / .updated / .deleted |
customer.subscription.trial_will_end | subscription.trial_will_end |
customer.subscription.paused / .resumed | subscription.paused / .resumed |
invoice.created / .finalized / .paid / .payment_failed / .voided | Same names. |
invoice.marked_uncollectible | invoice.marked_uncollectible |
coupon.created / .updated / .deleted | coupon.created / .updated / .deleted |
promotion_code.created / .updated | promotion_code.created / .updated / .deleted |
checkout.session.completed | checkout.session.completed (plus checkout.session.crypto_confirmed if you accept crypto) |
See the webhooks reference for the full event list and signature format.
Object-model differences
- Customer is
Identityunder the hood — most fields you read are nested underdata.entity.*. Anywhere Stripe takescustomer, Easy takesidentity_id. - Payment method is "instrument" —
payment_method→instrument_id. The shape returned fromcreatePaymentInstrumentcarries instrument-level enable/disable flags rather than card-level metadata. - No detached invoice items. Stripe's two-step
invoiceItems.create+invoices.createis collapsed: passitemsinline oncreateInvoice. - Single dunning policy per merchant. Stripe's per-customer retry rules are replaced by
dunning-config+ revenue-recovery automations applied globally. - Money is in the smallest currency unit (cents) on both platforms.
currencyis uppercase ISO 4217 on Easy (e.g."USD"). - Status enums are uppercase on Easy invoices (
OPEN,PAID,VOID) but lowercase on subscriptions (active,trialing,past_due) — matching the underlying engines. - No
expand. Endpoints return the linked records they need (e.g.getProductWithPrices); there is no generic expansion mechanism.