1import * as Provider from "@/Provider";
2import * as Stripe from "@/Stripe";
3import { isMissingStripeResource } from "@/Stripe/missing.ts";
4import * as Test from "@/Test/Alchemy";
5import {
6 GetTerminalReaders,
7 GetTerminalReader,
8 type DeletedTerminalReader,
9 type TerminalReader as StripeTerminalReader,
10} from "@distilled.cloud/stripe/stripe";
11import { expect } from "alchemy-test";
12import * as Effect from "effect/Effect";
13import { MinimumLogLevel } from "effect/References";
14import * as Result from "effect/Result";
15import * as Schedule from "effect/Schedule";
16
17const { test } = Test.make({ providers: Stripe.providers() });
18
19const logLevel = Effect.provideService(
20 MinimumLogLevel,
21 process.env.DEBUG ? "Debug" : "Info",
22);
23
24const SIMULATED_REGISTRATION_CODE = "simulated-wpe";
25
26const usAddress = {
27 line1: "185 Berry Street",
28 city: "San Francisco",
29 state: "CA",
30 postalCode: "94107",
31 country: "US",
32} as const;
33
34const isDeletedReader = (
35 value: StripeTerminalReader | DeletedTerminalReader,
36): value is DeletedTerminalReader =>
37 "deleted" in value && value.deleted === true;
38
39const waitUntilGone = (id: string) =>
40 GetTerminalReader({ reader: id }).pipe(
41 Effect.map((reader) =>
42 isDeletedReader(reader) ? ("gone" as const) : ("found" as const),
43 ),
44 Effect.catchIf(isMissingStripeResource, () =>
45 Effect.succeed("gone" as const),
46 ),
47 Effect.repeat({
48 schedule: Schedule.spaced("1 second"),
49 until: (status) => status === "gone",
50 times: 10,
51 }),
52 );
53
54test.provider(
55 "terminal readers entitlement probe",
56 (stack) =>
57 Effect.gen(function* () {
58 yield* stack.destroy();
59
60 const result = yield* GetTerminalReaders({ limit: 1 }).pipe(
61 Effect.result,
62 );
63
64 if (Result.isSuccess(result)) {
65 expect(Array.isArray(result.success.data)).toBe(true);
66 } else {
67 expect(result.failure._tag).not.toEqual("UnknownStripeError");
68 expect(["InvalidRequestError", "Forbidden", "Unauthorized"]).toContain(
69 result.failure._tag,
70 );
71 }
72
73 yield* stack.destroy();
74 }).pipe(logLevel),
75 {
76 tags: ["provider:stripe", "provider:stripe:terminalreader", "live"],
77 timeout: 60_000,
78 },
79);
80
81test.provider(
82 "create, update, and delete a terminal reader",
83 (stack) =>
84 Effect.gen(function* () {
85 yield* stack.destroy();
86
87 const created = yield* stack.deploy(
88 Effect.gen(function* () {
89 const location = yield* Stripe.TerminalLocation(
90 "ReaderLifecycleLocation",
91 {
92 displayName: "Alchemy Terminal Reader Lifecycle",
93 address: { ...usAddress },
94 },
95 );
96 const reader = yield* Stripe.TerminalReader("FrontCounter", {
97 registrationCode: SIMULATED_REGISTRATION_CODE,
98 label: "Alchemy Front Counter",
99 location: location.id,
100 metadata: { station: "1" },
101 });
102 return { location, reader };
103 }),
104 );
105
106 expect(created.reader.id).toMatch(/^tmr_/);
107 expect(created.reader.label).toEqual("Alchemy Front Counter");
108 expect(created.reader.location).toEqual(created.location.id);
109 expect(created.reader.deviceType).toEqual("simulated_wisepos_e");
110 expect(created.reader.serialNumber.length).toBeGreaterThan(0);
111 expect(created.reader.metadata).toMatchObject({ station: "1" });
112 expect(created.reader.livemode).toEqual(false);
113
114 const fetched = yield* GetTerminalReader({
115 reader: created.reader.id,
116 });
117 expect(isDeletedReader(fetched)).toEqual(false);
118 if (isDeletedReader(fetched)) return;
119 expect(fetched.id).toEqual(created.reader.id);
120 expect(fetched.label).toEqual("Alchemy Front Counter");
121 expect(fetched.device_type).toEqual("simulated_wisepos_e");
122 expect(fetched.metadata?.station).toEqual("1");
123 expect(
124 fetched.metadata?.[Stripe.alchemyMetadataKeys.stack],
125 ).toBeDefined();
126 expect(
127 fetched.metadata?.[Stripe.alchemyMetadataKeys.stage],
128 ).toBeDefined();
129 expect(fetched.metadata?.[Stripe.alchemyMetadataKeys.id]).toBeDefined();
130
131 const updated = yield* stack.deploy(
132 Effect.gen(function* () {
133 const location = yield* Stripe.TerminalLocation(
134 "ReaderLifecycleLocation",
135 {
136 displayName: "Alchemy Terminal Reader Lifecycle",
137 address: { ...usAddress },
138 },
139 );
140 const reader = yield* Stripe.TerminalReader("FrontCounter", {
141 registrationCode: SIMULATED_REGISTRATION_CODE,
142 label: "Alchemy Front Counter Updated",
143 location: location.id,
144 metadata: { station: "1", floor: "lobby" },
145 });
146 return { location, reader };
147 }),
148 );
149
150 expect(updated.reader.id).toEqual(created.reader.id);
151 expect(updated.reader.label).toEqual("Alchemy Front Counter Updated");
152 expect(updated.reader.location).toEqual(created.location.id);
153 expect(updated.reader.metadata).toEqual({
154 station: "1",
155 floor: "lobby",
156 });
157
158 const refetched = yield* GetTerminalReader({
159 reader: updated.reader.id,
160 });
161 expect(isDeletedReader(refetched)).toEqual(false);
162 if (isDeletedReader(refetched)) return;
163 expect(refetched.label).toEqual("Alchemy Front Counter Updated");
164 expect(refetched.metadata?.station).toEqual("1");
165 expect(refetched.metadata?.floor).toEqual("lobby");
166 expect(refetched.metadata?.[Stripe.alchemyMetadataKeys.id]).toBeDefined();
167
168 yield* stack.destroy();
169
170 const gone = yield* waitUntilGone(created.reader.id);
171 expect(gone).toEqual("gone");
172 }).pipe(logLevel),
173 {
174 tags: [
175 "provider:stripe",
176 "provider:stripe:terminallocation",
177 "provider:stripe:terminalreader",
178 "live",
179 ],
180 timeout: 120_000,
181 },
182);
183
184test.provider(
185 "list enumerates the deployed terminal reader",
186 (stack) =>
187 Effect.gen(function* () {
188 yield* stack.destroy();
189
190 const deployed = yield* stack.deploy(
191 Effect.gen(function* () {
192 const location = yield* Stripe.TerminalLocation(
193 "ReaderListLocation",
194 {
195 displayName: "Alchemy Terminal Reader List",
196 address: { ...usAddress },
197 },
198 );
199 const reader = yield* Stripe.TerminalReader("ListReader", {
200 registrationCode: SIMULATED_REGISTRATION_CODE,
201 label: "Alchemy List Reader",
202 location: location.id,
203 metadata: { kind: "list" },
204 });
205 return { location, reader };
206 }),
207 );
208
209 const provider = yield* Provider.findProvider(Stripe.TerminalReader);
210 const all = yield* provider.list();
211 const found = all.find((reader) => reader.id === deployed.reader.id);
212 expect(found).toBeDefined();
213 expect(found?.label).toEqual("Alchemy List Reader");
214 expect(found?.metadata).toMatchObject({ kind: "list" });
215
216 yield* stack.destroy();
217
218 const gone = yield* waitUntilGone(deployed.reader.id);
219 expect(gone).toEqual("gone");
220 }).pipe(logLevel),
221 {
222 tags: [
223 "provider:stripe",
224 "provider:stripe:terminallocation",
225 "provider:stripe:terminalreader",
226 "live",
227 ],
228 timeout: 120_000,
229 },
230);