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