@abdssamie/adyen-payments 0.1.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/LICENSE +201 -0
- package/README.md +258 -0
- package/dist/client/_generated/_ignore.d.ts +1 -0
- package/dist/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/client/_generated/_ignore.js +3 -0
- package/dist/client/_generated/_ignore.js.map +1 -0
- package/dist/client/index.d.ts +206 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +566 -0
- package/dist/client/index.js.map +1 -0
- package/dist/component/_generated/api.d.ts +36 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +215 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/private.d.ts +71 -0
- package/dist/component/private.d.ts.map +1 -0
- package/dist/component/private.js +250 -0
- package/dist/component/private.js.map +1 -0
- package/dist/component/public.d.ts +170 -0
- package/dist/component/public.d.ts.map +1 -0
- package/dist/component/public.js +210 -0
- package/dist/component/public.js.map +1 -0
- package/dist/component/schema.d.ts +101 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +63 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/react/hooks.d.ts +182 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +215 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +104 -0
- package/src/client/_generated/_ignore.ts +1 -0
- package/src/client/index.test.ts +196 -0
- package/src/client/index.ts +823 -0
- package/src/client/setup.test.ts +26 -0
- package/src/client/webhooks.test.ts +182 -0
- package/src/component/_generated/api.ts +52 -0
- package/src/component/_generated/component.ts +293 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/private.ts +277 -0
- package/src/component/public.test.ts +92 -0
- package/src/component/public.ts +229 -0
- package/src/component/schema.ts +67 -0
- package/src/component/setup.test.ts +11 -0
- package/src/react/hooks.ts +488 -0
- package/src/react/index.ts +18 -0
- package/src/test.ts +18 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useAction, useQuery } from "convex/react";
|
|
5
|
+
import type {
|
|
6
|
+
FunctionReference,
|
|
7
|
+
} from "convex/server";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Shared Types
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export interface StoredCard {
|
|
14
|
+
shopperReference: string;
|
|
15
|
+
recurringDetailReference: string;
|
|
16
|
+
variant: string;
|
|
17
|
+
cardLast4?: string;
|
|
18
|
+
cardExpiryMonth?: string;
|
|
19
|
+
cardExpiryYear?: string;
|
|
20
|
+
status: string;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PaymentTransaction {
|
|
25
|
+
pspReference: string;
|
|
26
|
+
originalReference?: string;
|
|
27
|
+
shopperReference?: string;
|
|
28
|
+
merchantReference: string;
|
|
29
|
+
amount: number;
|
|
30
|
+
currency: string;
|
|
31
|
+
status: string;
|
|
32
|
+
paymentMethod?: string;
|
|
33
|
+
created: number;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Config shape required by all hooks
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The Convex API references the hooks need.
|
|
43
|
+
* Pass in the `api` object generated for your example app.
|
|
44
|
+
*
|
|
45
|
+
* Example:
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { api } from "../convex/_generated/api";
|
|
48
|
+
* const config: AdyenHooksConfig = { api };
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export interface AdyenHooksConfig {
|
|
52
|
+
queries: {
|
|
53
|
+
getShopper: FunctionReference<"query">;
|
|
54
|
+
listPaymentMethods: FunctionReference<"query">;
|
|
55
|
+
listPayments: FunctionReference<"query">;
|
|
56
|
+
getPayment: FunctionReference<"query">;
|
|
57
|
+
};
|
|
58
|
+
actions: {
|
|
59
|
+
getOrCreateShopper: FunctionReference<"action">;
|
|
60
|
+
createCheckout: FunctionReference<"action">;
|
|
61
|
+
syncPaymentMethods: FunctionReference<"action">;
|
|
62
|
+
deletePaymentMethod: FunctionReference<"action">;
|
|
63
|
+
chargeCard: FunctionReference<"action">;
|
|
64
|
+
capture: FunctionReference<"action">;
|
|
65
|
+
refund: FunctionReference<"action">;
|
|
66
|
+
cancel: FunctionReference<"action">;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// useAdyenShopper
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
interface UseAdyenShopperOptions {
|
|
75
|
+
shopperReference: string;
|
|
76
|
+
config: AdyenHooksConfig;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface UseAdyenShopperResult {
|
|
80
|
+
shopper: Record<string, unknown> | null | undefined;
|
|
81
|
+
isLoading: boolean;
|
|
82
|
+
error: string | null;
|
|
83
|
+
register: (args: {
|
|
84
|
+
userId: string;
|
|
85
|
+
email?: string;
|
|
86
|
+
name?: string;
|
|
87
|
+
}) => Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Manages a shopper identity: reads their record from Convex and provides
|
|
92
|
+
* a `register` function to upsert them via the Adyen component.
|
|
93
|
+
*/
|
|
94
|
+
export function useAdyenShopper({
|
|
95
|
+
shopperReference,
|
|
96
|
+
config,
|
|
97
|
+
}: UseAdyenShopperOptions): UseAdyenShopperResult {
|
|
98
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
99
|
+
const [error, setError] = useState<string | null>(null);
|
|
100
|
+
|
|
101
|
+
const shopper = useQuery(config.queries.getShopper, { shopperReference }) as
|
|
102
|
+
| Record<string, unknown>
|
|
103
|
+
| null
|
|
104
|
+
| undefined;
|
|
105
|
+
|
|
106
|
+
const getOrCreateShopperAction = useAction(config.actions.getOrCreateShopper);
|
|
107
|
+
|
|
108
|
+
const register = useCallback(
|
|
109
|
+
async (args: { userId: string; email?: string; name?: string }) => {
|
|
110
|
+
setIsLoading(true);
|
|
111
|
+
setError(null);
|
|
112
|
+
try {
|
|
113
|
+
await getOrCreateShopperAction(args);
|
|
114
|
+
} catch (err: unknown) {
|
|
115
|
+
const message = err instanceof Error ? err.message : "Failed to register shopper";
|
|
116
|
+
setError(message);
|
|
117
|
+
throw err;
|
|
118
|
+
} finally {
|
|
119
|
+
setIsLoading(false);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
[getOrCreateShopperAction]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return { shopper, isLoading, error, register };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// useStoredPaymentMethods
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
interface UseStoredPaymentMethodsOptions {
|
|
133
|
+
shopperReference: string;
|
|
134
|
+
config: AdyenHooksConfig;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface UseStoredPaymentMethodsResult {
|
|
138
|
+
paymentMethods: StoredCard[];
|
|
139
|
+
isLoading: boolean;
|
|
140
|
+
isSyncing: boolean;
|
|
141
|
+
error: string | null;
|
|
142
|
+
sync: () => Promise<void>;
|
|
143
|
+
remove: (recurringDetailReference: string) => Promise<void>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Reads stored (tokenised) payment methods for a shopper from Convex,
|
|
148
|
+
* and provides helpers to sync from Adyen or delete a token.
|
|
149
|
+
*/
|
|
150
|
+
export function useStoredPaymentMethods({
|
|
151
|
+
shopperReference,
|
|
152
|
+
config,
|
|
153
|
+
}: UseStoredPaymentMethodsOptions): UseStoredPaymentMethodsResult {
|
|
154
|
+
const [isSyncing, setIsSyncing] = useState(false);
|
|
155
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
156
|
+
const [error, setError] = useState<string | null>(null);
|
|
157
|
+
|
|
158
|
+
const paymentMethods = (useQuery(config.queries.listPaymentMethods, {
|
|
159
|
+
shopperReference,
|
|
160
|
+
}) ?? []) as StoredCard[];
|
|
161
|
+
|
|
162
|
+
const syncAction = useAction(config.actions.syncPaymentMethods);
|
|
163
|
+
const deleteAction = useAction(config.actions.deletePaymentMethod);
|
|
164
|
+
|
|
165
|
+
const sync = useCallback(async () => {
|
|
166
|
+
setIsSyncing(true);
|
|
167
|
+
setError(null);
|
|
168
|
+
try {
|
|
169
|
+
await syncAction({ shopperReference });
|
|
170
|
+
} catch (err: unknown) {
|
|
171
|
+
const message = err instanceof Error ? err.message : "Failed to sync payment methods";
|
|
172
|
+
setError(message);
|
|
173
|
+
throw err;
|
|
174
|
+
} finally {
|
|
175
|
+
setIsSyncing(false);
|
|
176
|
+
}
|
|
177
|
+
}, [syncAction, shopperReference]);
|
|
178
|
+
|
|
179
|
+
const remove = useCallback(
|
|
180
|
+
async (recurringDetailReference: string) => {
|
|
181
|
+
setIsLoading(true);
|
|
182
|
+
setError(null);
|
|
183
|
+
try {
|
|
184
|
+
await deleteAction({ shopperReference, recurringDetailReference });
|
|
185
|
+
} catch (err: unknown) {
|
|
186
|
+
const message = err instanceof Error ? err.message : "Failed to remove payment method";
|
|
187
|
+
setError(message);
|
|
188
|
+
throw err;
|
|
189
|
+
} finally {
|
|
190
|
+
setIsLoading(false);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
[deleteAction, shopperReference]
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return { paymentMethods, isLoading, isSyncing, error, sync, remove };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// usePayments
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
interface UsePaymentsOptions {
|
|
204
|
+
shopperReference: string;
|
|
205
|
+
config: AdyenHooksConfig;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
interface UsePaymentsResult {
|
|
209
|
+
payments: PaymentTransaction[];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Reactive list of payment transactions for a shopper, automatically kept
|
|
214
|
+
* up to date by Convex's live query subscription.
|
|
215
|
+
*/
|
|
216
|
+
export function usePayments({
|
|
217
|
+
shopperReference,
|
|
218
|
+
config,
|
|
219
|
+
}: UsePaymentsOptions): UsePaymentsResult {
|
|
220
|
+
const payments = (useQuery(config.queries.listPayments, {
|
|
221
|
+
shopperReference,
|
|
222
|
+
}) ?? []) as PaymentTransaction[];
|
|
223
|
+
|
|
224
|
+
return { payments };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// usePaymentOperations
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
export interface PaymentOperations {
|
|
232
|
+
chargeCard: (args: {
|
|
233
|
+
shopperReference: string;
|
|
234
|
+
recurringDetailReference: string;
|
|
235
|
+
amount: number;
|
|
236
|
+
currency: string;
|
|
237
|
+
}) => Promise<{ pspReference: string | null; status: string; resultCode: string }>;
|
|
238
|
+
capture: (args: {
|
|
239
|
+
pspReference: string;
|
|
240
|
+
amount: number;
|
|
241
|
+
currency: string;
|
|
242
|
+
}) => Promise<void>;
|
|
243
|
+
refund: (args: {
|
|
244
|
+
pspReference: string;
|
|
245
|
+
amount: number;
|
|
246
|
+
currency: string;
|
|
247
|
+
}) => Promise<void>;
|
|
248
|
+
cancel: (args: { pspReference: string }) => Promise<void>;
|
|
249
|
+
createCheckoutSession: (args: {
|
|
250
|
+
amount: number;
|
|
251
|
+
currency: string;
|
|
252
|
+
shopperReference?: string;
|
|
253
|
+
}) => Promise<{ sessionId: string; sessionData: string; url: string | null }>;
|
|
254
|
+
isLoading: (key: string) => boolean;
|
|
255
|
+
error: string | null;
|
|
256
|
+
clearError: () => void;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Provides callable async helpers for all payment lifecycle operations —
|
|
261
|
+
* checkout session creation, MIT charges, captures, refunds, and cancellations.
|
|
262
|
+
* Tracks per-operation loading state via a string key.
|
|
263
|
+
*/
|
|
264
|
+
export function usePaymentOperations(config: AdyenHooksConfig): PaymentOperations {
|
|
265
|
+
const [loadingKeys, setLoadingKeys] = useState<Set<string>>(new Set());
|
|
266
|
+
const [error, setError] = useState<string | null>(null);
|
|
267
|
+
|
|
268
|
+
const chargeCardAction = useAction(config.actions.chargeCard);
|
|
269
|
+
const captureAction = useAction(config.actions.capture);
|
|
270
|
+
const refundAction = useAction(config.actions.refund);
|
|
271
|
+
const cancelAction = useAction(config.actions.cancel);
|
|
272
|
+
const createCheckoutAction = useAction(config.actions.createCheckout);
|
|
273
|
+
|
|
274
|
+
const withLoading = useCallback(
|
|
275
|
+
async <T>(key: string, fn: () => Promise<T>): Promise<T> => {
|
|
276
|
+
setLoadingKeys((prev) => new Set(prev).add(key));
|
|
277
|
+
setError(null);
|
|
278
|
+
try {
|
|
279
|
+
return await fn();
|
|
280
|
+
} catch (err: unknown) {
|
|
281
|
+
const message = err instanceof Error ? err.message : "An error occurred";
|
|
282
|
+
setError(message);
|
|
283
|
+
throw err;
|
|
284
|
+
} finally {
|
|
285
|
+
setLoadingKeys((prev) => {
|
|
286
|
+
const next = new Set(prev);
|
|
287
|
+
next.delete(key);
|
|
288
|
+
return next;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
[]
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const chargeCard = useCallback(
|
|
296
|
+
(args: {
|
|
297
|
+
shopperReference: string;
|
|
298
|
+
recurringDetailReference: string;
|
|
299
|
+
amount: number;
|
|
300
|
+
currency: string;
|
|
301
|
+
}) =>
|
|
302
|
+
withLoading(`charge:${args.recurringDetailReference}`, () =>
|
|
303
|
+
chargeCardAction(args) as Promise<{
|
|
304
|
+
pspReference: string | null;
|
|
305
|
+
status: string;
|
|
306
|
+
resultCode: string;
|
|
307
|
+
}>
|
|
308
|
+
),
|
|
309
|
+
[chargeCardAction, withLoading]
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const capture = useCallback(
|
|
313
|
+
(args: { pspReference: string; amount: number; currency: string }) =>
|
|
314
|
+
withLoading(`capture:${args.pspReference}`, () =>
|
|
315
|
+
captureAction(args).then(() => undefined)
|
|
316
|
+
),
|
|
317
|
+
[captureAction, withLoading]
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const refund = useCallback(
|
|
321
|
+
(args: { pspReference: string; amount: number; currency: string }) =>
|
|
322
|
+
withLoading(`refund:${args.pspReference}`, () =>
|
|
323
|
+
refundAction(args).then(() => undefined)
|
|
324
|
+
),
|
|
325
|
+
[refundAction, withLoading]
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const cancel = useCallback(
|
|
329
|
+
(args: { pspReference: string }) =>
|
|
330
|
+
withLoading(`cancel:${args.pspReference}`, () =>
|
|
331
|
+
cancelAction(args).then(() => undefined)
|
|
332
|
+
),
|
|
333
|
+
[cancelAction, withLoading]
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const createCheckoutSession = useCallback(
|
|
337
|
+
(args: { amount: number; currency: string; shopperReference?: string }) =>
|
|
338
|
+
withLoading("checkout", () =>
|
|
339
|
+
createCheckoutAction(args) as Promise<{
|
|
340
|
+
sessionId: string;
|
|
341
|
+
sessionData: string;
|
|
342
|
+
url: string | null;
|
|
343
|
+
}>
|
|
344
|
+
),
|
|
345
|
+
[createCheckoutAction, withLoading]
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const isLoading = useCallback(
|
|
349
|
+
(key: string) => loadingKeys.has(key),
|
|
350
|
+
[loadingKeys]
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const clearError = useCallback(() => setError(null), []);
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
chargeCard,
|
|
357
|
+
capture,
|
|
358
|
+
refund,
|
|
359
|
+
cancel,
|
|
360
|
+
createCheckoutSession,
|
|
361
|
+
isLoading,
|
|
362
|
+
error,
|
|
363
|
+
clearError,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// useAdyenDropin
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
export interface UseAdyenDropinOptions {
|
|
372
|
+
/** Adyen client key (ADYEN_CLIENT_KEY) — safe to expose on the frontend */
|
|
373
|
+
clientKey: string;
|
|
374
|
+
/** Session ID returned by createCheckoutSession */
|
|
375
|
+
sessionId: string | null;
|
|
376
|
+
/** Session data blob returned by createCheckoutSession */
|
|
377
|
+
sessionData: string | null;
|
|
378
|
+
/** Environment: "TEST" or "LIVE" */
|
|
379
|
+
environment?: "test" | "live";
|
|
380
|
+
/** Shopper country code (e.g. "US", "NL") — required by some payment methods */
|
|
381
|
+
countryCode?: string;
|
|
382
|
+
/** Callback when Adyen reports payment completion */
|
|
383
|
+
onPaymentCompleted?: (result: { resultCode: string }) => void;
|
|
384
|
+
/** Callback when Adyen reports an error */
|
|
385
|
+
onError?: (error: { name: string; message: string }) => void;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export interface UseAdyenDropinResult {
|
|
389
|
+
/** Ref to attach to a container div — Adyen mounts the Drop-in here */
|
|
390
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
391
|
+
isReady: boolean;
|
|
392
|
+
mountError: string | null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Mounts the Adyen Drop-in UI component into a provided container div.
|
|
397
|
+
* Automatically initialises or re-initialises when sessionId/sessionData changes.
|
|
398
|
+
*
|
|
399
|
+
* Usage:
|
|
400
|
+
* ```tsx
|
|
401
|
+
* const { containerRef } = useAdyenDropin({ clientKey, sessionId, sessionData });
|
|
402
|
+
* return <div ref={containerRef} />;
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
export function useAdyenDropin({
|
|
406
|
+
clientKey,
|
|
407
|
+
sessionId,
|
|
408
|
+
sessionData,
|
|
409
|
+
environment = "test",
|
|
410
|
+
countryCode,
|
|
411
|
+
onPaymentCompleted,
|
|
412
|
+
onError,
|
|
413
|
+
}: UseAdyenDropinOptions): UseAdyenDropinResult {
|
|
414
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
415
|
+
const dropinRef = useRef<{ unmount?: () => void } | null>(null);
|
|
416
|
+
const [isReady, setIsReady] = useState(false);
|
|
417
|
+
const [mountError, setMountError] = useState<string | null>(null);
|
|
418
|
+
const onPaymentCompletedRef = useRef(onPaymentCompleted);
|
|
419
|
+
const onErrorRef = useRef(onError);
|
|
420
|
+
|
|
421
|
+
useEffect(() => {
|
|
422
|
+
onPaymentCompletedRef.current = onPaymentCompleted;
|
|
423
|
+
onErrorRef.current = onError;
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
useEffect(() => {
|
|
427
|
+
if (!sessionId || !sessionData || !containerRef.current || !clientKey) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
let cancelled = false;
|
|
432
|
+
|
|
433
|
+
const mountDropin = async () => {
|
|
434
|
+
try {
|
|
435
|
+
// Dynamically import to avoid SSR issues and keep bundle lean
|
|
436
|
+
const { AdyenCheckout, Dropin, Card } = await import("@adyen/adyen-web");
|
|
437
|
+
|
|
438
|
+
if (cancelled || !containerRef.current) return;
|
|
439
|
+
|
|
440
|
+
// Unmount previous instance if any
|
|
441
|
+
if (dropinRef.current?.unmount) {
|
|
442
|
+
dropinRef.current.unmount();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const checkout = await AdyenCheckout({
|
|
446
|
+
environment,
|
|
447
|
+
clientKey,
|
|
448
|
+
countryCode,
|
|
449
|
+
session: { id: sessionId, sessionData },
|
|
450
|
+
onPaymentCompleted: (result: { resultCode: string }) => {
|
|
451
|
+
if (!cancelled) onPaymentCompletedRef.current?.(result);
|
|
452
|
+
},
|
|
453
|
+
onError: (err: { name: string; message: string }) => {
|
|
454
|
+
if (!cancelled) onErrorRef.current?.(err);
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
if (cancelled || !containerRef.current) return;
|
|
459
|
+
|
|
460
|
+
// v6 API: instantiate Dropin with the Core instance, then mount
|
|
461
|
+
const dropin = new Dropin(checkout, {
|
|
462
|
+
paymentMethodComponents: [Card],
|
|
463
|
+
}).mount(containerRef.current);
|
|
464
|
+
dropinRef.current = dropin as { unmount?: () => void };
|
|
465
|
+
if (!cancelled) setIsReady(true);
|
|
466
|
+
} catch (err: unknown) {
|
|
467
|
+
if (!cancelled) {
|
|
468
|
+
const message = err instanceof Error ? err.message : "Failed to mount Adyen Drop-in";
|
|
469
|
+
setMountError(message);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
setIsReady(false);
|
|
475
|
+
setMountError(null);
|
|
476
|
+
void mountDropin();
|
|
477
|
+
|
|
478
|
+
return () => {
|
|
479
|
+
cancelled = true;
|
|
480
|
+
if (dropinRef.current?.unmount) {
|
|
481
|
+
dropinRef.current.unmount();
|
|
482
|
+
dropinRef.current = null;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
}, [clientKey, sessionId, sessionData, environment, countryCode]);
|
|
486
|
+
|
|
487
|
+
return { containerRef, isReady, mountError };
|
|
488
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
useAdyenShopper,
|
|
5
|
+
useStoredPaymentMethods,
|
|
6
|
+
usePayments,
|
|
7
|
+
usePaymentOperations,
|
|
8
|
+
useAdyenDropin,
|
|
9
|
+
} from "./hooks.js";
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
AdyenHooksConfig,
|
|
13
|
+
StoredCard,
|
|
14
|
+
PaymentTransaction,
|
|
15
|
+
PaymentOperations,
|
|
16
|
+
UseAdyenDropinOptions,
|
|
17
|
+
UseAdyenDropinResult,
|
|
18
|
+
} from "./hooks.js";
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
import type { TestConvex } from "convex-test";
|
|
3
|
+
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
4
|
+
import schema from "./component/schema.js";
|
|
5
|
+
const modules = import.meta.glob("./component/**/*.ts");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register the component with the test convex instance.
|
|
9
|
+
* @param t - The test convex instance, e.g. from calling `convexTest`.
|
|
10
|
+
* @param name - The name of the component, as registered in convex.config.ts.
|
|
11
|
+
*/
|
|
12
|
+
export function register(
|
|
13
|
+
t: TestConvex<SchemaDefinition<GenericSchema, boolean>>,
|
|
14
|
+
name: string = "adyenPayments",
|
|
15
|
+
) {
|
|
16
|
+
t.registerComponent(name, schema, modules);
|
|
17
|
+
}
|
|
18
|
+
export default { register, schema, modules };
|