Loading...
Loading...
Guide for implementing discount codes and promotional pricing with Dodo Payments, including CRUD operations, eligibility rules, stacking, subscription-cycle limits, and plan-change preservation.
npx skill4agent add dodopayments/skills discounts-and-promotionsSUMMER2025discount_codespreserve_on_plan_changediscount_codesimport DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
});
// Percentage discount: 15% off
const percentageDiscount = await client.discounts.create({
type: 'percentage',
amount: 1500, // 15%
code: 'SUMMER2025',
metadata: {
campaign: 'summer_promo',
source: 'email_blast'
}
});
// Flat discount: $10 off (1000 cents in currency_options)
const flatDiscount = await client.discounts.create({
type: 'flat',
amount: 1000,
currency_options: [{
currency: 'USD',
max_amount_possible: 1000,
is_default: true,
}],
code: 'WELCOME10',
});const discounts = await client.discounts.list({
page_size: 50,
page_number: 0,
});
// Paginated responses expose `items`.
for (const discount of discounts.items) {
console.log(`${discount.code}: ${discount.type} ${discount.amount}`);
}
// Or let the SDK walk every page for you:
for await (const discount of client.discounts.list()) {
console.log(discount.code);
}const discount = await client.discounts.retrieve('discount_id');
console.log(discount.code, discount.type, discount.amount);try {
const discount = await client.discounts.retrieveByCode('SUMMER2025');
console.log('Discount found:', discount.amount);
} catch (error) {
console.log('Code not found or expired');
}await client.discounts.update('discount_id', {
metadata: { updated_at: new Date().toISOString() }
});await client.discounts.delete('discount_id');flatpercentage1500const discount = await client.discounts.create({
type: 'percentage',
amount: 1500, // 15%
code: 'PERCENT15',
});currency_optionsmax_amount_possible: 1000const discount = await client.discounts.create({
type: 'flat',
amount: 1000, // $10.00
currency_options: [{
currency: 'USD',
max_amount_possible: 1000,
is_default: true,
}],
code: 'FLAT10',
});const discount = await client.discounts.create({
type: 'percentage',
amount: 1500,
code: 'PREMIUM_ONLY',
restricted_to: ['pdt_premium', 'pdt_enterprise'],
});customer_eligibility: 'specific'customer_idsdiscounts.createconst discount = await client.discounts.create({
type: 'percentage',
amount: 1500,
code: 'VIP_ONLY',
customer_eligibility: 'specific',
});
await fetch(
`https://test.dodopayments.com/discounts/${discount.discount_id}/customers`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DODO_PAYMENTS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_ids: ['cus_vip_001', 'cus_vip_002'],
}),
},
);const discount = await client.discounts.create({
type: 'percentage',
amount: 1500,
code: 'SUMMER2025',
starts_at: '2025-06-01T00:00:00Z',
expires_at: '2025-08-31T23:59:59Z',
});const discount = await client.discounts.create({
type: 'percentage',
amount: 1500,
code: 'LIMITED_100',
usage_limit: 100,
});const discount = await client.discounts.create({
type: 'percentage',
amount: 1500,
code: 'FIRST_3_MONTHS',
subscription_cycles: 3, // applies to first 3 billing cycles only
});const session = await client.checkoutSessions.create({
product_cart: [{ product_id: 'pdt_abc', quantity: 1 }],
discount_codes: ['SUMMER2025'],
customer: { email: 'user@example.com' },
return_url: 'https://yoursite.com/return'
});
window.location.href = session.checkout_url;const session = await client.checkoutSessions.create({
product_cart: [{ product_id: 'pdt_abc', quantity: 1 }],
discount_codes: ['WELCOME10', 'BLACKFRIDAY20', 'LOYALTY5'],
customer: { email: 'user@example.com' },
return_url: 'https://yoursite.com/return'
});async function validateDiscount(code, productId) {
try {
const discount = await client.discounts.retrieveByCode(code);
// Check expiration
if (discount.expires_at && new Date(discount.expires_at) < new Date()) {
return { valid: false, reason: 'Code expired' };
}
// Check usage limit
if (discount.usage_limit && discount.times_used >= discount.usage_limit) {
return { valid: false, reason: 'Code exhausted' };
}
// Check product eligibility
if (discount.restricted_to.length > 0) {
if (!discount.restricted_to.includes(productId)) {
return { valid: false, reason: 'Code not valid for this product' };
}
}
return { valid: true, discount };
} catch (error) {
return { valid: false, reason: 'Code not found or unavailable' };
}
}
// Usage
const result = await validateDiscount('SUMMER2025', 'pdt_abc');
if (result.valid) {
console.log('Discount applies:', result.discount.amount);
} else {
console.log('Cannot apply:', result.reason);
}discount_codespreserve_on_plan_changepreserve_on_plan_changediscounts.creatediscounts.updatesubscriptions.changePlan| Scenario | | Discount's | Result |
|---|---|---|---|
| Upgrade with no change | | | Existing discounts carry forward |
| Upgrade with no change | | | Existing discounts are removed |
| Upgrade with no change | | any | Existing discounts are removed |
| Upgrade with replacement | | any | Only |
await client.discounts.update('dsc_existing', {
preserve_on_plan_change: true,
});
await client.subscriptions.changePlan('sub_123', {
product_id: 'pdt_pro',
quantity: 1,
proration_billing_mode: 'prorated_immediately',
// Omit discount_codes. Existing discounts configured with
// preserve_on_plan_change: true carry forward.
});await client.subscriptions.changePlan('sub_123', {
product_id: 'pdt_pro',
quantity: 1,
proration_billing_mode: 'prorated_immediately',
discount_codes: ['UPGRADE20'], // replaces old discounts
});await client.subscriptions.changePlan('sub_123', {
product_id: 'pdt_basic',
quantity: 1,
proration_billing_mode: 'prorated_immediately',
discount_codes: [], // empty array removes all discounts
});// WRONG: client-side only
if (code.length > 0) {
applyDiscount(code);
}
// CORRECT: server-side validation
try {
const discount = await client.discounts.retrieveByCode(code);
if (isEligible(discount)) {
// proceed with checkout
}
} catch (error) {
// Unknown or unavailable code; reject the checkout request.
}// Both codes may not apply if they have conflicting restrictions
const session = await client.checkoutSessions.create({
product_cart: [{ product_id: 'pdt_basic', quantity: 1 }],
discount_codes: ['PREMIUM_ONLY', 'BASIC_ONLY'], // one or both may fail
return_url: 'https://yoursite.com/return'
});discount_codes: []preserve_on_plan_change// WRONG: removes all discounts
await client.subscriptions.changePlan('sub_123', {
product_id: 'pdt_pro',
quantity: 1,
proration_billing_mode: 'prorated_immediately',
discount_codes: [], // this clears discounts
});
// CORRECT: preserves discounts whose preserve_on_plan_change property is true
await client.subscriptions.changePlan('sub_123', {
product_id: 'pdt_pro',
quantity: 1,
proration_billing_mode: 'prorated_immediately',
// omit discount_codes
});// WRONG: assumes code is always valid
const discount = await client.discounts.retrieveByCode(code);
applyDiscount(discount);
// CORRECT: catch unknown codes, then check expiration and usage
try {
const discount = await client.discounts.retrieveByCode(code);
if (discount.expires_at && new Date(discount.expires_at) < new Date()) {
showError('Code expired');
} else if (discount.usage_limit && discount.times_used >= discount.usage_limit) {
showError('Code exhausted');
} else {
applyDiscount(discount);
}
} catch (error) {
showError('Code not found');
}subscription_cyclesexpires_at// Applies to first 3 billing cycles of any subscription
const discount = await client.discounts.create({
type: 'percentage',
amount: 1500,
code: 'FIRST_3_MONTHS',
subscription_cycles: 3,
});
// Expires on a specific date, regardless of billing cycles
const discount2 = await client.discounts.create({
type: 'percentage',
amount: 1500,
code: 'SUMMER_ONLY',
expires_at: '2025-08-31T23:59:59Z',
});