Affiliates with Stripe
Use this guide when your website sends buyers to Stripe Checkout or a Stripe Payment Link created outside Plandalf. For a Plandalf-created checkout, use the Plandalf checkout guide.
Note. Early access These instructions describe the current affiliate preview. Verify a test payment and, for subscriptions, a paid renewal before switching to live mode.
1. Connect the Stripe sales source
- Connect the Stripe account that creates the payments under Settings → Integrations.
- Open Affiliates → Settings → Where you sell → Stripe.
- Enable Test Stripe sales while testing. Enable Live Stripe sales when you are ready for real referrals.
- Check the source’s last received event after your test payment. The connected account’s webhook events must reach Plandalf; installing the browser script alone does not import sales.
This connection reads sales and subscription events. Stripe affiliate payouts are disabled in this release, so affiliates can still be paid manually or through PayPal.
2. Carry the referral into Stripe
Copy the script URL from Affiliates → Setup. Replace acme.plandalf.dev below with your organization’s host. Pick the checkout type you use.
Stripe Payment Links
Install this on the page containing your Payment Link:
<script
src="https://acme.plandalf.dev/js/plandalf-sdk.js"
data-config-referral-decorate="buy.stripe.com"
async
></script>
<a href="https://buy.stripe.com/REPLACE_WITH_YOUR_PAYMENT_LINK">Buy now</a>Open that page through a partner link before clicking Buy now. The SDK captures the referral and adds it as Stripe’s client_reference_id when the link is clicked. Use an ordinary link to buy.stripe.com for this flow. Programmatic redirects and custom Stripe domains need an explicit handoff.
The decorator preserves an existing client_reference_id. If you already use that field for something else, use a server-created Checkout Session with referral metadata instead. Do not put one visitor’s referral token into a shared Payment Link’s permanent metadata.
Stripe Checkout created by your server
Read the referral before calling your own checkout endpoint. This example loads the SDK before the inline setup code; install the SDK only once.
<button id="buy" disabled>Buy now</button>
<script src="https://acme.plandalf.dev/js/plandalf-sdk.js"></script>
<script>
plandalf("ready", (sdk) => {
const button = document.querySelector("#buy");
button.disabled = false;
button.addEventListener("click", async () => {
button.disabled = true;
try {
const referral = await sdk.referral();
const response = await fetch("/billing/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ referral_token: referral?.token ?? null }),
});
if (!response.ok) throw new Error("Could not start checkout");
const { url } = await response.json();
window.location.assign(url);
} catch (error) {
button.disabled = false;
window.alert("Checkout could not start. Please try again.");
}
});
});
</script>/billing/checkout is an endpoint you implement in your app, not a Plandalf API route. Apply your normal authentication and CSRF protection there. Get the price and Stripe customer from trusted server-side records, not from the browser request. Pass the captured referral_token as referralToken to the following helper.
For a one-off payment, set metadata on the Session and PaymentIntent:
export async function createOneOffCheckout(stripe, { referralToken, stripeCustomerId }) {
const metadata = referralToken ? { plandalf_referral: referralToken } : {};
return stripe.checkout.sessions.create({
mode: "payment",
customer: stripeCustomerId,
line_items: [{ price: process.env.STRIPE_ONE_OFF_PRICE_ID, quantity: 1 }],
success_url: "https://your-site.com/thanks",
cancel_url: "https://your-site.com/pricing",
metadata,
payment_intent_data: { metadata },
});
}For a subscription, set metadata on the Session and the Subscription:
export async function createSubscriptionCheckout(stripe, { referralToken, stripeCustomerId }) {
const metadata = referralToken ? { plandalf_referral: referralToken } : {};
return stripe.checkout.sessions.create({
mode: "subscription",
customer: stripeCustomerId,
line_items: [{ price: process.env.STRIPE_RECURRING_PRICE_ID, quantity: 1 }],
success_url: "https://your-site.com/thanks",
cancel_url: "https://your-site.com/pricing",
metadata,
subscription_data: { metadata },
});
}Use your initialized Stripe server client and real success/cancel URLs. Keep Stripe secret keys on the server. Plandalf validates the referral evidence; the browser does not decide the affiliate or commission rate.
Session metadata alone is not automatically copied to all related Stripe objects. subscription_data.metadata puts it on the Subscription, and Stripe snapshots that metadata onto later invoices. Stripe’s metadata rules.
If you create PaymentIntents directly instead of using Stripe Checkout, attach metadata.plandalf_referral to the PaymentIntent. If you create Subscriptions directly, attach it to the Subscription and retain the same Stripe customer ID.
3. Verify a paid sale
- Use the test partner link from Affiliates → Setup, with a fresh test customer.
- Complete a payment through the actual Payment Link or Checkout Session using Stripe test mode.
- Open Affiliates → Sales in test mode. Check the affiliate, amount and commission decision.
- Confirm one payment creates one sale. A checkout-completed page alone is not proof of paid-sale tracking.
- For a subscription, generate a second paid test invoice without clicking the referral link again. Confirm the same affiliate receives the renewal under your subscription policy.
Do not additionally send these payments to the affiliate sales API. Use one reporting path per payment.
Events handled
Stripe source handling covers Checkout Sessions, invoices, PaymentIntents, charges, refunds, credit notes and disputes. Sale-producing objects are linked by Stripe payment identity so a Checkout Session, invoice, PaymentIntent and charge for the same payment create one referred sale, not four.
Subscription Checkout Sessions do not create the sale directly. The paid invoice is the sale source. For a first subscription invoice with no attribution yet, Plandalf waits briefly for the matching subscription Checkout Session. If the invoice was recorded direct first, late checkout evidence can re-attribute it once, before payout. Renewals use the active customer binding and the program’s duration policy.
Plandalf checkout sales on the same Stripe account are deduped by Plandalf metadata so the checkout path remains the source of truth for Plandalf-created checkouts.
Attribution sources
Plandalf checks Stripe evidence including:
client_reference_id, including Payment Links decorated by the SDKmetadata.plandalf_referral- the program compatibility metadata key
- subscription metadata
- customer metadata
- Stripe promotion-code, coupon or code mappings assigned to partners
- existing customer bindings
- direct partner codes where supplied by normalized input
Renewals and money events
Recurring commissions follow the saved duration policy: first payment only, a set period, or ongoing payments. See subscription setup for customer identity, trials, and renewal testing. Renewals use the customer binding while it is active, and the locked binding rate is used for recurring customer decisions.
Refunds, credit notes and disputes are processed as money adjustments:
- Each successful partial refund is recorded by refund ID. Failed or cancelled refunds do not reverse commission.
- Credit notes reverse on an ex-tax basis where Stripe provides one. A voided credit note restores the previous credit-note reversal.
- Open disputes hold unpaid commissions.
- Funds withdrawn from a dispute create a reversal even if the commission was already approved.
- Won disputes and reinstated funds restore the withdrawn amount once.
warning_closedreleases the hold and does not create a loss.
Multi-currency sales are decided into the program payout currency with an FX snapshot. If the exchange rate is missing, the commission waits in a held state until the rate can be resolved.
Test mode and backfill
Stripe livemode is matched to the program’s live/test source settings. Test events stay in test mode.
Backfill runs for every connected Stripe account in the selected mode. It can scan up to 365 days and covers completed paid Checkout Sessions, paid invoices, refunded charges, standalone refunds, credit notes and disputes. Objects are normalized through the same source-event path as live events, derived from current Stripe object state, and checkpointed per account and stream so a failed stream can resume.
Backfill uses the merchant Stripe OAuth token or secret stored on the Stripe integration. If that is missing, Plandalf can fall back to the legacy platform key plus connected-account header; otherwise the backfill fails closed instead of guessing.
Failed and pending Stripe source events are retried by the source-event sweep. Pending adjustments, including refunds, credit notes and disputes that arrived before the matching sale link, are replayed after the sale is linked.