Skip to content

Commit f38e29f

Browse files
authored
Merge pull request #864 from useautumn/fix/discounts-on-update
fix: discounts on update
2 parents 4d8c469 + 50f0257 commit f38e29f

File tree

8 files changed

+761
-388
lines changed

8 files changed

+761
-388
lines changed

server/src/internal/billing/v2/providers/stripe/actionBuilders/buildStripeCheckoutSessionAction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { msToSeconds, orgToReturnUrl } from "@autumn/shared";
77
import type Stripe from "stripe";
88
import type { AutumnContext } from "@/honoUtils/HonoEnv";
99
import { buildStripeCheckoutSessionItems } from "@/internal/billing/v2/providers/stripe/utils/checkoutSessions/buildStripeCheckoutSessionItems";
10-
import { stripeDiscountsToParams } from "@/internal/billing/v2/providers/stripe/utils/discounts/stripeDiscountsToParams";
10+
import { stripeDiscountsToCheckoutParams } from "@/internal/billing/v2/providers/stripe/utils/discounts/stripeDiscountsToParams";
1111

1212
export const buildStripeCheckoutSessionAction = ({
1313
ctx,
@@ -64,7 +64,7 @@ export const buildStripeCheckoutSessionAction = ({
6464

6565
// 6. Build discounts for checkout session
6666
const discounts = stripeDiscounts?.length
67-
? stripeDiscountsToParams({ stripeDiscounts })
67+
? stripeDiscountsToCheckoutParams({ stripeDiscounts })
6868
: undefined;
6969

7070
// 7. Build params (only variable params - static params added in execute)

server/src/internal/billing/v2/providers/stripe/utils/discounts/stripeDiscountsToParams.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11
import type { StripeDiscountWithCoupon } from "@autumn/shared";
22

33
/**
4-
* Maps internal discount objects to Stripe API `discounts` param format.
5-
* Uses { promotion_code: id } when the discount originates from a promo code,
6-
* otherwise uses { coupon: id } for direct coupon references.
4+
* Maps internal discount objects to Stripe API `discounts` param format for subscription updates.
5+
* Uses { discount: id } for existing discounts (preserving original start/end),
6+
* { promotion_code: id } for promo-code-based new discounts,
7+
* and { coupon: id } for new coupon-based discounts.
78
*/
89
export const stripeDiscountsToParams = ({
910
stripeDiscounts,
1011
}: {
1112
stripeDiscounts: StripeDiscountWithCoupon[];
13+
}): (
14+
| { discount: string }
15+
| { coupon: string }
16+
| { promotion_code: string }
17+
)[] => {
18+
return stripeDiscounts.map((d) => {
19+
if (d.id) return { discount: d.id };
20+
if (d.promotionCodeId) return { promotion_code: d.promotionCodeId };
21+
return { coupon: d.source.coupon.id };
22+
});
23+
};
24+
25+
/**
26+
* Maps discount objects to Stripe checkout session `discounts` param format.
27+
* Checkout sessions only accept { coupon } or { promotion_code } — not { discount }.
28+
*/
29+
export const stripeDiscountsToCheckoutParams = ({
30+
stripeDiscounts,
31+
}: {
32+
stripeDiscounts: StripeDiscountWithCoupon[];
1233
}): ({ coupon: string } | { promotion_code: string })[] => {
1334
return stripeDiscounts.map((d) =>
1435
d.promotionCodeId

server/tests/_temp/temp.test.ts

Lines changed: 2 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,6 @@
11
import { test } from "bun:test";
2-
import {
3-
type ApiPlanV1,
4-
BillingInterval,
5-
BillingMethod,
6-
type CreatePlanParamsInput,
7-
TierBehavior,
8-
TierInfinite,
9-
} from "@autumn/shared";
10-
import { TestFeature } from "@tests/setup/v2Features";
11-
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario";
122
import chalk from "chalk";
133

14-
const customerId = "temp-test";
15-
16-
test.concurrent(`${chalk.yellowBright("temp: rest update then rpc inverse update returns product to baseline")}`, async () => {
17-
const productId = "only_price_interval";
18-
19-
const {
20-
autumnV2_1: autumnV2,
21-
autumnV1,
22-
autumnV0,
23-
} = await initScenario({
24-
customerId,
25-
setup: [s.products({ list: [] })],
26-
actions: [],
27-
});
28-
29-
try {
30-
await autumnV2.products.delete(`${productId}_v2`);
31-
} catch (_error) {}
32-
33-
await autumnV2.products.create<ApiPlanV1, CreatePlanParamsInput>({
34-
id: `${productId}_v2`,
35-
name: "Volume V2 Test",
36-
items: [
37-
{
38-
feature_id: TestFeature.Messages,
39-
price: {
40-
interval: BillingInterval.Month,
41-
billing_method: BillingMethod.Prepaid,
42-
billing_units: 1,
43-
tier_behavior: TierBehavior.VolumeBased,
44-
tiers: [
45-
{ to: 100, amount: 10, flat_amount: 100 },
46-
{ to: TierInfinite, amount: 20, flat_amount: 90 },
47-
],
48-
},
49-
},
50-
],
51-
});
52-
53-
const v2 = await autumnV2.products.get<ApiPlanV1>(`${productId}_v2`);
54-
console.log(JSON.stringify(v2, null, 2));
55-
56-
// try {
57-
// await autumnV2.products.delete(`${productId}_v1`);
58-
// } catch (_error) {}
59-
60-
// await autumnV2.products.create<ApiPlan, CreatePlanParamsV2>({
61-
// plan_id: `${productId}_v1`,
62-
// name: "Volume V1 Test",
63-
// description: "Volume V1 Test",
64-
// group: "Volume V1 Test",
65-
// add_on: false,
66-
// auto_enable: true,
67-
// items: [
68-
// {
69-
// feature_id: TestFeature.Messages,
70-
// price: {
71-
// interval: BillingInterval.Month,
72-
// billing_method: BillingMethod.Prepaid,
73-
// billing_units: 1,
74-
// tier_behavior: TierBehavior.VolumeBased,
75-
// tiers: [
76-
// { to: 100, amount: 10, flat_amount: 100 },
77-
// { to: TierInfinite, amount: 20, flat_amount: 90 },
78-
// ],
79-
// },
80-
// },
81-
// ],
82-
// });
83-
84-
// const v1 = await autumnV1.products.get<ApiPlan>(productId);
85-
// console.log(JSON.stringify(v1, null, 2));
86-
87-
// try {
88-
// await autumnV0.products.delete(productId);
89-
// } catch (_error) {}
90-
91-
// await autumnV0.products.create<ApiPlan, CreatePlanParamsInput>({
92-
// id: productId,
93-
// name: "Volume V0 Test",
94-
// items: [
95-
// {
96-
// feature_id: TestFeature.Messages,
97-
// price: {
98-
// interval: BillingInterval.Month,
99-
// billing_method: BillingMethod.Prepaid,
100-
// billing_units: 1,
101-
// tier_behavior: TierBehavior.VolumeBased,
102-
// tiers: [
103-
// { to: 100, amount: 10, flat_amount: 100 },
104-
// { to: TierInfinite, amount: 20, flat_amount: 90 },
105-
// ],
106-
// },
107-
// },
108-
// ],
109-
// });
110-
111-
// const v0 = await autumnV0.products.get<ApiPlan>(productId);
112-
// console.log(JSON.stringify(v0, null, 2));
4+
test(`${chalk.yellowBright("temp: placeholder")}`, async () => {
5+
// Placeholder test
1136
});

0 commit comments

Comments
 (0)