1import * as Provider from "@/Provider";
2import * as Stripe from "@/Stripe";
3import * as Test from "@/Test/Alchemy";
4import { GetPlan } from "@distilled.cloud/stripe/stripe";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9import { isMissingStripeResource } from "@/Stripe/missing.ts";
10
11const { test } = Test.make({ providers: Stripe.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const waitUntilGone = (id: string) =>
19 GetPlan({ plan: id }).pipe(
20 Effect.as("found" as const),
21 Effect.catchIf(isMissingStripeResource, () =>
22 Effect.succeed("gone" as const),
23 ),
24 Effect.repeat({
25 schedule: Schedule.spaced("1 second"),
26 until: (status) => status === "gone",
27 times: 10,
28 }),
29 );
30
31test.provider(
32 "create, update, and delete a plan",
33 (stack) =>
34 Effect.gen(function* () {
35 yield* stack.destroy();
36
37 const created = yield* stack.deploy(
38 Effect.gen(function* () {
39 const product = yield* Stripe.Product("MonthlyPlanProduct", {
40 name: "Alchemy Monthly Plan Product",
41 });
42 return yield* Stripe.Plan("MonthlyPlan", {
43 product: product.id,
44 currency: "usd",
45 interval: "month",
46 amount: 1500,
47 nickname: "Alchemy monthly",
48 metadata: { tier: "pro" },
49 });
50 }),
51 );
52
53 expect(created.id).toEqual(expect.any(String));
54 expect(created.product).toEqual(expect.any(String));
55 expect(created.currency).toEqual("usd");
56 expect(created.interval).toEqual("month");
57 expect(created.intervalCount).toEqual(1);
58 expect(created.amount).toEqual(1500);
59 expect(created.active).toEqual(true);
60 expect(created.nickname).toEqual("Alchemy monthly");
61 expect(created.usageType).toEqual("licensed");
62 expect(created.billingScheme).toEqual("per_unit");
63 expect(created.livemode).toEqual(false);
64 expect(created.metadata).toMatchObject({ tier: "pro" });
65 expect(created.created).toEqual(expect.any(Number));
66
67 const fetched = yield* GetPlan({ plan: created.id });
68 expect(fetched.id).toEqual(created.id);
69 expect(fetched.amount).toEqual(1500);
70 expect(fetched.interval).toEqual("month");
71 expect(fetched.nickname).toEqual("Alchemy monthly");
72 expect(fetched.active).toEqual(true);
73 expect(fetched.metadata?.tier).toEqual("pro");
74 expect(
75 fetched.metadata?.[Stripe.alchemyMetadataKeys.stack],
76 ).toBeDefined();
77 expect(
78 fetched.metadata?.[Stripe.alchemyMetadataKeys.stage],
79 ).toBeDefined();
80 expect(fetched.metadata?.[Stripe.alchemyMetadataKeys.id]).toBeDefined();
81
82 const updated = yield* stack.deploy(
83 Effect.gen(function* () {
84 const product = yield* Stripe.Product("MonthlyPlanProduct", {
85 name: "Alchemy Monthly Plan Product",
86 });
87 return yield* Stripe.Plan("MonthlyPlan", {
88 product: product.id,
89 currency: "usd",
90 interval: "month",
91 amount: 1500,
92 nickname: "Alchemy monthly (trial)",
93 trialPeriodDays: 14,
94 metadata: { tier: "enterprise", sku: "ent-1" },
95 });
96 }),
97 );
98
99 expect(updated.id).toEqual(created.id);
100 expect(updated.nickname).toEqual("Alchemy monthly (trial)");
101 expect(updated.trialPeriodDays).toEqual(14);
102 expect(updated.amount).toEqual(1500);
103 expect(updated.metadata).toEqual({
104 tier: "enterprise",
105 sku: "ent-1",
106 });
107
108 const refetched = yield* GetPlan({ plan: updated.id });
109 expect(refetched.id).toEqual(updated.id);
110 expect(refetched.nickname).toEqual("Alchemy monthly (trial)");
111 expect(refetched.trial_period_days).toEqual(14);
112 expect(refetched.amount).toEqual(1500);
113 expect(refetched.metadata?.tier).toEqual("enterprise");
114 expect(refetched.metadata?.sku).toEqual("ent-1");
115 expect(refetched.metadata?.[Stripe.alchemyMetadataKeys.id]).toBeDefined();
116
117 yield* stack.destroy();
118
119 const gone = yield* waitUntilGone(created.id);
120 expect(gone).toEqual("gone");
121 }).pipe(logLevel),
122 {
123 tags: [
124 "provider:stripe",
125 "provider:stripe:plan",
126 "provider:stripe:product",
127 "live",
128 ],
129 timeout: 120_000,
130 },
131);
132
133test.provider(
134 "replace when the amount changes",
135 (stack) =>
136 Effect.gen(function* () {
137 yield* stack.destroy();
138
139 const created = yield* stack.deploy(
140 Effect.gen(function* () {
141 const product = yield* Stripe.Product("ReplacePlanProduct", {
142 name: "Alchemy Replace Plan Product",
143 });
144 return yield* Stripe.Plan("ReplacePlan", {
145 product: product.id,
146 currency: "usd",
147 interval: "month",
148 amount: 1000,
149 nickname: "v1",
150 });
151 }),
152 );
153
154 expect(created.amount).toEqual(1000);
155
156 const replaced = yield* stack.deploy(
157 Effect.gen(function* () {
158 const product = yield* Stripe.Product("ReplacePlanProduct", {
159 name: "Alchemy Replace Plan Product",
160 });
161 return yield* Stripe.Plan("ReplacePlan", {
162 product: product.id,
163 currency: "usd",
164 interval: "month",
165 amount: 2500,
166 nickname: "v2",
167 });
168 }),
169 );
170
171 expect(replaced.id).not.toEqual(created.id);
172 expect(replaced.amount).toEqual(2500);
173 expect(replaced.nickname).toEqual("v2");
174 expect(replaced.interval).toEqual("month");
175
176 const fetched = yield* GetPlan({ plan: replaced.id });
177 expect(fetched.id).toEqual(replaced.id);
178 expect(fetched.amount).toEqual(2500);
179 expect(fetched.nickname).toEqual("v2");
180
181 const oldGone = yield* waitUntilGone(created.id);
182 expect(oldGone).toEqual("gone");
183
184 yield* stack.destroy();
185
186 const gone = yield* waitUntilGone(replaced.id);
187 expect(gone).toEqual("gone");
188 }).pipe(logLevel),
189 {
190 tags: [
191 "provider:stripe",
192 "provider:stripe:plan",
193 "provider:stripe:product",
194 "live",
195 ],
196 timeout: 120_000,
197 },
198);
199
200test.provider(
201 "list enumerates the deployed plan",
202 (stack) =>
203 Effect.gen(function* () {
204 yield* stack.destroy();
205
206 const deployed = yield* stack.deploy(
207 Effect.gen(function* () {
208 const product = yield* Stripe.Product("ListPlanProduct", {
209 name: "Alchemy List Plan Product",
210 });
211 return yield* Stripe.Plan("ListPlan", {
212 product: product.id,
213 currency: "usd",
214 interval: "year",
215 amount: 12000,
216 nickname: "Alchemy yearly",
217 metadata: { kind: "list" },
218 });
219 }),
220 );
221
222 expect(deployed.interval).toEqual("year");
223 expect(deployed.amount).toEqual(12000);
224
225 const provider = yield* Provider.findProvider(Stripe.Plan);
226 const all = yield* provider.list();
227 const found = all.find((plan) => plan.id === deployed.id);
228 expect(found).toBeDefined();
229 expect(found?.nickname).toEqual("Alchemy yearly");
230 expect(found?.interval).toEqual("year");
231 expect(found?.metadata).toMatchObject({ kind: "list" });
232
233 yield* stack.destroy();
234
235 const gone = yield* waitUntilGone(deployed.id);
236 expect(gone).toEqual("gone");
237 }).pipe(logLevel),
238 {
239 tags: [
240 "provider:stripe",
241 "provider:stripe:plan",
242 "provider:stripe:product",
243 "live",
244 ],
245 timeout: 120_000,
246 },
247);