Discount codes

Plugipay family portal: discounts

A discount code is a string a customer enters at checkout to reduce their total: WELCOME10, BLACKFRIDAY, FREESHIP. The Discounts page is where you create them, edit them, watch redemptions land, and turn them off when a campaign ends.

You don't have to use codes for every promotion — sitewide markdowns are often better handled storefront-side. Use a code when you want a targeted, trackable lever: "share this code with newsletter subscribers", "first-order coupon for users coming from a paid ad", "winback offer in an abandoned-cart email."

Rule of thumb. If you want to know which campaign drove a sale, give the campaign a code. The redemption ledger gives you that attribution for free.

What a discount code holds

Every code has the same shape, whether you create it from the portal or the API:

Field What it's for
id Ripllo-assigned, looks like disc_01H…. Stable forever.
code The string the customer types. Case-insensitive within a workspace.
description Internal-only note for your team. Customers never see it.
type percent or fixed.
value The amount: 10 for 10%, or 25000 for IDR 25,000 off.
currency Currency for fixed-value codes (and for minPurchaseAmount).
scope all, products, or tag. Controls which cart lines the code applies to.
productIds When scope = products, the list of product IDs that qualify.
tagFilter When scope = tag, the product tags that qualify.
minPurchaseAmount Smallest order subtotal the code accepts.
maxUsesTotal Global cap across all customers.
maxUsesPerCustomer Cap per customer.
startsAt / expiresAt When the code is valid. Either or both optional.
active Toggle without deleting.
public If true, the code surfaces in the public storefront teaser endpoint.

The list view

From the dashboard sidebar, click Discounts. You land at /dashboard/discounts.

The page shows every code in the workspace sorted by createdAt desc. Filters at the top let you narrow to active only or expiring soon.

Columns

  • Code — the string + short status pill (active, paused, expired).
  • Type10% off or IDR 25,000 off.
  • Uses — current / cap. Empty cell if unlimited.
  • Created — short date stamp.

Click any row to open the detail panel.

Creating a code

Click + New discount. The form covers the fields above. A few notes:

  • The code field is case-insensitive on save and at validation time. Storing welcome10 vs WELCOME10 matters for the URL slug but not for the customer.
  • percent codes don't need a currency for the value itself, only for minPurchaseAmount. The form hides the irrelevant fields based on type.
  • Scoped codes (products or tag) only discount the matching lines — not the whole cart. A 10% off "footwear" code on a cart with a shirt and a pair of shoes only discounts the shoes.

When you click Create, Ripllo POSTs to /api/v1/discount-codes and the new code appears in the list.

Editing a code

Open a code's detail panel and click Edit. You can change every field except code and type — those are immutable once redemptions exist. To change either, archive the existing code and create a new one.

Why immutable? Redemption rows reference the code at the time of redemption. If you renamed WELCOME10 to WELCOME15 later, historical analytics would lie about what the customer actually used.

Validating before checkout

The storefront can call validate to preview whether a code applies, without consuming a use:

POST /api/v1/discount-codes/validate
{
  "code": "WELCOME10",
  "customerId": "cust_...",
  "subtotal": 250000,
  "currency": "IDR",
  "lineItems": [...]
}

The response returns applies: true | false, the computed discountAmount, and a reason if it didn't apply (expired, min_purchase_not_met, out_of_uses, scope_mismatch, etc.). This is what powers the "code accepted" UX in your cart before the customer hits Pay.

Redeeming at payment-success

Once the order is paid, your payment handler calls redeem to consume one use:

POST /api/v1/discount-codes/redeem
{
  "code": "WELCOME10",
  "customerId": "cust_...",
  "orderId": "order_...",        // becomes the redemption's externalRef
  "subtotal": 250000,
  "currency": "IDR"
}

Redemptions are idempotent on orderId — if your webhook retries, the same redemption row is returned with created: false. No double-counting.

If you're integrating through Storlaunch, this is wired up for you — Storlaunch's payment-success handler calls ripllo.discountCodes.redeem automatically.

Public applicable-codes endpoint

The storefront can fetch a list of codes the current customer could apply, for a "you might also like" teaser:

GET /api/v1/discount-codes/applicable/:accountId?subtotal=250000&currency=IDR

Only codes marked public: true show up here. Codes with public: false are still valid when the customer enters them manually, but they don't get surfaced.

Archiving a code

The trash icon on a code's row sends DELETE /api/v1/discount-codes/:id. This sets active: false and stamps archivedAt — the code is no longer redeemable but stays in the database for historical reporting. There is no hard-delete.

What you can't do (yet)

  • Bulk import — one code at a time via UI or API. Bulk endpoint is on the backlog.
  • Stacking codes — one code per order; the validator rejects a second one.
  • Per-region codes — no geo-fence today. Workaround: use tag scope on region-tagged products.

Next

  • Referrals — the dual-sided counterpart to discounts.
  • Marketing — combine discounts with abandoned-cart reminders.
  • API reference — the full endpoint set.