@frak-labs/core-sdk 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/cdn/bundle.js +14 -0
- package/dist/actions.cjs +1 -1
- package/dist/actions.d.cts +3 -3
- package/dist/actions.d.ts +3 -3
- package/dist/actions.js +1 -1
- package/dist/bundle.cjs +1 -1
- package/dist/bundle.d.cts +4 -6
- package/dist/bundle.d.ts +4 -6
- package/dist/bundle.js +1 -1
- package/dist/{index-CRsQWnTs.d.cts → computeLegacyProductId-BkyJ4rEY.d.ts} +197 -10
- package/dist/{index-Ck1hudEi.d.ts → computeLegacyProductId-Raks6FXg.d.cts} +197 -10
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -1
- package/dist/{openSso-D--Airj6.d.cts → openSso-BCJGchIb.d.cts} +135 -131
- package/dist/{openSso-DsKJ4y0j.d.ts → openSso-DG-_9CED.d.ts} +135 -131
- package/dist/setupClient-CQrMDGyZ.js +13 -0
- package/dist/setupClient-Ccv3XxwL.cjs +13 -0
- package/dist/{index-d8xS4ryI.d.ts → siweAuthenticate-BH7Dn7nZ.d.cts} +90 -65
- package/dist/siweAuthenticate-BJHbtty4.js +1 -0
- package/dist/{index-C6FxkWPC.d.cts → siweAuthenticate-Btem4QHs.d.ts} +90 -65
- package/dist/siweAuthenticate-Cwj3HP0m.cjs +1 -0
- package/dist/trackEvent-M2RLTQ2p.js +1 -0
- package/dist/trackEvent-T_R9ER2S.cjs +1 -0
- package/package.json +11 -22
- package/src/actions/displayEmbeddedWallet.ts +1 -0
- package/src/actions/displayModal.test.ts +12 -11
- package/src/actions/displayModal.ts +7 -18
- package/src/actions/ensureIdentity.ts +68 -0
- package/src/actions/{getProductInformation.test.ts → getMerchantInformation.test.ts} +33 -50
- package/src/actions/getMerchantInformation.ts +16 -0
- package/src/actions/index.ts +3 -2
- package/src/actions/openSso.ts +4 -2
- package/src/actions/referral/processReferral.test.ts +42 -151
- package/src/actions/referral/processReferral.ts +18 -42
- package/src/actions/referral/referralInteraction.test.ts +1 -7
- package/src/actions/referral/referralInteraction.ts +1 -6
- package/src/actions/sendInteraction.ts +46 -22
- package/src/actions/trackPurchaseStatus.test.ts +354 -141
- package/src/actions/trackPurchaseStatus.ts +48 -11
- package/src/actions/watchWalletStatus.ts +2 -3
- package/src/actions/wrapper/modalBuilder.test.ts +0 -14
- package/src/actions/wrapper/modalBuilder.ts +3 -12
- package/src/bundle.ts +0 -1
- package/src/clients/createIFrameFrakClient.ts +10 -5
- package/src/clients/transports/iframeLifecycleManager.test.ts +163 -4
- package/src/clients/transports/iframeLifecycleManager.ts +172 -33
- package/src/constants/interactionTypes.ts +12 -41
- package/src/index.ts +24 -16
- package/src/types/config.ts +6 -0
- package/src/types/index.ts +13 -10
- package/src/types/lifecycle/client.ts +24 -1
- package/src/types/lifecycle/iframe.ts +6 -0
- package/src/types/rpc/displayModal.ts +2 -4
- package/src/types/rpc/embedded/index.ts +2 -2
- package/src/types/rpc/interaction.ts +26 -39
- package/src/types/rpc/merchantInformation.ts +77 -0
- package/src/types/rpc/modal/index.ts +0 -4
- package/src/types/rpc/modal/login.ts +5 -1
- package/src/types/rpc/walletStatus.ts +1 -7
- package/src/types/rpc.ts +22 -30
- package/src/types/tracking.ts +60 -0
- package/src/utils/backendUrl.test.ts +83 -0
- package/src/utils/backendUrl.ts +62 -0
- package/src/utils/clientId.test.ts +41 -0
- package/src/utils/clientId.ts +43 -0
- package/src/utils/compression/compress.test.ts +1 -1
- package/src/utils/compression/compress.ts +2 -2
- package/src/utils/compression/decompress.test.ts +8 -4
- package/src/utils/compression/decompress.ts +2 -2
- package/src/utils/{computeProductId.ts → computeLegacyProductId.ts} +2 -2
- package/src/utils/constants.ts +5 -0
- package/src/utils/deepLinkWithFallback.test.ts +243 -0
- package/src/utils/deepLinkWithFallback.ts +103 -0
- package/src/utils/formatAmount.ts +6 -0
- package/src/utils/iframeHelper.test.ts +18 -5
- package/src/utils/iframeHelper.ts +10 -3
- package/src/utils/index.ts +16 -1
- package/src/utils/merchantId.test.ts +653 -0
- package/src/utils/merchantId.ts +143 -0
- package/src/utils/sso.ts +18 -11
- package/src/utils/trackEvent.test.ts +23 -5
- package/src/utils/trackEvent.ts +13 -0
- package/cdn/bundle.iife.js +0 -14
- package/dist/actions-B5j-i1p0.cjs +0 -1
- package/dist/actions-q090Z0oR.js +0 -1
- package/dist/index-7OZ39x1U.d.ts +0 -195
- package/dist/index-zDq-VlKx.d.cts +0 -195
- package/dist/interaction-DMJ3ZfaF.d.cts +0 -45
- package/dist/interaction-KX1h9a7V.d.ts +0 -45
- package/dist/interactions-DnfM3oe0.js +0 -1
- package/dist/interactions-EIXhNLf6.cjs +0 -1
- package/dist/interactions.cjs +0 -1
- package/dist/interactions.d.cts +0 -2
- package/dist/interactions.d.ts +0 -2
- package/dist/interactions.js +0 -1
- package/dist/productTypes-BUkXJKZ7.cjs +0 -1
- package/dist/productTypes-CGb1MmBF.js +0 -1
- package/dist/src-1LQ4eLq5.js +0 -13
- package/dist/src-hW71KjPN.cjs +0 -13
- package/dist/trackEvent-CHnYa85W.js +0 -1
- package/dist/trackEvent-GuQm_1Nm.cjs +0 -1
- package/src/actions/getProductInformation.ts +0 -14
- package/src/actions/openSso.test.ts +0 -407
- package/src/actions/sendInteraction.test.ts +0 -219
- package/src/constants/interactionTypes.test.ts +0 -128
- package/src/constants/productTypes.test.ts +0 -130
- package/src/constants/productTypes.ts +0 -33
- package/src/interactions/index.ts +0 -5
- package/src/interactions/pressEncoder.test.ts +0 -215
- package/src/interactions/pressEncoder.ts +0 -53
- package/src/interactions/purchaseEncoder.test.ts +0 -291
- package/src/interactions/purchaseEncoder.ts +0 -99
- package/src/interactions/referralEncoder.test.ts +0 -170
- package/src/interactions/referralEncoder.ts +0 -47
- package/src/interactions/retailEncoder.test.ts +0 -107
- package/src/interactions/retailEncoder.ts +0 -37
- package/src/interactions/webshopEncoder.test.ts +0 -56
- package/src/interactions/webshopEncoder.ts +0 -30
- package/src/types/rpc/modal/openSession.ts +0 -25
- package/src/types/rpc/productInformation.ts +0 -59
- package/src/utils/computeProductId.test.ts +0 -80
- package/src/utils/sso.test.ts +0 -361
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
clearMerchantIdCache,
|
|
4
|
+
fetchMerchantId,
|
|
5
|
+
resolveMerchantId,
|
|
6
|
+
} from "./merchantId";
|
|
7
|
+
|
|
8
|
+
// Mock the backendUrl module
|
|
9
|
+
vi.mock("./backendUrl", () => ({
|
|
10
|
+
getBackendUrl: vi.fn((walletUrl?: string) => {
|
|
11
|
+
if (walletUrl?.includes("localhost")) {
|
|
12
|
+
return "http://localhost:3030";
|
|
13
|
+
}
|
|
14
|
+
return "https://backend.frak.id";
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
describe("merchantId", () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Clear cache before each test (also clears sessionStorage)
|
|
21
|
+
clearMerchantIdCache();
|
|
22
|
+
window.sessionStorage.clear();
|
|
23
|
+
// Clear all mocks
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
// Mock console methods to avoid noise in test output
|
|
26
|
+
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
vi.restoreAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("fetchMerchantId", () => {
|
|
34
|
+
it("should fetch merchantId from backend when not cached", async () => {
|
|
35
|
+
const mockResponse = {
|
|
36
|
+
merchantId: "merchant-123",
|
|
37
|
+
name: "Test Merchant",
|
|
38
|
+
domain: "shop.example.com",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
42
|
+
ok: true,
|
|
43
|
+
json: async () => mockResponse,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = await fetchMerchantId("shop.example.com");
|
|
47
|
+
|
|
48
|
+
expect(result).toBe("merchant-123");
|
|
49
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
50
|
+
"https://backend.frak.id/user/merchant/resolve?domain=shop.example.com"
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should return cached merchantId on subsequent calls", async () => {
|
|
55
|
+
const mockResponse = {
|
|
56
|
+
merchantId: "merchant-456",
|
|
57
|
+
name: "Test Merchant",
|
|
58
|
+
domain: "shop.example.com",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
62
|
+
ok: true,
|
|
63
|
+
json: async () => mockResponse,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// First call
|
|
67
|
+
const result1 = await fetchMerchantId("shop.example.com");
|
|
68
|
+
expect(result1).toBe("merchant-456");
|
|
69
|
+
|
|
70
|
+
// Second call should use cache
|
|
71
|
+
const result2 = await fetchMerchantId("shop.example.com");
|
|
72
|
+
expect(result2).toBe("merchant-456");
|
|
73
|
+
|
|
74
|
+
// Fetch should only be called once
|
|
75
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should deduplicate concurrent requests", async () => {
|
|
79
|
+
const mockResponse = {
|
|
80
|
+
merchantId: "merchant-789",
|
|
81
|
+
name: "Test Merchant",
|
|
82
|
+
domain: "shop.example.com",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
86
|
+
ok: true,
|
|
87
|
+
json: async () => mockResponse,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Start multiple concurrent requests
|
|
91
|
+
const [result1, result2, result3] = await Promise.all([
|
|
92
|
+
fetchMerchantId("shop.example.com"),
|
|
93
|
+
fetchMerchantId("shop.example.com"),
|
|
94
|
+
fetchMerchantId("shop.example.com"),
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
expect(result1).toBe("merchant-789");
|
|
98
|
+
expect(result2).toBe("merchant-789");
|
|
99
|
+
expect(result3).toBe("merchant-789");
|
|
100
|
+
|
|
101
|
+
// Fetch should only be called once despite concurrent requests
|
|
102
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should use window.location.hostname as fallback when domain not provided", async () => {
|
|
106
|
+
const mockResponse = {
|
|
107
|
+
merchantId: "merchant-default",
|
|
108
|
+
name: "Test Merchant",
|
|
109
|
+
domain: "example.com",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
113
|
+
ok: true,
|
|
114
|
+
json: async () => mockResponse,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Mock window.location.hostname
|
|
118
|
+
Object.defineProperty(window, "location", {
|
|
119
|
+
value: {
|
|
120
|
+
hostname: "example.com",
|
|
121
|
+
},
|
|
122
|
+
writable: true,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = await fetchMerchantId();
|
|
126
|
+
|
|
127
|
+
expect(result).toBe("merchant-default");
|
|
128
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
129
|
+
"https://backend.frak.id/user/merchant/resolve?domain=example.com"
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should return undefined when domain is empty and no hostname available", async () => {
|
|
134
|
+
global.fetch = vi.fn();
|
|
135
|
+
|
|
136
|
+
const result = await fetchMerchantId("");
|
|
137
|
+
|
|
138
|
+
expect(result).toBeUndefined();
|
|
139
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should handle fetch errors gracefully", async () => {
|
|
143
|
+
global.fetch = vi
|
|
144
|
+
.fn()
|
|
145
|
+
.mockRejectedValueOnce(new Error("Network error"));
|
|
146
|
+
|
|
147
|
+
const result = await fetchMerchantId("shop.example.com");
|
|
148
|
+
|
|
149
|
+
expect(result).toBeUndefined();
|
|
150
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
151
|
+
"[Frak SDK] Failed to fetch merchantId:",
|
|
152
|
+
expect.any(Error)
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should handle non-ok response status", async () => {
|
|
157
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
158
|
+
ok: false,
|
|
159
|
+
status: 404,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = await fetchMerchantId("nonexistent.com");
|
|
163
|
+
|
|
164
|
+
expect(result).toBeUndefined();
|
|
165
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
166
|
+
"[Frak SDK] Merchant lookup failed for domain nonexistent.com: 404"
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should handle 500 server error", async () => {
|
|
171
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
172
|
+
ok: false,
|
|
173
|
+
status: 500,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const result = await fetchMerchantId("shop.example.com");
|
|
177
|
+
|
|
178
|
+
expect(result).toBeUndefined();
|
|
179
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
180
|
+
"[Frak SDK] Merchant lookup failed for domain shop.example.com: 500"
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should handle invalid JSON response", async () => {
|
|
185
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
186
|
+
ok: true,
|
|
187
|
+
json: async () => {
|
|
188
|
+
throw new Error("Invalid JSON");
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const result = await fetchMerchantId("shop.example.com");
|
|
193
|
+
|
|
194
|
+
expect(result).toBeUndefined();
|
|
195
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
196
|
+
"[Frak SDK] Failed to fetch merchantId:",
|
|
197
|
+
expect.any(Error)
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should use custom walletUrl to derive backend URL", async () => {
|
|
202
|
+
const mockResponse = {
|
|
203
|
+
merchantId: "merchant-local",
|
|
204
|
+
name: "Test Merchant",
|
|
205
|
+
domain: "shop.example.com",
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
209
|
+
ok: true,
|
|
210
|
+
json: async () => mockResponse,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const result = await fetchMerchantId(
|
|
214
|
+
"shop.example.com",
|
|
215
|
+
"http://localhost:3000"
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect(result).toBe("merchant-local");
|
|
219
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
220
|
+
"http://localhost:3030/user/merchant/resolve?domain=shop.example.com"
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should encode domain in URL query parameter", async () => {
|
|
225
|
+
const mockResponse = {
|
|
226
|
+
merchantId: "merchant-encoded",
|
|
227
|
+
name: "Test Merchant",
|
|
228
|
+
domain: "shop.example.com",
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
232
|
+
ok: true,
|
|
233
|
+
json: async () => mockResponse,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const domainWithSpecialChars = "shop.example.com?test=1&foo=bar";
|
|
237
|
+
await fetchMerchantId(domainWithSpecialChars);
|
|
238
|
+
|
|
239
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
240
|
+
"https://backend.frak.id/user/merchant/resolve?domain=shop.example.com%3Ftest%3D1%26foo%3Dbar"
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should cache merchantId from response", async () => {
|
|
245
|
+
const mockResponse = {
|
|
246
|
+
merchantId: "merchant-cached",
|
|
247
|
+
name: "Test Merchant",
|
|
248
|
+
domain: "shop.example.com",
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
252
|
+
ok: true,
|
|
253
|
+
json: async () => mockResponse,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const result1 = await fetchMerchantId("shop.example.com");
|
|
257
|
+
expect(result1).toBe("merchant-cached");
|
|
258
|
+
|
|
259
|
+
// Clear fetch mock and call again
|
|
260
|
+
global.fetch = vi.fn();
|
|
261
|
+
const result2 = await fetchMerchantId("shop.example.com");
|
|
262
|
+
|
|
263
|
+
// Should return cached value without calling fetch
|
|
264
|
+
expect(result2).toBe("merchant-cached");
|
|
265
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("should persist merchantId to sessionStorage after fetch", async () => {
|
|
269
|
+
const mockResponse = {
|
|
270
|
+
merchantId: "merchant-persisted",
|
|
271
|
+
name: "Test Merchant",
|
|
272
|
+
domain: "shop.example.com",
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
276
|
+
ok: true,
|
|
277
|
+
json: async () => mockResponse,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await fetchMerchantId("shop.example.com");
|
|
281
|
+
|
|
282
|
+
expect(window.sessionStorage.getItem("frak-merchant-id")).toBe(
|
|
283
|
+
"merchant-persisted"
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should restore merchantId from sessionStorage when in-memory cache is cleared", async () => {
|
|
288
|
+
const mockResponse = {
|
|
289
|
+
merchantId: "merchant-storage",
|
|
290
|
+
name: "Test Merchant",
|
|
291
|
+
domain: "shop.example.com",
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
295
|
+
ok: true,
|
|
296
|
+
json: async () => mockResponse,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// First call populates both caches
|
|
300
|
+
const result1 = await fetchMerchantId("shop.example.com");
|
|
301
|
+
expect(result1).toBe("merchant-storage");
|
|
302
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
303
|
+
|
|
304
|
+
// Clear only in-memory cache (not sessionStorage)
|
|
305
|
+
vi.clearAllMocks();
|
|
306
|
+
global.fetch = vi.fn();
|
|
307
|
+
|
|
308
|
+
// Manually clear in-memory but keep sessionStorage
|
|
309
|
+
// We do this by calling the internal clear then restoring sessionStorage
|
|
310
|
+
const stored = window.sessionStorage.getItem("frak-merchant-id");
|
|
311
|
+
clearMerchantIdCache();
|
|
312
|
+
window.sessionStorage.setItem("frak-merchant-id", stored!);
|
|
313
|
+
|
|
314
|
+
// Second call should restore from sessionStorage without fetch
|
|
315
|
+
const result2 = await fetchMerchantId("shop.example.com");
|
|
316
|
+
expect(result2).toBe("merchant-storage");
|
|
317
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should not write to sessionStorage when fetch fails", async () => {
|
|
321
|
+
global.fetch = vi
|
|
322
|
+
.fn()
|
|
323
|
+
.mockRejectedValueOnce(new Error("Network error"));
|
|
324
|
+
|
|
325
|
+
await fetchMerchantId("shop.example.com");
|
|
326
|
+
|
|
327
|
+
expect(
|
|
328
|
+
window.sessionStorage.getItem("frak-merchant-id")
|
|
329
|
+
).toBeNull();
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe("clearMerchantIdCache", () => {
|
|
334
|
+
it("should clear the cached merchantId", async () => {
|
|
335
|
+
const mockResponse = {
|
|
336
|
+
merchantId: "merchant-clear-test",
|
|
337
|
+
name: "Test Merchant",
|
|
338
|
+
domain: "shop.example.com",
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
342
|
+
ok: true,
|
|
343
|
+
json: async () => mockResponse,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// First fetch
|
|
347
|
+
const result1 = await fetchMerchantId("shop.example.com");
|
|
348
|
+
expect(result1).toBe("merchant-clear-test");
|
|
349
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
350
|
+
|
|
351
|
+
// Clear cache
|
|
352
|
+
clearMerchantIdCache();
|
|
353
|
+
|
|
354
|
+
// Second fetch should call API again
|
|
355
|
+
const result2 = await fetchMerchantId("shop.example.com");
|
|
356
|
+
expect(result2).toBe("merchant-clear-test");
|
|
357
|
+
expect(global.fetch).toHaveBeenCalledTimes(2);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("should allow re-fetching after cache clear", async () => {
|
|
361
|
+
const mockResponse1 = {
|
|
362
|
+
merchantId: "merchant-first",
|
|
363
|
+
name: "Test Merchant",
|
|
364
|
+
domain: "shop1.example.com",
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const mockResponse2 = {
|
|
368
|
+
merchantId: "merchant-second",
|
|
369
|
+
name: "Test Merchant",
|
|
370
|
+
domain: "shop2.example.com",
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
global.fetch = vi
|
|
374
|
+
.fn()
|
|
375
|
+
.mockResolvedValueOnce({
|
|
376
|
+
ok: true,
|
|
377
|
+
json: async () => mockResponse1,
|
|
378
|
+
})
|
|
379
|
+
.mockResolvedValueOnce({
|
|
380
|
+
ok: true,
|
|
381
|
+
json: async () => mockResponse2,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const result1 = await fetchMerchantId("shop1.example.com");
|
|
385
|
+
expect(result1).toBe("merchant-first");
|
|
386
|
+
|
|
387
|
+
clearMerchantIdCache();
|
|
388
|
+
|
|
389
|
+
const result2 = await fetchMerchantId("shop2.example.com");
|
|
390
|
+
expect(result2).toBe("merchant-second");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("should clear sessionStorage when clearing cache", async () => {
|
|
394
|
+
const mockResponse = {
|
|
395
|
+
merchantId: "merchant-session-clear",
|
|
396
|
+
name: "Test Merchant",
|
|
397
|
+
domain: "shop.example.com",
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
401
|
+
ok: true,
|
|
402
|
+
json: async () => mockResponse,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
await fetchMerchantId("shop.example.com");
|
|
406
|
+
expect(window.sessionStorage.getItem("frak-merchant-id")).toBe(
|
|
407
|
+
"merchant-session-clear"
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
clearMerchantIdCache();
|
|
411
|
+
|
|
412
|
+
expect(
|
|
413
|
+
window.sessionStorage.getItem("frak-merchant-id")
|
|
414
|
+
).toBeNull();
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe("resolveMerchantId", () => {
|
|
419
|
+
it("should return merchantId from config if available", async () => {
|
|
420
|
+
global.fetch = vi.fn();
|
|
421
|
+
|
|
422
|
+
const config = {
|
|
423
|
+
metadata: {
|
|
424
|
+
merchantId: "config-merchant-123",
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const result = await resolveMerchantId(config);
|
|
429
|
+
|
|
430
|
+
expect(result).toBe("config-merchant-123");
|
|
431
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("should fetch merchantId from backend if not in config", async () => {
|
|
435
|
+
const mockResponse = {
|
|
436
|
+
merchantId: "fetched-merchant-456",
|
|
437
|
+
name: "Test Merchant",
|
|
438
|
+
domain: "shop.example.com",
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
442
|
+
ok: true,
|
|
443
|
+
json: async () => mockResponse,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
Object.defineProperty(window, "location", {
|
|
447
|
+
value: {
|
|
448
|
+
hostname: "shop.example.com",
|
|
449
|
+
},
|
|
450
|
+
writable: true,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const config = {
|
|
454
|
+
metadata: {},
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const result = await resolveMerchantId(config);
|
|
458
|
+
|
|
459
|
+
expect(result).toBe("fetched-merchant-456");
|
|
460
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("should return undefined when config has no merchantId and fetch fails", async () => {
|
|
464
|
+
global.fetch = vi
|
|
465
|
+
.fn()
|
|
466
|
+
.mockRejectedValueOnce(new Error("Network error"));
|
|
467
|
+
|
|
468
|
+
const config = {
|
|
469
|
+
metadata: {},
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const result = await resolveMerchantId(config);
|
|
473
|
+
|
|
474
|
+
expect(result).toBeUndefined();
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("should prioritize config merchantId over fetched value", async () => {
|
|
478
|
+
global.fetch = vi.fn();
|
|
479
|
+
|
|
480
|
+
const config = {
|
|
481
|
+
metadata: {
|
|
482
|
+
merchantId: "config-priority",
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const result = await resolveMerchantId(config);
|
|
487
|
+
|
|
488
|
+
expect(result).toBe("config-priority");
|
|
489
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("should use walletUrl parameter when fetching", async () => {
|
|
493
|
+
const mockResponse = {
|
|
494
|
+
merchantId: "merchant-with-wallet-url",
|
|
495
|
+
name: "Test Merchant",
|
|
496
|
+
domain: "shop.example.com",
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
500
|
+
ok: true,
|
|
501
|
+
json: async () => mockResponse,
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
Object.defineProperty(window, "location", {
|
|
505
|
+
value: {
|
|
506
|
+
hostname: "shop.example.com",
|
|
507
|
+
},
|
|
508
|
+
writable: true,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const config = {
|
|
512
|
+
metadata: {},
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const result = await resolveMerchantId(
|
|
516
|
+
config,
|
|
517
|
+
"http://localhost:3000"
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
expect(result).toBe("merchant-with-wallet-url");
|
|
521
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
522
|
+
"http://localhost:3030/user/merchant/resolve?domain=shop.example.com"
|
|
523
|
+
);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it("should handle config without metadata property", async () => {
|
|
527
|
+
const mockResponse = {
|
|
528
|
+
merchantId: "merchant-no-metadata",
|
|
529
|
+
name: "Test Merchant",
|
|
530
|
+
domain: "shop.example.com",
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
534
|
+
ok: true,
|
|
535
|
+
json: async () => mockResponse,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
Object.defineProperty(window, "location", {
|
|
539
|
+
value: {
|
|
540
|
+
hostname: "shop.example.com",
|
|
541
|
+
},
|
|
542
|
+
writable: true,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const config = {};
|
|
546
|
+
|
|
547
|
+
const result = await resolveMerchantId(config);
|
|
548
|
+
|
|
549
|
+
expect(result).toBe("merchant-no-metadata");
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("should cache result from fetch in resolveMerchantId", async () => {
|
|
553
|
+
const mockResponse = {
|
|
554
|
+
merchantId: "merchant-cached-resolve",
|
|
555
|
+
name: "Test Merchant",
|
|
556
|
+
domain: "shop.example.com",
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
560
|
+
ok: true,
|
|
561
|
+
json: async () => mockResponse,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
Object.defineProperty(window, "location", {
|
|
565
|
+
value: {
|
|
566
|
+
hostname: "shop.example.com",
|
|
567
|
+
},
|
|
568
|
+
writable: true,
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
const config = {
|
|
572
|
+
metadata: {},
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
// First call
|
|
576
|
+
const result1 = await resolveMerchantId(config);
|
|
577
|
+
expect(result1).toBe("merchant-cached-resolve");
|
|
578
|
+
|
|
579
|
+
// Second call should use cache
|
|
580
|
+
global.fetch = vi.fn();
|
|
581
|
+
const result2 = await resolveMerchantId(config);
|
|
582
|
+
expect(result2).toBe("merchant-cached-resolve");
|
|
583
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
describe("integration scenarios", () => {
|
|
588
|
+
it("should handle multiple different domains", async () => {
|
|
589
|
+
const mockResponse1 = {
|
|
590
|
+
merchantId: "merchant-domain1",
|
|
591
|
+
name: "Merchant 1",
|
|
592
|
+
domain: "shop1.example.com",
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
const mockResponse2 = {
|
|
596
|
+
merchantId: "merchant-domain2",
|
|
597
|
+
name: "Merchant 2",
|
|
598
|
+
domain: "shop2.example.com",
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
global.fetch = vi
|
|
602
|
+
.fn()
|
|
603
|
+
.mockResolvedValueOnce({
|
|
604
|
+
ok: true,
|
|
605
|
+
json: async () => mockResponse1,
|
|
606
|
+
})
|
|
607
|
+
.mockResolvedValueOnce({
|
|
608
|
+
ok: true,
|
|
609
|
+
json: async () => mockResponse2,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
const result1 = await fetchMerchantId("shop1.example.com");
|
|
613
|
+
expect(result1).toBe("merchant-domain1");
|
|
614
|
+
|
|
615
|
+
clearMerchantIdCache();
|
|
616
|
+
|
|
617
|
+
const result2 = await fetchMerchantId("shop2.example.com");
|
|
618
|
+
expect(result2).toBe("merchant-domain2");
|
|
619
|
+
|
|
620
|
+
expect(global.fetch).toHaveBeenCalledTimes(2);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it("should handle rapid successive calls with cache", async () => {
|
|
624
|
+
const mockResponse = {
|
|
625
|
+
merchantId: "merchant-rapid",
|
|
626
|
+
name: "Test Merchant",
|
|
627
|
+
domain: "shop.example.com",
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
631
|
+
ok: true,
|
|
632
|
+
json: async () => mockResponse,
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
const results = await Promise.all([
|
|
636
|
+
fetchMerchantId("shop.example.com"),
|
|
637
|
+
fetchMerchantId("shop.example.com"),
|
|
638
|
+
fetchMerchantId("shop.example.com"),
|
|
639
|
+
fetchMerchantId("shop.example.com"),
|
|
640
|
+
fetchMerchantId("shop.example.com"),
|
|
641
|
+
]);
|
|
642
|
+
|
|
643
|
+
expect(results).toEqual([
|
|
644
|
+
"merchant-rapid",
|
|
645
|
+
"merchant-rapid",
|
|
646
|
+
"merchant-rapid",
|
|
647
|
+
"merchant-rapid",
|
|
648
|
+
"merchant-rapid",
|
|
649
|
+
]);
|
|
650
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
});
|