Skip to content

feat: Add Coupons Migration to Lemon Squeezy Provider - #7

Merged
sancho1952007 merged 12 commits into
dodopayments:mainfrom
yashranaway:lemonsqueezy-coupons-feature
Oct 21, 2025
Merged

sancho1952007 merged 12 commits into
dodopayments:mainfrom
yashranaway:lemonsqueezy-coupons-feature

Conversation

@yashranaway

@yashranaway yashranaway commented Sep 26, 2025 •

Copy link
Copy Markdown
Contributor

fixes #4
Added Lemon Squeezy migration support for products, coupons, and customers
Implemented interactive CLI with progress tracking
Added comprehensive documentation in /docs/lemonsqueezy
Updated README with Lemon Squeezy support status

Summary by CodeRabbit

  • New Features

    • Interactive prompts to choose which data types to migrate (products, coupons)
    • New --types CLI option to specify migration scope
    • Brand selection prompt added for Dodo Payments
    • Improved migration reporting with consolidated outcome categories
  • Documentation

    • Added coupon migration documentation (percentage-based discounts supported, fixed-amount discounts skipped)

@yashranaway

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Sep 27, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Sep 27, 2025 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

The changes implement on-demand coupon migration from Lemon Squeezy to Dodo Payments, adding interactive checkbox selection for migration types (products/coupons) and refactoring payload construction to use runtime currency resolution per store instead of pre-defined pipelines. Product migration is also restructured to align with the new on-demand pattern.

Changes

Cohort / File(s) Summary
Core Migration Logic
src/providers/lemonsqueezy/index.ts
Replaces pre-defined data pipelines with on-demand migration flow; adds --types CLI option and interactive checkbox prompts for selecting migration scopes (products/coupons); introduces getProduct import and checkbox from @inquirer/prompts; adds interactive brand selection fallback; implements per-store currency caching for pricing resolution; introduces separate on-demand product and coupon migration pathways with runtime payload construction; consolidates final reporting by completed categories rather than per-entity summaries; adjusts control flow for interactive stepwise execution with early exits on failures.
Documentation
docs/lemonsqueezy/README.md
Adds coupon migration documentation specifying that only published coupons and percentage-based discounts are migrated; fixed-amount discounts are skipped with warnings; documents preservation of expiration dates, usage limits, and codes/names.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as CLI/Main
    participant Selector as Type Selector
    participant Store as Store Fetch
    participant ProdMgr as Product Migrator
    participant CouponMgr as Coupon Migrator
    participant DodoAPI as Dodo Payments
    participant Report as Reporting

    User->>CLI: Invoke with --types
    CLI->>Selector: Interactive checkbox prompt
    Selector-->>CLI: Selected types (products, coupons)
    
    rect rgb(220, 240, 255)
        Note over CLI: Setup Phase
        CLI->>Store: Fetch store & cache currency
        Store-->>CLI: Store data + currency
    end
    
    alt Products selected
        rect rgb(240, 255, 240)
            Note over CLI: Product Migration
            CLI->>ProdMgr: Fetch products at runtime
            ProdMgr->>ProdMgr: Resolve currency per store
            ProdMgr->>ProdMgr: Build Dodo Payments payloads
            ProdMgr-->>CLI: Product payloads
            CLI->>CLI: Confirm with user
            CLI->>DodoAPI: Migrate products
            DodoAPI-->>Report: Success/failure per product
        end
    end
    
    alt Coupons selected
        rect rgb(255, 245, 230)
            Note over CLI: Coupon Migration
            CLI->>CouponMgr: Fetch discounts
            CouponMgr->>CouponMgr: Filter published, percentage-based
            CouponMgr->>CouponMgr: Resolve currency, build payloads
            CouponMgr-->>CLI: Coupon payloads
            CLI->>CLI: Confirm with user
            CLI->>DodoAPI: Migrate coupons
            DodoAPI-->>Report: Success/failure per coupon
        end
    end
    
    CLI->>Report: Consolidate results by category
    Report-->>User: Final summary (migrated categories)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The changes introduce substantial new migration pathways (coupon support), add interactive user flows with new dependencies (@inquirer/prompts), and significantly restructure existing product migration logic. Multiple new control paths, currency handling logic, and reworked reporting require careful verification across distinct feature areas.

Possibly related PRs

Poem

🐰 Hop, skip, and click through checkboxes bright,
Coupons and products in interactive flight!
From Lemon to Dodo, currencies align,
Per-store, on-demand—a migration so fine!
No pipelines pre-set, just what you select,
Runtime flows dance, every detail checked! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The linked issue #4 specifically addresses coupon migration for Lemon Squeezy only. However, the PR introduces substantial out-of-scope changes including: product migration for Lemon Squeezy, customer migration for Lemon Squeezy, interactive brand selection and enhanced CLI features, and comprehensive refactoring of the Stripe provider with pagination flows for products, prices, coupons, and customers. These changes extend significantly beyond the scope of the single linked issue and appear to be additional feature implementations or refactoring work not covered by the provided issue requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: Add Coupons Migration to Lemon Squeezy Provider" clearly refers to a significant and real part of the changeset. The title accurately captures the coupon migration feature implementation for the Lemon Squeezy provider, which is a core component of the work. While the PR also includes additional changes such as product migration, customer migration for Lemon Squeezy, and Stripe provider refactoring, the title appropriately highlights a major aspect of the implementation and is specific and clear enough for teammates to understand the primary feature being added.
Linked Issues Check ✅ Passed Issue #4 requires implementing coupon migration functionality to move coupons from Lemon Squeezy to Dodo Payments. The changeset introduces an "on-demand coupon migration path" for the Lemon Squeezy provider that loads discounts, filters published ones, resolves currency appropriately, builds coupon payloads, prompts for user confirmation, and executes the migration with per-item logging. This implementation directly addresses the core requirement of providing coupon migration support from Lemon Squeezy to Dodo Payments as specified in the linked issue.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1252e62 and eaa1166.

📒 Files selected for processing (1)
  • src/providers/lemonsqueezy/index.ts (5 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
docs/stripe/README.md (1)

3-6: Fix Markdown heading and fenced block formatting

markdownlint is flagging the #### Usage: heading jump and the unlabeled code fence right below it. Bumping the heading to ## and tagging the snippet as bash resolves both issues without changing meaning.

-#### Usage:
-```
-dodo-migrate stripe
-```
+## Usage
+```bash
+dodo-migrate stripe
+```
README.md (1)

57-66: Add fenced code languages to satisfy markdownlint (MD040).

Mark the CLI snippets as console/bash for readability and lint compliance.

Apply:

-```
+```console
 dodo-migrate lemonsqueezy

- +console
dodo-migrate stripe

...
-```
+```bash
dodo-migrate lemonsqueezy \
  --provider-api-key=lsq_XXXXXXXXXXXXXXXX \
  --dodo-api-key=dp_XXXXXXXXXXXXXXXX \
  --mode=test_mode \
  --dodo-brand-id=brand_XXXXXX

- +bash
dodo-migrate stripe
--provider-api-key=sk_test_XXXXXXXXXXXXXXXX
--dodo-api-key=dp_XXXXXXXXXXXXXXXX
--mode=test_mode
--dodo-brand-id=brand_XXXXXX
--migrate-types=products,coupons

Also applies to: 100-104, 106-119

src/providers/stripe/index.ts (4)

169-171: Subscription cadence mapping is incomplete.

Interval_count (e.g., every 3 months) and weekly/daily intervals are not handled and default to monthly, potentially wrong in target.

Would you like me to add an interval mapper (handles day/week/month/year + interval_count) and confirm what Dodo accepts for billing_period? Based on learnings.


209-215: Rename local variable to avoid shadowing the function name.

Improves readability.

Apply:

-        const migrateProducts = await select({
+        const proceedProducts = await select({
             message: 'Proceed to create these products in Dodo Payments?',
             choices: [
                 { name: 'Yes', value: 'yes' },
                 { name: 'No', value: 'no' }
             ],
         });
 
-        if (migrateProducts === 'yes') {
+        if (proceedProducts === 'yes') {

Also applies to: 217-230


299-305: Rename local variable to avoid shadowing the function name.

Apply:

-        const migrateCoupons = await select({
+        const proceedCoupons = await select({
             message: 'Proceed to create these coupons in Dodo Payments?',
             choices: [
                 { name: 'Yes', value: 'yes' },
                 { name: 'No', value: 'no' }
             ],
         });
 
-        if (migrateCoupons === 'yes') {
+        if (proceedCoupons === 'yes') {

Also applies to: 307-321


380-386: Rename local variable to avoid shadowing the function name.

Apply:

-        const migrateCustomers = await select({
+        const proceedCustomers = await select({
             message: 'Proceed to create these customers in Dodo Payments?',
             choices: [
                 { name: 'Yes', value: 'yes' },
                 { name: 'No', value: 'no' }
             ],
         });
 
-        if (migrateCustomers === 'yes') {
+        if (proceedCustomers === 'yes') {

Also applies to: 388-402

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1bd8c9f and 3196e6e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • README.md (3 hunks)
  • docs/lemonsqueezy/README.md (2 hunks)
  • docs/stripe/README.md (1 hunks)
  • package.json (1 hunks)
  • src/index.ts (2 hunks)
  • src/providers/lemonsqueezy/index.ts (4 hunks)
  • src/providers/stripe/index.ts (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
docs/stripe/README.md

3-3: Heading levels should only increment by one level at a time
Expected: h2; Actual: h4

(MD001, heading-increment)


4-4: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


14-14: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


15-15: Table pipe style
Expected: leading_only; Actual: no_leading_or_trailing; Missing leading pipe

(MD055, table-pipe-style)


94-94: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


98-98: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


102-102: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


107-107: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

README.md

63-63: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


96-96: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


101-101: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


106-106: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (3)
docs/lemonsqueezy/README.md (1)

12-24: Clear, actionable docs for --types (LGTM).

Examples and defaults are unambiguous and match the described behavior.

src/index.ts (1)

6-7: Approve Stripe provider registration
Dependencies (stripe, @inquirer/prompts, dodopayments) are listed in package.json and docs/stripe/README.md and docs/lemonsqueezy/README.md exist.

src/providers/stripe/index.ts (1)

56-58: Retain explicit API version
‘2025-02-24.acacia’ is a valid Stripe API version string for stripe-node v17; pinning it ensures consistent API behavior across environments.

Likely an incorrect or invalid review comment.

Comment thread src/providers/stripe/index.ts Resolved
Comment thread src/providers/stripe/index.ts Resolved
Comment thread src/providers/stripe/index.ts Resolved
Comment thread src/providers/stripe/index.ts Resolved
@yashranaway

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@yashranaway yashranaway reopened this Sep 27, 2025
@coderabbitai

coderabbitai Bot commented Sep 27, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3196e6e and c84cf69.

📒 Files selected for processing (2)
  • src/providers/lemonsqueezy/index.ts (4 hunks)
  • src/providers/stripe/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/providers/stripe/index.ts

Comment thread src/providers/lemonsqueezy/index.ts Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c84cf69 and e5de0e5.

📒 Files selected for processing (1)
  • src/providers/lemonsqueezy/index.ts (4 hunks)

Comment thread src/providers/lemonsqueezy/index.ts Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5de0e5 and f222975.

📒 Files selected for processing (1)
  • src/providers/lemonsqueezy/index.ts (4 hunks)

Comment thread src/providers/lemonsqueezy/index.ts Resolved
@yashranaway
yashranaway deleted the lemonsqueezy-coupons-feature branch September 27, 2025 11:46
@yashranaway
yashranaway restored the lemonsqueezy-coupons-feature branch October 18, 2025 09:59
@yashranaway yashranaway reopened this Oct 18, 2025
@sancho1952007

Copy link
Copy Markdown
Contributor

Facing some issue
Screenshot 2025-10-20 at 8 48 31 PM

@yashranaway

Copy link
Copy Markdown
Contributor Author

Facing some issue Screenshot 2025-10-20 at 8 48 31 PM

resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/providers/stripe/index.ts (3)

3-3: Mask API keys; use password prompts instead of input.

Secrets are echoed to the terminal. Switch to password prompts and add validation.

-import { input, select, checkbox } from '@inquirer/prompts';
+import { input, select, checkbox, password } from '@inquirer/prompts';

-const PROVIDER_API_KEY = argv['provider-api-key'] || await input({ 
-  message: 'Enter your Stripe Secret API Key (sk_...):', 
-  required: true 
-});
+const PROVIDER_API_KEY = argv['provider-api-key'] || await password({
+  message: 'Enter your Stripe Secret API Key (sk_...):',
+  mask: true,
+  validate: (v) => v?.trim().startsWith('sk_') ? true : 'Please provide a valid Stripe secret key'
+});

-const DODO_API_KEY = argv['dodo-api-key'] || await input({ 
-  message: 'Enter your Dodo Payments API Key:', 
-  required: true 
-});
+const DODO_API_KEY = argv['dodo-api-key'] || await password({
+  message: 'Enter your Dodo Payments API Key:',
+  mask: true,
+  validate: (v) => v?.trim() ? true : 'API key is required'
+});

Also consider replacing required: true with a validate function across prompts. [Based on learnings]

Also applies to: 39-47


168-186: Handle weekly/daily Stripe intervals or skip unsupported ones.

Currently defaults to monthly for non-year/month intervals; this mislabels weekly/daily plans.

- price: {
-   currency: price.currency.toUpperCase(),
-   price: price.unit_amount as number,
-   discount: 0,
-   purchasing_power_parity: false,
-   type: 'recurring_price',
-   billing_period: price.recurring?.interval === 'month' ? 'monthly' : 
-                  price.recurring?.interval === 'year' ? 'yearly' : 'monthly'
- },
+ // Map Stripe interval -> Dodo billing_period; skip unsupported.
+ const interval = price.recurring?.interval;
+ const billingMap: Record<string, 'daily' | 'weekly' | 'monthly' | 'yearly'> = {
+   day: 'daily',
+   week: 'weekly',
+   month: 'monthly',
+   year: 'yearly',
+ };
+ const billingPeriod = interval ? billingMap[interval] : undefined;
+ if (!billingPeriod) {
+   console.log(`[LOG] Skipping price ${price.id} - unsupported interval: ${interval}`);
+   continue;
+ }
+ price: {
+   currency: price.currency.toUpperCase(),
+   price: price.unit_amount as number,
+   discount: 0,
+   purchasing_power_parity: false,
+   type: 'recurring_price',
+   billing_period: billingPeriod
+ },

If Dodo only accepts monthly/yearly, log and skip day/week instead.


56-58: I'll verify the Stripe API version validity by searching for current Stripe documentation.

Let me search for specific information about the Node.js Stripe SDK's apiVersion parameter format.

Let me verify if 2025-02-24 was an actual Stripe API version release.

Based on my verification, I can now provide the corrected review comment.


Remove the .acacia suffix from the apiVersion string; Node.js SDK expects YYYY-MM-DD format.

For the Node.js Stripe SDK, the apiVersion parameter should use the format YYYY-MM-DD (e.g., 2025-02-24), not 2025-02-24.acacia. While 2025-02-24.acacia is a valid API version supported by webhook endpoints, the .codename suffix was historically used for Stripe.js preview features but is no longer supported.

The current Stripe API version is 2025-09-30.clover. If you want to pin to a specific stable version, use 2025-02-24 alone. Otherwise, omit apiVersion to use your account's default.

const stripe = new Stripe(PROVIDER_API_KEY, {
-  apiVersion: '2025-02-24.acacia',
+  apiVersion: '2025-02-24',
});
♻️ Duplicate comments (2)
src/providers/stripe/index.ts (2)

276-281: Promotion codes: add active filter and paginate; otherwise you’ll miss >100 and include disabled codes.

Use active: true and autoPagingEach (or autoPagingToArray) to iterate all promotion codes.

-// Fetch promotion codes for this coupon
-const promotionCodes = await stripe.promotionCodes.list({
-  coupon: coupon.id,
-  limit: 100
-});
+// Fetch all active promotion codes for this coupon
+const promoCodes: Stripe.PromotionCode[] = [];
+await stripe.promotionCodes
+  .list({ coupon: coupon.id, active: true, limit: 100 })
+  .autoPagingEach((pc) => { promoCodes.push(pc); });

And replace subsequent uses of promotionCodes.data with promoCodes.


312-341: Use promotion code–specific fields (expires_at, max_redemptions, name) when mapping; don’t reuse coupon-level values.

For each promotion code: prefer promotionCode.expires_at and promotionCode.max_redemptions, and default name to coupon.name || promotionCode.code.

- for (const promotionCode of promotionCodes.data) {
+ for (const promotionCode of promoCodes) {
   // ... discountType/discountValue derived from coupon (unchanged)
-  const currency = coupon.amount_off ? coupon.currency?.toUpperCase() : undefined;
+  const currency = coupon.amount_off ? coupon.currency?.toUpperCase() : undefined;

   CouponsToMigrate.push({
-    code: promotionCode.code,
-    name: coupon.name || coupon.id,
+    code: promotionCode.code,
+    name: coupon.name || promotionCode.code,
     discount_type: discountType,
     amount: discountValue,
     currency: currency,
-    usage_limit: coupon.max_redemptions || null,
-    expires_at: coupon.redeem_by ? new Date(coupon.redeem_by * 1000).toISOString() : null,
+    usage_limit: promotionCode.max_redemptions ?? null,
+    expires_at: promotionCode.expires_at
+      ? new Date(promotionCode.expires_at * 1000).toISOString()
+      : null,
     brand_id: brand_id
   });
 }
🧹 Nitpick comments (5)
src/providers/lemonsqueezy/index.ts (4)

35-39: Add validation for the --types option.

The --types option currently accepts any comma-separated string without validation. If a user provides invalid types (e.g., --types=foo,bar), the script will run but migrate nothing, which may confuse users.

Consider validating the types after parsing in the handler:

// After line 67, add validation
if (migrationTypes.length === 0) {
    console.log('[ERROR] No migration types selected. Please select at least one type.');
    process.exit(1);
}

const validTypes = ['products', 'coupons'];
const invalidTypes = migrationTypes.filter(type => !validTypes.includes(type));
if (invalidTypes.length > 0) {
    console.log(`[ERROR] Invalid migration types: ${invalidTypes.join(', ')}. Valid types are: ${validTypes.join(', ')}`);
    process.exit(1);
}

296-308: Enhance error handling for failed coupon migrations.

The current error handling stringifies the entire error object, which may not provide clear information to users. If a coupon fails due to validation errors (e.g., missing currency, duplicate code, invalid format), users would benefit from more specific error messages.

Consider parsing and displaying structured error information:

                         } catch (error) {
-                            console.log(`[ERROR] Failed to migrate coupon: ${coupon.data.name} - ${error}`);
+                            console.log(`[ERROR] Failed to migrate coupon: ${coupon.data.name}`);
+                            if (error && typeof error === 'object' && 'message' in error) {
+                                console.log(`[ERROR] Reason: ${error.message}`);
+                            } else {
+                                console.log(`[ERROR] Reason: ${error}`);
+                            }
                             failureCount++;
                         }

166-166: Address type assertions for currency fields.

Multiple as any type assertions are used when assigning currency values from Lemon Squeezy to Dodo Payments (Lines 166, 238, 252). These assertions bypass TypeScript's type safety and may hide actual type incompatibilities between the two APIs.

Investigate the actual types returned by the Lemon Squeezy SDK and expected by Dodo Payments. Consider:

  1. Creating a type mapping function if the currency formats differ
  2. Adding runtime validation to ensure currency values are valid
  3. Using proper type assertions with validation rather than as any

Example approach:

type LemonSqueezyCurrency = string; // actual type from SDK
type DodoPaymentsCurrency = string; // actual type from Dodo API

function mapCurrency(lsCurrency: LemonSqueezyurrency | undefined): DodoPaymentsCurrency | undefined {
    // Add validation/mapping logic if needed
    return lsCurrency;
}

// Usage
currency: mapCurrency(StoreData.data.attributes.currency)

Also applies to: 238-238, 252-252


236-254: Remove the unnecessary initial check for discount.attributes.currency — the Lemon Squeezy API does not return a currency field on discount objects.

The Lemon Squeezy Discount object does not include a currency field. The code at line 238 using (discount as any).attributes.currency will always be undefined, making the initial check dead code. Fixed amounts are stored as cents and currency is implied by the store/order, so the store data fallback (lines 240–253) will always be needed.

Simplify by removing the unnecessary type assertion and initial check:

                let fixedCurrency: string | undefined = undefined;
                if (discount.attributes.amount_type === 'fixed') {
-                    fixedCurrency = (discount as any).attributes.currency;
-                    if (!fixedCurrency) {
                         // Fallback: use cached store currency or fetch and cache the store if not present
                         if (!StoresData[discount.attributes.store_id]) {
                             // ... fetch store
                         }
                         fixedCurrency = StoresData[discount.attributes.store_id]?.data.attributes.currency as any;
-                    }
                 }
src/providers/stripe/index.ts (1)

161-167: unit_amount guard is good; drop fallback “|| 0” to avoid masking issues.

You already skip null amounts; prefer using the non-null value directly to prevent accidental zeros.

- price: price.unit_amount || 0,
+ price: price.unit_amount as number,

Apply to both one-time and recurring blocks.

Also applies to: 176-177, 196-197

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f222975 and 0ef456b.

📒 Files selected for processing (2)
  • src/providers/lemonsqueezy/index.ts (4 hunks)
  • src/providers/stripe/index.ts (6 hunks)
🔇 Additional comments (9)
src/providers/lemonsqueezy/index.ts (6)

112-120: Good addition of completion tracking flags.

The completion tracking flags (completedProducts and completedCoupons) properly address the previous review concern about misleading completion summaries. The final message will now accurately reflect what was actually migrated.


122-211: Products migration implementation looks solid.

The products migration flow is well-structured with proper error handling, store data caching to prevent rate limiting, detailed logging, and user confirmation before execution. The completion flag is set only after successful migration.


255-265: Amount handling is correct.

The code correctly passes the raw amount value for both percentage and fixed-amount coupons without dividing by 100, which aligns with Dodo Payments API expectations (minor-unit values). The division by 100 on Line 275 is only for display purposes, which is appropriate.


290-318: Completion tracking logic is strict but appropriate.

The code only sets completedCoupons = true when all coupons migrate successfully (failureCount === 0). This strict approach ensures that the final completion message accurately reflects a fully successful migration, while partial successes are clearly reported separately. This design is appropriate for a migration tool where users need to know if any items failed.


327-334: Accurate completion message implementation.

The final completion message correctly uses the completedProducts and completedCoupons flags to report only what was actually migrated, addressing the previous review concern about misleading summaries.


59-66: ****

The required property is a valid and supported option in @inquirer/checkbox that ensures at least one choice must be selected. The code correctly prevents empty selections with required: true. No validation code is needed.

Likely an incorrect or invalid review comment.

src/providers/stripe/index.ts (3)

128-135: Nice: full pagination for products and prices.

Using autoPagingEach to collect all products and per-product prices addresses missed pages.

Also applies to: 146-155


252-259: Good: coupon pagination implemented.

Collecting all coupons avoids truncation at 100.


404-410: Customers: pagination and address cleanup look solid.

Good handling of deleted customers and omission of empty address objects.

Also applies to: 427-457

Comment thread src/providers/stripe/index.ts Resolved
@sancho1952007

Copy link
Copy Markdown
Contributor

Please only accept discounts that are percentage only. Ignore discounts that are not percentage based. This is because the Dodo Payments SDK only supports creating percentage discounts at the moment.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/providers/lemonsqueezy/index.ts (3)

1-1: Remove unused import.

getProduct is imported but never referenced in the code.

Apply this diff:

-import { listProducts, lemonSqueezySetup, getProduct, getStore, Store, listDiscounts } from '@lemonsqueezy/lemonsqueezy.js';
+import { listProducts, lemonSqueezySetup, getStore, Store, listDiscounts } from '@lemonsqueezy/lemonsqueezy.js';

235-254: Currency resolution logic is solid.

The fallback to store currency when discount.attributes.currency is unavailable ensures fixed-amount coupons always have currency defined. The caching prevents redundant API calls.

Consider removing the type assertion on Line 238 for better type safety:

-                    fixedCurrency = (discount as any).attributes.currency;
+                    fixedCurrency = discount.attributes.currency;

If TypeScript complains, verify the @lemonsqueezy/lemonsqueezy.js types include currency on discount attributes for fixed amounts, or add a proper type guard.


259-260: Clarify the normalization comment.

The comment implies all amounts are multiplied by 100, but only percentage discounts are transformed (to basis points for Dodo). Fixed amounts remain in minor units.

Apply this diff:

-                    // * 100 to normalize it to Dodo Payments expected format
-                    amount: discount.attributes.amount_type === 'percent' ? discount.attributes.amount * 100 : discount.attributes.amount,
+                    // For percentage: convert to basis points (e.g., 10% → 1000). For fixed: keep minor units as-is.
+                    amount: discount.attributes.amount_type === 'percent' ? discount.attributes.amount * 100 : discount.attributes.amount,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ef456b and 134e999.

📒 Files selected for processing (1)
  • src/providers/lemonsqueezy/index.ts (5 hunks)
🔇 Additional comments (4)
src/providers/lemonsqueezy/index.ts (4)

54-67: LGTM!

The migration type selection logic elegantly handles both CLI and interactive modes with sensible defaults.


293-324: Excellent error handling and reporting.

The per-coupon try-catch ensures one failure doesn't abort the entire migration, and the success/failure tallies provide clear feedback. Setting completedCoupons only when all succeed is the right choice for the final summary.


330-337: LGTM!

The completion summary now correctly reflects actual migration outcomes by checking the boolean flags set during each successful migration branch.


126-130: Based on my research, I found that the Lemon Squeezy API uses page-based pagination for all "list" endpoints with a default page size of 10 items.

While the official Lemon Squeezy JavaScript SDK (@lemonsqueezy/lemonsqueezy.js) documentation does not clearly specify whether listProducts() and listDiscounts() auto-paginate or only return the first page, the SDK examples and tutorials consistently show single function calls without pagination loops. The underlying API architecture requires manual pagination handling—developers must check for links.next to fetch additional pages.

Your original review comment is valid. The code at lines 126-130 calls listProducts() without any pagination parameters, which means if the account has more than 10 products (the default page size), only the first page will be migrated. The same concern applies to listDiscounts() at line 218.

Add pagination handling to fetch all products and discounts.

The Lemon Squeezy API uses page-based pagination for all "list" endpoints. Implement pagination loops using the response's links.next or manually specify page[number] and page[size] parameters to ensure all products and discounts are migrated, not just the first page.

Comment thread src/providers/lemonsqueezy/index.ts Resolved
@yashranaway
yashranaway force-pushed the lemonsqueezy-coupons-feature branch from 134e999 to 1252e62 Compare October 21, 2025 08:22

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/providers/lemonsqueezy/index.ts (1)

1-1: Remove unused getProduct import.

The getProduct function is imported but never used in the code.

Apply this diff:

-import { listProducts, lemonSqueezySetup, getProduct, getStore, Store, listDiscounts } from '@lemonsqueezy/lemonsqueezy.js';
+import { listProducts, lemonSqueezySetup, getStore, Store, listDiscounts } from '@lemonsqueezy/lemonsqueezy.js';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 134e999 and 1252e62.

📒 Files selected for processing (2)
  • docs/lemonsqueezy/README.md (1 hunks)
  • src/providers/lemonsqueezy/index.ts (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/lemonsqueezy/README.md
🔇 Additional comments (3)
src/providers/lemonsqueezy/index.ts (3)

35-39: LGTM! Well-designed CLI option and interactive flow.

The --types option with fallback to interactive checkbox selection provides good UX flexibility. The defaults (both checked) and required validation are sensible.

Also applies to: 54-67


122-211: LGTM! Products migration flow is well-structured.

The on-demand fetching with store caching, clear preview formatting, user confirmation, and per-item logging provide a solid migration experience. Price handling correctly preserves minor units.


236-236: LGTM! Percentage-only filtering correctly implements requirements.

The filter to process only amount_type === 'percent' discounts and skip fixed-amount discounts with a clear warning aligns with the stakeholder requirement (issue #4 discussion) that Dodo Payments SDK currently supports percentage discounts only.

Also applies to: 249-251

Comment thread src/providers/lemonsqueezy/index.ts Resolved
Comment thread src/providers/lemonsqueezy/index.ts Resolved
Comment thread src/providers/lemonsqueezy/index.ts Resolved
@sancho1952007

Copy link
Copy Markdown
Contributor

Thank you for your contribution!

@sancho1952007
sancho1952007 merged commit 8c180b6 into dodopayments:main Oct 21, 2025
1 check was pending
@yashranaway
yashranaway deleted the lemonsqueezy-coupons-feature branch October 21, 2025 08:48
@coderabbitai coderabbitai Bot mentioned this pull request Dec 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add cupons migration to Lemon Squeezy

2 participants