1import * as Provider from "@/Provider";
2import * as Stripe from "@/Stripe";
3import * as Test from "@/Test/Alchemy";
4import { GetCoupon } 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 GetCoupon({ coupon: 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 coupon",
33 (stack) =>
34 Effect.gen(function* () {
35 yield* stack.destroy();
36
37 const created = yield* stack.deploy(
38 Effect.gen(function* () {
39 return yield* Stripe.Coupon("WelcomeCoupon", {
40 percentOff: 20,
41 duration: "forever",
42 name: "Alchemy Welcome 20%",
43 metadata: { campaign: "welcome" },
44 });
45 }),
46 );
47
48 expect(created.id).toEqual(expect.any(String));
49 expect(created.percentOff).toEqual(20);
50 expect(created.amountOff).toBeUndefined();
51 expect(created.duration).toEqual("forever");
52 expect(created.name).toEqual("Alchemy Welcome 20%");
53 expect(created.valid).toEqual(true);
54 expect(created.livemode).toEqual(false);
55 expect(created.metadata).toMatchObject({ campaign: "welcome" });
56 expect(created.created).toEqual(expect.any(Number));
57
58 const fetched = yield* GetCoupon({ coupon: created.id });
59 expect(fetched.id).toEqual(created.id);
60 expect(fetched.percent_off).toEqual(20);
61 expect(fetched.duration).toEqual("forever");
62 expect(fetched.name).toEqual("Alchemy Welcome 20%");
63 expect(fetched.metadata?.campaign).toEqual("welcome");
64 expect(
65 fetched.metadata?.[Stripe.alchemyMetadataKeys.stack],
66 ).toBeDefined();
67 expect(
68 fetched.metadata?.[Stripe.alchemyMetadataKeys.stage],
69 ).toBeDefined();
70 expect(fetched.metadata?.[Stripe.alchemyMetadataKeys.id]).toBeDefined();
71
72 const updated = yield* stack.deploy(
73 Effect.gen(function* () {
74 return yield* Stripe.Coupon("WelcomeCoupon", {
75 percentOff: 20,
76 duration: "forever",
77 name: "Alchemy Welcome 20% Updated",
78 metadata: { campaign: "spring", sku: "welcome-2" },
79 });
80 }),
81 );
82
83 expect(updated.id).toEqual(created.id);
84 expect(updated.percentOff).toEqual(20);
85 expect(updated.duration).toEqual("forever");
86 expect(updated.name).toEqual("Alchemy Welcome 20% Updated");
87 expect(updated.metadata).toEqual({
88 campaign: "spring",
89 sku: "welcome-2",
90 });
91
92 const refetched = yield* GetCoupon({ coupon: updated.id });
93 expect(refetched.id).toEqual(updated.id);
94 expect(refetched.name).toEqual("Alchemy Welcome 20% Updated");
95 expect(refetched.percent_off).toEqual(20);
96 expect(refetched.metadata?.campaign).toEqual("spring");
97 expect(refetched.metadata?.sku).toEqual("welcome-2");
98 expect(refetched.metadata?.[Stripe.alchemyMetadataKeys.id]).toBeDefined();
99
100 yield* stack.destroy();
101
102 const gone = yield* waitUntilGone(created.id);
103 expect(gone).toEqual("gone");
104 }).pipe(logLevel),
105 {
106 tags: ["provider:stripe", "provider:stripe:coupon", "live"],
107 timeout: 120_000,
108 },
109);
110
111test.provider(
112 "replace when the discount changes",
113 (stack) =>
114 Effect.gen(function* () {
115 yield* stack.destroy();
116
117 const created = yield* stack.deploy(
118 Effect.gen(function* () {
119 return yield* Stripe.Coupon("ReplaceCoupon", {
120 percentOff: 10,
121 duration: "once",
122 name: "Alchemy Replace Coupon",
123 });
124 }),
125 );
126
127 expect(created.percentOff).toEqual(10);
128 expect(created.duration).toEqual("once");
129
130 const replaced = yield* stack.deploy(
131 Effect.gen(function* () {
132 return yield* Stripe.Coupon("ReplaceCoupon", {
133 amountOff: 500,
134 currency: "usd",
135 duration: "once",
136 name: "Alchemy Replace Coupon",
137 });
138 }),
139 );
140
141 expect(replaced.id).not.toEqual(created.id);
142 expect(replaced.amountOff).toEqual(500);
143 expect(replaced.currency).toEqual("usd");
144 expect(replaced.percentOff).toBeUndefined();
145 expect(replaced.duration).toEqual("once");
146
147 const fetched = yield* GetCoupon({ coupon: replaced.id });
148 expect(fetched.id).toEqual(replaced.id);
149 expect(fetched.amount_off).toEqual(500);
150 expect(fetched.currency).toEqual("usd");
151 expect(fetched.percent_off).toBeNull();
152
153 const oldGone = yield* waitUntilGone(created.id);
154 expect(oldGone).toEqual("gone");
155
156 yield* stack.destroy();
157
158 const gone = yield* waitUntilGone(replaced.id);
159 expect(gone).toEqual("gone");
160 }).pipe(logLevel),
161 {
162 tags: ["provider:stripe", "provider:stripe:coupon", "live"],
163 timeout: 120_000,
164 },
165);
166
167test.provider(
168 "list enumerates the deployed coupon",
169 (stack) =>
170 Effect.gen(function* () {
171 yield* stack.destroy();
172
173 const deployed = yield* stack.deploy(
174 Effect.gen(function* () {
175 return yield* Stripe.Coupon("ListCoupon", {
176 percentOff: 15,
177 duration: "forever",
178 name: "Alchemy List Coupon",
179 metadata: { kind: "list" },
180 });
181 }),
182 );
183
184 const provider = yield* Provider.findProvider(Stripe.Coupon);
185 const all = yield* provider.list();
186 const found = all.find((coupon) => coupon.id === deployed.id);
187 expect(found).toBeDefined();
188 expect(found?.name).toEqual(deployed.name);
189 expect(found?.percentOff).toEqual(15);
190 expect(found?.metadata).toMatchObject({ kind: "list" });
191
192 yield* stack.destroy();
193
194 const gone = yield* waitUntilGone(deployed.id);
195 expect(gone).toEqual("gone");
196 }).pipe(logLevel),
197 {
198 tags: ["provider:stripe", "provider:stripe:coupon", "live"],
199 timeout: 120_000,
200 },
201);