@verbb/formie-browser 1.0.10 → 1.0.12
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/CHANGELOG.md +17 -0
- package/dist/chunks/{api-BYe9YPko.js → api-DqpfkZWL.js} +15 -2
- package/dist/chunks/{bpoint-C0VOftX9.js → bpoint-Dlq34XH1.js} +1 -1
- package/dist/chunks/{calculations-HAZ8wUrT.js → calculations-DERi2xaC.js} +8 -8
- package/dist/chunks/{conditions-BiA_uANV.js → conditions-65kBcFWW.js} +1 -1
- package/dist/chunks/{dist-BaHXfvBO.js → dist-BdUy8m_p.js} +2 -2
- package/dist/chunks/{eway-CS6Y1Yvo.js → eway-D9_m8ooj.js} +1 -1
- package/dist/chunks/{go-cardless-BTPD668l.js → go-cardless-DAT3ZaQf.js} +1 -1
- package/dist/chunks/{mollie-CXgBwvo6.js → mollie-Bibhl67p.js} +1 -1
- package/dist/chunks/{moneris-B6oLu7nj.js → moneris-H2wSqk9h.js} +1 -1
- package/dist/chunks/opayo-CR0DtsLa.js +179 -0
- package/dist/chunks/{paddle-GRlsm5mm.js → paddle-DkvIxQdG.js} +1 -1
- package/dist/chunks/{password-validation-D5AirwUM.js → password-validation-T0vDQPCr.js} +1 -1
- package/dist/chunks/{paypal-CdcZnf6r.js → paypal-CQpAF7Ua.js} +32 -31
- package/dist/chunks/{payway-CfmYOS__.js → payway-CwDkC7YZ.js} +1 -1
- package/dist/chunks/{square-BY--5M-X.js → square-DwdcOI1G.js} +1 -1
- package/dist/chunks/{stripe-uusYjWty.js → stripe-DaoG2FT1.js} +1 -1
- package/dist/chunks/{text-limit-nSATKyhp.js → text-limit-Bn18WrPU.js} +1 -1
- package/dist/css/theme/fields/_check-radio.css +3 -1
- package/dist/css/theme/integrations/_opayo.css +9 -0
- package/dist/index.js +16 -16
- package/dist/js/modules/payments/factories.d.ts +5 -1
- package/dist/js/modules/payments/factories.d.ts.map +1 -1
- package/dist/js/modules/payments/opayo.d.ts +17 -2
- package/dist/js/modules/payments/opayo.d.ts.map +1 -1
- package/dist/js/modules/payments/paypal.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/css/theme/fields/_check-radio.css +3 -1
- package/src/css/theme/integrations/_opayo.css +9 -0
- package/src/js/modules/payments/factories.ts +27 -2
- package/src/js/modules/payments/opayo.ts +256 -69
- package/src/js/modules/payments/paypal.ts +23 -3
- package/dist/chunks/opayo-DPUA5CKr.js +0 -113
|
@@ -5,7 +5,7 @@ type SagePayTokeniseResult = {
|
|
|
5
5
|
message: string;
|
|
6
6
|
}>;
|
|
7
7
|
};
|
|
8
|
-
type
|
|
8
|
+
type SagePayOwnFormGlobal = {
|
|
9
9
|
(opts: {
|
|
10
10
|
merchantSessionKey: string;
|
|
11
11
|
}): {
|
|
@@ -15,9 +15,24 @@ type SagePayGlobal = {
|
|
|
15
15
|
}) => void;
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
|
+
type SagePayCheckoutInstance = {
|
|
19
|
+
tokenise: (opts?: {
|
|
20
|
+
newMerchantSessionKey?: string;
|
|
21
|
+
}) => void;
|
|
22
|
+
destroy: () => void;
|
|
23
|
+
};
|
|
24
|
+
type SagePayCheckoutGlobal = {
|
|
25
|
+
(opts: {
|
|
26
|
+
merchantSessionKey: string;
|
|
27
|
+
containerSelector: string;
|
|
28
|
+
onTokenise?: (result: SagePayTokeniseResult) => void;
|
|
29
|
+
reusableCardIdentifier?: string;
|
|
30
|
+
}): SagePayCheckoutInstance;
|
|
31
|
+
};
|
|
18
32
|
declare global {
|
|
19
33
|
interface Window {
|
|
20
|
-
sagepayOwnForm?:
|
|
34
|
+
sagepayOwnForm?: SagePayOwnFormGlobal;
|
|
35
|
+
sagepayCheckout?: SagePayCheckoutGlobal;
|
|
21
36
|
}
|
|
22
37
|
}
|
|
23
38
|
export declare const opayoModule: import("../../..").FormieModuleDefinition;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opayo.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/payments/opayo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"opayo.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/payments/opayo.ts"],"names":[],"mappings":"AAUA,KAAK,qBAAqB,GAAG;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvC,CAAC;AAEF,KAAK,oBAAoB,GAAG;IACxB,CAAC,IAAI,EAAE;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,GAAG;QACpC,mBAAmB,EAAE,CAAC,IAAI,EAAE;YACxB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACpC,WAAW,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;SACxD,KAAK,IAAI,CAAC;KACd,CAAC;CACL,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC3B,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,qBAAqB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9D,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IACzB,CAAC,IAAI,EAAE;QACH,kBAAkB,EAAE,MAAM,CAAC;QAC3B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;QACrD,sBAAsB,CAAC,EAAE,MAAM,CAAC;KACnC,GAAG,uBAAuB,CAAC;CAC/B,CAAC;AAEF,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,cAAc,CAAC,EAAE,oBAAoB,CAAC;QACtC,eAAe,CAAC,EAAE,qBAAqB,CAAC;KAC3C;CACJ;AAwFD,eAAO,MAAM,WAAW,2CAsUtB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paypal.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/payments/paypal.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"paypal.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/payments/paypal.ts"],"names":[],"mappings":"AAqFA,eAAO,MAAM,YAAY,2CAoJvB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verbb/formie-browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@placekit/autocomplete-js": "^2.2.1",
|
|
71
71
|
"@uppy/core": "^5.2.0",
|
|
72
72
|
"@uppy/xhr-upload": "^5.2.0",
|
|
73
|
-
"@verbb/formie-core": "1.0.
|
|
73
|
+
"@verbb/formie-core": "1.0.12",
|
|
74
74
|
"expression-language": "^2.5.4",
|
|
75
75
|
"flatpickr": "^4.6.13",
|
|
76
76
|
"friendly-challenge": "^0.9.19",
|
|
@@ -116,8 +116,10 @@
|
|
|
116
116
|
|
|
117
117
|
.formie-field-option:has(> input[data-formie-other-option]),
|
|
118
118
|
.formie-other-option {
|
|
119
|
+
display: flex;
|
|
119
120
|
flex-wrap: wrap;
|
|
120
|
-
align-items:
|
|
121
|
+
align-items: center;
|
|
122
|
+
width: 100%;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
.formie-field-option:has(> input[data-formie-other-option]:checked) > .formie-other-option-text,
|
|
@@ -33,6 +33,11 @@ type PaymentSetupResult = {
|
|
|
33
33
|
onBeforeStage?: (stageCtx: import('#contracts/modules').SubmitHookContext) => void | Promise<void>;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
export type PaymentAfterSubmitResult = {
|
|
37
|
+
/** Tear down the current widget and mount again (for example after a failed payment). */
|
|
38
|
+
remount?: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
36
41
|
export type ManagedPaymentModuleAdapter<
|
|
37
42
|
TProvider extends Record<string, unknown>,
|
|
38
43
|
TApi,
|
|
@@ -74,7 +79,7 @@ export type ManagedPaymentModuleAdapter<
|
|
|
74
79
|
options: NormalizedPaymentModuleOptions<TProvider>;
|
|
75
80
|
provider: TProvider;
|
|
76
81
|
result?: import('#contracts/schema').FormSubmitResult;
|
|
77
|
-
}) => void | Promise<void>;
|
|
82
|
+
}) => void | Promise<void> | PaymentAfterSubmitResult | Promise<PaymentAfterSubmitResult>;
|
|
78
83
|
};
|
|
79
84
|
|
|
80
85
|
export function createManagedPaymentModule<
|
|
@@ -318,13 +323,33 @@ export function createManagedPaymentModule<
|
|
|
318
323
|
return;
|
|
319
324
|
}
|
|
320
325
|
|
|
321
|
-
await adapter.onAfterSubmit({
|
|
326
|
+
const afterSubmitResult = await adapter.onAfterSubmit({
|
|
322
327
|
field: ctx.target,
|
|
323
328
|
services,
|
|
324
329
|
options,
|
|
325
330
|
provider: options.provider,
|
|
326
331
|
result,
|
|
327
332
|
});
|
|
333
|
+
|
|
334
|
+
if (!afterSubmitResult?.remount || !adapter.mount) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (widget && adapter.unmount) {
|
|
339
|
+
const api = await getApi();
|
|
340
|
+
|
|
341
|
+
await adapter.unmount({
|
|
342
|
+
api,
|
|
343
|
+
widget,
|
|
344
|
+
field: ctx.target,
|
|
345
|
+
services,
|
|
346
|
+
options,
|
|
347
|
+
provider: options.provider,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
widget = null;
|
|
352
|
+
await ensureMounted();
|
|
328
353
|
},
|
|
329
354
|
};
|
|
330
355
|
},
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
import opayoCss from '#theme-css/integrations/_opayo.css?inline';
|
|
2
|
+
|
|
1
3
|
import { definePaymentModule } from '#modules/payments/api';
|
|
4
|
+
import { ensureModuleStyles } from '#modules/styles';
|
|
2
5
|
import { createDebug } from '#utils/debug';
|
|
3
6
|
import { getPaymentProviderActionEventName } from '#utils/event-names';
|
|
4
7
|
import { loadScriptAndEnsureGlobal } from '#utils/scripts';
|
|
5
8
|
|
|
9
|
+
ensureModuleStyles('opayo', [opayoCss]);
|
|
10
|
+
|
|
6
11
|
type SagePayTokeniseResult = {
|
|
7
12
|
success: boolean;
|
|
8
13
|
cardIdentifier?: string;
|
|
9
14
|
errors?: Array<{ message: string }>;
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
type
|
|
17
|
+
type SagePayOwnFormGlobal = {
|
|
13
18
|
(opts: { merchantSessionKey: string }): {
|
|
14
19
|
tokeniseCardDetails: (opts: {
|
|
15
20
|
cardDetails: Record<string, string>;
|
|
@@ -18,34 +23,123 @@ type SagePayGlobal = {
|
|
|
18
23
|
};
|
|
19
24
|
};
|
|
20
25
|
|
|
26
|
+
type SagePayCheckoutInstance = {
|
|
27
|
+
tokenise: (opts?: { newMerchantSessionKey?: string }) => void;
|
|
28
|
+
destroy: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type SagePayCheckoutGlobal = {
|
|
32
|
+
(opts: {
|
|
33
|
+
merchantSessionKey: string;
|
|
34
|
+
containerSelector: string;
|
|
35
|
+
onTokenise?: (result: SagePayTokeniseResult) => void;
|
|
36
|
+
reusableCardIdentifier?: string;
|
|
37
|
+
}): SagePayCheckoutInstance;
|
|
38
|
+
};
|
|
39
|
+
|
|
21
40
|
declare global {
|
|
22
41
|
interface Window {
|
|
23
|
-
sagepayOwnForm?:
|
|
42
|
+
sagepayOwnForm?: SagePayOwnFormGlobal;
|
|
43
|
+
sagepayCheckout?: SagePayCheckoutGlobal;
|
|
24
44
|
}
|
|
25
45
|
}
|
|
26
46
|
|
|
47
|
+
type OpayoCheckoutMode = 'ownForm' | 'dropIn';
|
|
48
|
+
|
|
27
49
|
type OpayoProviderOptions = {
|
|
28
50
|
useSandbox?: boolean;
|
|
29
51
|
handle?: string;
|
|
30
52
|
sessionToken?: string | null;
|
|
53
|
+
checkoutMode?: OpayoCheckoutMode;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type OpayoDropInWidget = {
|
|
57
|
+
checkout: SagePayCheckoutInstance;
|
|
58
|
+
merchantSessionKey: string;
|
|
59
|
+
pendingAuthorize: ((success: boolean) => void) | null;
|
|
60
|
+
retriedTokenise: boolean;
|
|
31
61
|
};
|
|
32
62
|
|
|
33
63
|
const SCRIPT_ID = 'FORMIE_OPAYO_SCRIPT';
|
|
34
64
|
const SCRIPT_SRC_LIVE = 'https://live.opayo.eu.elavon.com/api/v1/js/sagepay.js';
|
|
35
65
|
const SCRIPT_SRC_SANDBOX = 'https://sandbox.opayo.eu.elavon.com/api/v1/js/sagepay.js';
|
|
66
|
+
const DROP_IN_SELECTOR = '[data-formie-opayo-drop-in]';
|
|
36
67
|
const debug = createDebug('payments', 'opayo');
|
|
37
68
|
const CHALLENGE_EVENT = getPaymentProviderActionEventName('opayo', 'challenge');
|
|
38
69
|
const CHALLENGE_RESPONSE_MESSAGE = 'formie:payment:opayo:challenge:response';
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
function isDropInCheckoutMode(provider: OpayoProviderOptions): boolean {
|
|
72
|
+
return provider.checkoutMode === 'dropIn';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function requestMerchantSessionKey(args: {
|
|
76
|
+
form: HTMLFormElement;
|
|
77
|
+
handle: string;
|
|
78
|
+
sessionToken: string;
|
|
79
|
+
services: {
|
|
80
|
+
addError: (message: string) => void;
|
|
81
|
+
};
|
|
82
|
+
}): Promise<string | null> {
|
|
83
|
+
const { form, handle, sessionToken, services } = args;
|
|
84
|
+
const formData = new FormData();
|
|
85
|
+
formData.append('action', 'formie/payment-webhooks/process-callback');
|
|
86
|
+
formData.append('merchantSessionKey', 'true');
|
|
87
|
+
formData.append('handle', handle);
|
|
88
|
+
formData.append('sessionToken', sessionToken);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetch(form.action, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
body: formData,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (res.status < 200 || res.status >= 300) {
|
|
97
|
+
services.addError(`${res.status}: ${res.statusText}`);
|
|
98
|
+
debug.warn('Merchant session request failed.', {
|
|
99
|
+
status: res.status,
|
|
100
|
+
statusText: res.statusText,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const response = await res.json();
|
|
107
|
+
const merchantSessionKey = response.merchantSessionKey;
|
|
108
|
+
|
|
109
|
+
if (!merchantSessionKey) {
|
|
110
|
+
services.addError('Unable to get merchant session.');
|
|
111
|
+
debug.warn('merchantSessionKey missing in callback response.');
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return merchantSessionKey as string;
|
|
117
|
+
} catch {
|
|
118
|
+
services.addError('Network error. Please try again.');
|
|
119
|
+
debug.warn('Network error requesting merchant session.');
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ensureDropInContainerId(container: HTMLElement): string {
|
|
126
|
+
if (!container.id) {
|
|
127
|
+
container.id = `formie-opayo-drop-in-${Math.random().toString(36).slice(2, 9)}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return container.id;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const opayoModule = definePaymentModule<OpayoProviderOptions, null, OpayoDropInWidget | null>({
|
|
41
134
|
id: 'opayo',
|
|
42
135
|
defaultRequiredInputSuffixes: ['opayoTokenId'],
|
|
43
136
|
load: async(ctx) => {
|
|
44
137
|
const { provider } = ctx.options;
|
|
45
138
|
const useSandbox = Boolean(provider.useSandbox);
|
|
46
139
|
const src = useSandbox ? SCRIPT_SRC_SANDBOX : SCRIPT_SRC_LIVE;
|
|
140
|
+
const globalName = isDropInCheckoutMode(provider) ? 'sagepayCheckout' : 'sagepayOwnForm';
|
|
47
141
|
|
|
48
|
-
await loadScriptAndEnsureGlobal<Window['sagepayOwnForm']>(
|
|
142
|
+
await loadScriptAndEnsureGlobal<Window['sagepayOwnForm'] | Window['sagepayCheckout']>(globalName, {
|
|
49
143
|
id: SCRIPT_ID,
|
|
50
144
|
src,
|
|
51
145
|
timeoutMs: 10000,
|
|
@@ -53,9 +147,118 @@ export const opayoModule = definePaymentModule<OpayoProviderOptions, null, null>
|
|
|
53
147
|
|
|
54
148
|
return null;
|
|
55
149
|
},
|
|
150
|
+
mount: async({ field, services, provider }) => {
|
|
151
|
+
if (!isDropInCheckoutMode(provider)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const form = services.form;
|
|
156
|
+
const sagepayCheckout = window.sagepayCheckout;
|
|
157
|
+
const container = field.querySelector<HTMLElement>(DROP_IN_SELECTOR);
|
|
158
|
+
|
|
159
|
+
if (!form?.action) {
|
|
160
|
+
services.addError('Form action is missing.');
|
|
161
|
+
debug.warn('Missing form action before drop-in mount.');
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!sagepayCheckout) {
|
|
167
|
+
services.addError('Opayo script failed to load.');
|
|
168
|
+
debug.warn('sagepayCheckout global not available.');
|
|
169
|
+
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!container) {
|
|
174
|
+
services.addError('Opayo drop-in container is missing.');
|
|
175
|
+
debug.warn('Drop-in container not found in payment field.');
|
|
176
|
+
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const handle = (provider.handle || 'opayo') as string;
|
|
181
|
+
const merchantSessionKey = await requestMerchantSessionKey({
|
|
182
|
+
form,
|
|
183
|
+
handle,
|
|
184
|
+
sessionToken: provider.sessionToken || '',
|
|
185
|
+
services,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (!merchantSessionKey) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const containerId = ensureDropInContainerId(container);
|
|
193
|
+
const widget: OpayoDropInWidget = {
|
|
194
|
+
checkout: null as unknown as SagePayCheckoutInstance,
|
|
195
|
+
merchantSessionKey,
|
|
196
|
+
pendingAuthorize: null,
|
|
197
|
+
retriedTokenise: false,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
widget.checkout = sagepayCheckout({
|
|
201
|
+
merchantSessionKey,
|
|
202
|
+
containerSelector: `#${containerId}`,
|
|
203
|
+
onTokenise: (result) => {
|
|
204
|
+
const resolve = widget.pendingAuthorize;
|
|
205
|
+
widget.pendingAuthorize = null;
|
|
206
|
+
|
|
207
|
+
if (!resolve) {
|
|
208
|
+
debug.warn('Drop-in tokenisation completed without a pending authorize step.');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (result.success && result.cardIdentifier) {
|
|
213
|
+
services.updateInputs('opayoTokenId', result.cardIdentifier);
|
|
214
|
+
services.updateInputs('opayoSessionKey', widget.merchantSessionKey);
|
|
215
|
+
debug.log('Drop-in tokenization succeeded.', {
|
|
216
|
+
hasCardIdentifier: !!result.cardIdentifier,
|
|
217
|
+
});
|
|
218
|
+
resolve(true);
|
|
219
|
+
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!widget.retriedTokenise) {
|
|
224
|
+
widget.retriedTokenise = true;
|
|
225
|
+
void requestMerchantSessionKey({
|
|
226
|
+
form,
|
|
227
|
+
handle,
|
|
228
|
+
sessionToken: provider.sessionToken || '',
|
|
229
|
+
services,
|
|
230
|
+
}).then((newMerchantSessionKey) => {
|
|
231
|
+
if (!newMerchantSessionKey) {
|
|
232
|
+
services.addError(result.errors?.[0]?.message || 'Tokenization failed.');
|
|
233
|
+
debug.warn('Drop-in tokenization failed after session refresh.', result);
|
|
234
|
+
resolve(false);
|
|
235
|
+
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
widget.merchantSessionKey = newMerchantSessionKey;
|
|
240
|
+
widget.pendingAuthorize = resolve;
|
|
241
|
+
widget.checkout.tokenise({ newMerchantSessionKey });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
services.addError(result.errors?.[0]?.message || 'Tokenization failed.');
|
|
248
|
+
debug.warn('Drop-in tokenization failed.', result);
|
|
249
|
+
resolve(false);
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
debug.log('Drop-in checkout mounted.', { containerId });
|
|
254
|
+
|
|
255
|
+
return widget;
|
|
256
|
+
},
|
|
257
|
+
unmount: async({ widget }) => {
|
|
258
|
+
widget?.checkout?.destroy?.();
|
|
259
|
+
},
|
|
56
260
|
onBeforeAuthorize: async(args) => {
|
|
57
|
-
const { field, services, options } = args;
|
|
58
|
-
const provider = options.provider as OpayoProviderOptions;
|
|
261
|
+
const { field, services, options, provider, widget } = args;
|
|
59
262
|
const handle = (provider.handle || 'opayo') as string;
|
|
60
263
|
const form = services.form;
|
|
61
264
|
|
|
@@ -66,7 +269,23 @@ export const opayoModule = definePaymentModule<OpayoProviderOptions, null, null>
|
|
|
66
269
|
return false;
|
|
67
270
|
}
|
|
68
271
|
|
|
69
|
-
|
|
272
|
+
if (isDropInCheckoutMode(provider)) {
|
|
273
|
+
if (!widget) {
|
|
274
|
+
services.addError('Opayo drop-in checkout is not ready.');
|
|
275
|
+
debug.warn('Drop-in authorize requested before widget mount.');
|
|
276
|
+
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
widget.retriedTokenise = false;
|
|
281
|
+
|
|
282
|
+
return new Promise<boolean>((resolve) => {
|
|
283
|
+
widget.pendingAuthorize = resolve;
|
|
284
|
+
widget.checkout.tokenise();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const sagepayOwnForm = window.sagepayOwnForm;
|
|
70
289
|
|
|
71
290
|
if (!sagepayOwnForm) {
|
|
72
291
|
services.addError('Opayo script failed to load.');
|
|
@@ -83,72 +302,40 @@ export const opayoModule = definePaymentModule<OpayoProviderOptions, null, null>
|
|
|
83
302
|
cardNumber = cardNumber.replace(/[\s/]/g, '');
|
|
84
303
|
expiryDate = expiryDate.replace(/[\s/]/g, '');
|
|
85
304
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
fetch(form.action, {
|
|
94
|
-
method: 'POST',
|
|
95
|
-
body: formData,
|
|
96
|
-
}).then(async(res) => {
|
|
97
|
-
if (res.status < 200 || res.status >= 300) {
|
|
98
|
-
services.addError(`${res.status}: ${res.statusText}`);
|
|
99
|
-
debug.warn('Merchant session request failed.', {
|
|
100
|
-
status: res.status,
|
|
101
|
-
statusText: res.statusText,
|
|
102
|
-
});
|
|
103
|
-
resolve(false);
|
|
104
|
-
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
305
|
+
const merchantSessionKey = await requestMerchantSessionKey({
|
|
306
|
+
form,
|
|
307
|
+
handle,
|
|
308
|
+
sessionToken: provider.sessionToken || '',
|
|
309
|
+
services,
|
|
310
|
+
});
|
|
107
311
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
312
|
+
if (!merchantSessionKey) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
111
315
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
316
|
+
return new Promise<boolean>((resolve) => {
|
|
317
|
+
sagepayOwnForm({ merchantSessionKey }).tokeniseCardDetails({
|
|
318
|
+
cardDetails: {
|
|
319
|
+
cardholderName,
|
|
320
|
+
cardNumber,
|
|
321
|
+
expiryDate,
|
|
322
|
+
securityCode,
|
|
323
|
+
},
|
|
324
|
+
onTokenised: (result) => {
|
|
325
|
+
if (result.success && result.cardIdentifier) {
|
|
326
|
+
services.updateInputs('opayoTokenId', result.cardIdentifier);
|
|
327
|
+
services.updateInputs('opayoSessionKey', merchantSessionKey);
|
|
328
|
+
debug.log('Tokenization succeeded.', {
|
|
329
|
+
hasCardIdentifier: !!result.cardIdentifier,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
resolve(true);
|
|
333
|
+
} else {
|
|
334
|
+
services.addError(result.errors?.[0]?.message || 'Tokenization failed.');
|
|
335
|
+
debug.warn('Tokenization failed.', result);
|
|
115
336
|
resolve(false);
|
|
116
|
-
|
|
117
|
-
return;
|
|
118
337
|
}
|
|
119
|
-
|
|
120
|
-
sagepayOwnForm({ merchantSessionKey }).tokeniseCardDetails({
|
|
121
|
-
cardDetails: {
|
|
122
|
-
cardholderName,
|
|
123
|
-
cardNumber,
|
|
124
|
-
expiryDate,
|
|
125
|
-
securityCode,
|
|
126
|
-
},
|
|
127
|
-
onTokenised: (result) => {
|
|
128
|
-
if (result.success && result.cardIdentifier) {
|
|
129
|
-
services.updateInputs('opayoTokenId', result.cardIdentifier);
|
|
130
|
-
services.updateInputs('opayoSessionKey', merchantSessionKey);
|
|
131
|
-
debug.log('Tokenization succeeded.', {
|
|
132
|
-
hasCardIdentifier: !!result.cardIdentifier,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
resolve(true);
|
|
136
|
-
} else {
|
|
137
|
-
services.addError(result.errors?.[0]?.message || 'Tokenization failed.');
|
|
138
|
-
debug.warn('Tokenization failed.', result);
|
|
139
|
-
resolve(false);
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
} catch {
|
|
144
|
-
services.addError('Unable to parse merchant session response.');
|
|
145
|
-
debug.warn('Failed to parse merchant session response.');
|
|
146
|
-
resolve(false);
|
|
147
|
-
}
|
|
148
|
-
}).catch(() => {
|
|
149
|
-
services.addError('Network error. Please try again.');
|
|
150
|
-
debug.warn('Network error requesting merchant session.');
|
|
151
|
-
resolve(false);
|
|
338
|
+
},
|
|
152
339
|
});
|
|
153
340
|
});
|
|
154
341
|
},
|
|
@@ -64,6 +64,15 @@ function extractAuthIdFromAuthorization(result: Record<string, unknown> | null):
|
|
|
64
64
|
return authId.trim();
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
function hidePayPalButtons(widget: PayPalButtonsInstance | null, placeholder: HTMLElement): void {
|
|
68
|
+
if (widget?.close) {
|
|
69
|
+
widget.close();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
placeholder.removeAttribute('data-formie-paypal-rendered');
|
|
73
|
+
placeholder.innerHTML = '';
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
function getScriptUrl(clientId: string, currency: string): string {
|
|
68
77
|
const params = [
|
|
69
78
|
'intent=authorize',
|
|
@@ -137,6 +146,8 @@ export const paypalModule = definePaymentModule<PayPalProviderOptions, PayPalGlo
|
|
|
137
146
|
style.tagline = provider.buttonTagline ?? true;
|
|
138
147
|
}
|
|
139
148
|
|
|
149
|
+
let widget: PayPalButtonsInstance | null = null;
|
|
150
|
+
|
|
140
151
|
const paypalOptions: PayPalButtonsOptions = {
|
|
141
152
|
env: useSandbox ? 'sandbox' : 'production',
|
|
142
153
|
style,
|
|
@@ -183,6 +194,8 @@ export const paypalModule = definePaymentModule<PayPalProviderOptions, PayPalGlo
|
|
|
183
194
|
// Allow submit to continue using order ID fallback on the backend.
|
|
184
195
|
services.addSuccess('PayPal approval received. Finalizing payment on submit.');
|
|
185
196
|
}
|
|
197
|
+
|
|
198
|
+
hidePayPalButtons(widget, placeholder);
|
|
186
199
|
} catch {
|
|
187
200
|
services.addError('Unable to authorize payment. Please try again.');
|
|
188
201
|
}
|
|
@@ -191,9 +204,9 @@ export const paypalModule = definePaymentModule<PayPalProviderOptions, PayPalGlo
|
|
|
191
204
|
|
|
192
205
|
const buttons = api.Buttons(paypalOptions);
|
|
193
206
|
placeholder.setAttribute('data-formie-paypal-rendered', 'true');
|
|
194
|
-
|
|
207
|
+
widget = buttons.render(placeholder);
|
|
195
208
|
|
|
196
|
-
return
|
|
209
|
+
return widget;
|
|
197
210
|
},
|
|
198
211
|
unmount: async (args) => {
|
|
199
212
|
if (args.widget?.close) {
|
|
@@ -206,9 +219,16 @@ export const paypalModule = definePaymentModule<PayPalProviderOptions, PayPalGlo
|
|
|
206
219
|
placeholder.innerHTML = '';
|
|
207
220
|
}
|
|
208
221
|
},
|
|
209
|
-
onAfterSubmit: async ({ services }) => {
|
|
222
|
+
onAfterSubmit: async ({ services, result }) => {
|
|
210
223
|
services.updateInputs(['paypalOrderId', 'paypalAuthId'], '');
|
|
211
224
|
services.removeSuccess();
|
|
212
225
|
services.removeError();
|
|
226
|
+
|
|
227
|
+
// Restore buttons when auth was cleared but payment did not fully complete.
|
|
228
|
+
if (!result?.ok || result?.nextPage) {
|
|
229
|
+
return { remount: true };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {};
|
|
213
233
|
},
|
|
214
234
|
});
|