executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
localized-pricingDodo Payments multi-currency pricing, covering fixed localized prices per country or currency, Adaptive Currency live FX conversion at checkout, and purchasing power parity (PPP) percentages. Use when pricing in INR, EUR, or other local currencies, offering regional or PPP discounts, or resolving billing country and currency fallback.
Independent GitHub snapshot · Automated classification · Original documentation governs
This skill covers three distinct mechanisms for pricing in multiple countries and currencies. Developers often confuse them. This skill disambiguates them, shows how to implement each, and explains their interactions with plan changes, proration, and tax.
| Mechanism | What it does | Who controls it | When prices change |
|---|---|---|---|
| Localized Pricing | Developer sets fixed prices per country/currency | You (via API) | Only when you update them |
| Adaptive Currency | Live FX conversion at checkout time | Dodo (live rates) | Every transaction, based on current rates |
| Purchasing Power Parity (PPP) | Charges a per-country percentage of the base price | You (business-wide country percentages in the dashboard, per-product opt-in) | Only when you change the percentages |
Key distinction: Localized prices are static. Adaptive currency is dynamic. PPP is a native per-country percentage applied to the base price before Adaptive Currency converts it.
Set fixed prices for specific countries and currencies. Once created, a localized price does not change unless you update it. It does not track exchange rates.
Use localized pricing when you want predictable, region-specific pricing that you control.
Enable live foreign-exchange conversion at checkout. The customer sees their local currency, and Dodo converts the base price using current rates.
Use adaptive currency when you want to offer checkout in any currency without manually setting prices for each one.
PPP is native (docs). Dodo keeps one business-wide percentage per country (30 to 100, managed in the dashboard only), and the customer pays that share of your list price based on their billing country. Each product opts in with price.purchasing_power_parity: true (default false).
PPP requires Adaptive Currency: enable it under Settings → Business first, or product opt-in is rejected. A matching localized price always wins over PPP, and discount codes apply to the PPP-reduced price.
await client.products.create({
name: 'Starter Plan',
tax_category: 'saas',
price: {
type: 'one_time_price',
currency: 'USD',
price: 2000, // $20.00; a customer in India at 30% pays $6.00
discount: 0,
purchasing_power_parity: true,
},
});
Every product has a pricing_mode that determines how prices are resolved:
by_country: Dodo looks up a localized price by the customer's billing country.by_currency: Dodo looks up a localized price by the customer's billing currency.Set the mode once per product via client.products.update(...). You cannot mix modes on the same product.
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
});
// Set pricing mode to by_country
await client.products.update('pdt_premium_plan', {
pricing_mode: 'by_country',
});
// Or by_currency
await client.products.update('pdt_premium_plan', {
pricing_mode: 'by_currency',
});
Localized prices are nested under products. All amounts are in the smallest currency unit (cents for USD, paise for INR).
// ₹999.00 for customers in India
const localizedPrice = await client.products.localizedPrices.create('pdt_premium_plan', {
currency: 'INR',
country_code: 'IN',
amount: 99900, // 999.00 in paise
});
const prices = await client.products.localizedPrices.list('pdt_premium_plan');
console.log(prices);
const price = await client.products.localizedPrices.retrieve('lp_abc123', {
product_id: 'pdt_premium_plan',
});
console.log(price.amount, price.currency, price.country_code);
await client.products.localizedPrices.update('lp_abc123', {
product_id: 'pdt_premium_plan',
amount: 109900, // Update to ₹1099.00
});
await client.products.localizedPrices.archive('lp_abc123', {
product_id: 'pdt_premium_plan',
});
Archived prices are soft-deleted. They no longer apply to new checkouts but remain in your history.
Enable Adaptive Currency in the dashboard under Settings → Business (docs). Dodo then converts the base product price at live rates. Passing billing_currency and billing_address.country on a checkout session only pins the currency you charge in; if you omit them, Adaptive Currency picks the currency and country from the customer's IP address.
// Server route (e.g. POST /api/checkout): create the session and return its URL
export async function POST() {
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'pdt_premium_plan',
quantity: 1,
},
],
billing_currency: 'AED', // Pin the charge currency (requires Adaptive Currency)
billing_address: { country: 'AE' },
return_url: 'https://example.com/return',
});
return Response.json({ checkout_url: session.checkout_url });
}
Never call the SDK or expose the API key in browser code. The browser only receives the URL and redirects:
// Browser
const res = await fetch('/api/checkout', { method: 'POST' });
const { checkout_url } = (await res.json()) as { checkout_url: string };
window.location.href = checkout_url;
Fees: Adaptive Currency adds a tiered conversion fee (4% under $500, 3% from $500 to $1,500, 2% above $1,500) on top of the displayed price by default. Turn on Fees Inclusive in Settings → Business to absorb the fee from your settlement instead. When a localized price matches, the transaction is always treated as fees-inclusive.
Charm Pricing: With Adaptive Currency on, Charm Pricing can round the converted price to a clean ending such as 49.99. It requires Adaptive Currency.
Adaptive currency is also available on subscription creation and plan changes. Check the API reference for billing_currency support on the specific endpoint you're using.
Dodo resolves the customer's billing country and currency in this order:
billing_address.country or billing_currency passed to checkout.If no localized price exists for the resolved country or currency, Dodo falls back to the base price. This fallback is automatic and does not raise an error.
When a customer changes plans, localized prices apply to the new plan using the same billing country or currency as the original subscription.
Proration is calculated using the new plan's localized price (if one exists for the customer's country/currency).
await client.subscriptions.changePlan('sub_abc123', {
product_id: 'pdt_enterprise_plan',
quantity: 1,
proration_billing_mode: 'prorated_immediately',
});
The new plan's localized price is used for the prorated amount.
Tax is calculated on the localized price, not the base price. If you set a localized price of ₹999.00 for India, tax is applied to that amount.
Tax-inclusive pricing works the same way: the localized price is treated as the tax-inclusive total.
Localized prices are fixed. If you set INR 999.00 today, it remains 999.00 until you manually update it. It does not track USD/INR rates.
If you want live FX conversion, use adaptive currency instead.
by_country and by_currencyby_country: Dodo matches the customer's billing country to a localized price's country_code.by_currency: Dodo matches the customer's billing currency to a localized price's currency.A customer in India using a USD card will match by_currency: USD but not by_country: IN (unless you also set a USD price for India).
If you set pricing_mode: by_country but don't create a localized price for a customer's country, Dodo falls back to the base price: charged directly if the customer is already in your base currency, or converted through Adaptive Currency when that is enabled (docs). Without Adaptive Currency, that is the base price in the base currency, which is usually not what you want.
Always create localized prices for your target markets before enabling the mode.
You can use both on the same product, but they serve different purposes. Localized pricing is for fixed, region-specific prices. Adaptive currency is for live FX conversion.
If you set a localized price for India and also enable adaptive currency, the localized price takes precedence for customers in India.
PPP is native: opt products in with price.purchasing_power_parity: true and set country percentages in the dashboard (Adaptive Currency must be on). Third-party tools like ParityDeals are only an alternative if you want their site banners; their codes are passed as ordinary discount_codes at checkout.
Always test your fallback behavior. If a customer's country or currency has no localized price, they see the base price. Decide whether that's acceptable or whether you need to block checkout for unsupported regions.