Currency

Referrals

The referrals namespace is the merchant-side surface for Ripllo's referral program: one toggle for the whole workspace, per-customer link issuance, and a four-step attribution lifecycle (signup → checkout-start → fulfill → void) that mints reward discount codes when a referred purchase clears. This page covers ripllo.referrals on the Node SDK. For the data model and the HTTP surface, see API → Referrals.

Namespace

ripllo.referrals.getProgram()
ripllo.referrals.putProgram(input)
ripllo.referrals.stats()

ripllo.referrals.issueLink(input)
ripllo.referrals.resolveLink(accountId, code)
ripllo.referrals.recordClick(input)

ripllo.referrals.attributeOnSignup(input)
ripllo.referrals.attributeCheckoutStart(input)
ripllo.referrals.fulfillRewardOnPayment(input)
ripllo.referrals.voidAttributionOnRefund(checkoutSessionId)

ripllo.referrals.listMyRewards(accountId, customerId)
ripllo.referrals.expirePending()

Three sub-surfaces: program config, links for sharing, attributions for the buyer lifecycle. The reward currency is a Ripllo-issued discount code — under the hood, fulfillment mints real DiscountCode rows (REF-… for the referrer, WELCOME-… for the referee).

Program

getProgram

const program = await ripllo.referrals.getProgram(); // null if never configured

Returns the merchant's ReferralProgram or null if it's never been set up. There is exactly one program per workspace.

putProgram

const program = await ripllo.referrals.putProgram({
  enabled: true,
  rewardType: 'percent',
  referrerValue: 15,                   // 15%
  refereeValue: 10,                    // 10%
  currency: 'IDR',
  minPurchaseAmount: 100_000,
  rewardExpiryDays: 90,
  attributionWindowDays: 30,
  maxRewardsPerReferrer: 50,
  programTerms: 'Codes redeemable on full-price items only.',
});

Upsert — same shape on every call. attributionWindowDays is the maximum gap between a click and a qualifying signup, and rewardExpiryDays becomes the expiresAt on each minted reward code.

Reward values are in the reward type's own units, and the program row does not validate them. For percent / shipping_percent, referrerValue and refereeValue must be 1–100 — a percent program saved as 1500 accepts the save and then throws INVALID_VALUE ("Percent discount value must be 1-100") on every reward issuance, so the program looks configured and silently never pays out. For fixed / shipping_fixed the values are minor units of currency.

stats

const s = await ripllo.referrals.stats();
console.log(s.totalLinks, s.totalClicks, s.totalSignups, s.totalRewards);
console.log(s.attributedRevenue, s.conversionRate);

Lifetime counters for the program, aggregated over every ReferralLink in the workspace: totalLinks, totalClicks, totalSignups, totalRewards, attributedRevenue (minor units, summed at fulfillment) and conversionRate (totalRewards / totalClicks, 0 when there are no clicks). Use this for the dashboard hero strip.

const { link } = await ripllo.referrals.issueLink({
  accountId: 'acc_<merchant>',
  customerId: 'cus_<referrer>',
});
// link.code → 'r-7H3K2N' (the shareable suffix)

Mints (or returns the existing) referral link for a customer. Idempotent — calling it twice for the same (accountId, customerId) returns the same record.

const { link } = await ripllo.referrals.resolveLink('acc_<merchant>', 'r-7H3K2N');

Looks up a link by its code. Throws not_found if the code doesn't exist or doesn't belong to the named merchant.

recordClick

await ripllo.referrals.recordClick({ accountId: 'acc_<merchant>', code: 'r-7H3K2N' });

Stamps a click for analytics. Call this from your storefront's /r/:code redirect handler before bouncing the visitor to the shop. The response carries the resolved linkId and programId so you can drop a click cookie if you want fingerprintless attribution.

Attribution lifecycle

The four-step lifecycle is the heart of the referral product. Call each step from the corresponding hook in your stack:

attributeOnSignup

const { attribution } = await ripllo.referrals.attributeOnSignup({
  accountId: 'acc_<merchant>',
  refereeCustomerId: 'cus_<new>',
  refereeEmail: 'new@example.com',
  linkCode: 'r-7H3K2N',
  externalSource: 'storlaunch',
  externalRef: 'cus_<storlaunchCustomerId>',
});

Called when a new customer registers via a referral link. Creates a ReferralAttribution in pending state. Returns null if the link is invalid or the referee's email matches the referrer's (self-referral guard).

attributeCheckoutStart

await ripllo.referrals.attributeCheckoutStart({
  accountId,
  customerId,
  checkoutSessionId,
});

Stamps the attribution onto a checkout session at cart-start. Idempotent — safe to call from your checkout-initiated webhook regardless of whether referrer attribution exists.

fulfillRewardOnPayment

const result = await ripllo.referrals.fulfillRewardOnPayment({
  checkoutSessionId,
  status: 'completed',        // anything else is a no-op — see below
  currency: 'IDR',
  amount: 250_000,
});

if (result.issued) {
  console.log('Rewards:', result.referrerCodeId, result.refereeCodeId);
} else {
  console.warn('No reward issued:', result.reason);
}

The reward-minting step — called from your payment-success webhook. When the referred customer's purchase clears, this mints two discount codes (one for the referrer, one for the referee) and flips the attribution to rewarded. Idempotent on checkoutSessionId (matched against the attribution's qualifyingCheckoutSessionId).

status must be exactly 'completed'. Any other value — 'paid' included — returns { issued: false, reason: 'session_not_completed' } and throws nothing, so both parties silently never get their codes. Always branch on result.issued and log result.reason:

reason Meaning
session_not_completed status wasn't 'completed'.
no_attribution No attribution is stamped on that checkout session.
already_fulfilled The attribution has already left pending.
expired Past the attribution's expiresAt; the attribution is voided as expired.
program_disabled The program was switched off between signup and payment; attribution voided.
currency_mismatch currency differs from the program's; attribution voided.
min_purchase amount is under minPurchaseAmount. The attribution stays pending.
max_rewards_per_referrer The referrer hit their cap; attribution voided.

voidAttributionOnRefund

await ripllo.referrals.voidAttributionOnRefund('cs_<plugipaySession>');

Reverses the attribution and claws back the issued reward codes when the underlying payment is refunded. The codes are archived rather than deleted — this leaves an audit trail.

Rewards

const { items } = await ripllo.referrals.listMyRewards('acc_<merchant>', 'cus_<self>');
for (const r of items) {
  console.log(r.role, r.code, r.value, r.expiresAt, r.redeemed, r.active);
}

Returns the calling customer's earned reward codes (for a "my rewards" dashboard page on the storefront) — only attributions that reached rewarded, so there is nothing pending in here. Each item is { role: 'referrer' | 'referee', attributionId, code, discountType, value, currency, expiresAt, redeemed, active, earnedAt }. Use redeemed / active / expiresAt to render state; there is no status field on a reward.

The attribution itself carries the status: pendingrewarded → (voided | expired). Note it is rewarded, not fulfilled.

Sweeps

const { expired } = await ripllo.referrals.expirePending();

Idempotent sweep that flips any attribution older than attributionWindowDays to expired. Requires a platform-admin key (ripllo:platform:admin); an ordinary merchant key gets 403. Wire it to a daily cron — the Ripllo platform calls it for you on prod, but partners running their own deployments need to schedule it.

Errors

Code Status Cause
program_disabled 422 Calling an attribution method while program.enabled = false.
self_referral 422 Referee's email matches the referrer's.
not_found 404 Unknown link code or attribution.
forbidden 403 Key lacks ripllo:referral:write.

See Errors for handling.

Next

CurrencyRupiah is paid by transfer or QRIS; US dollars settle through PayPal.