@lightspeed/online-payments-sdk 1.7.3 → 1.9.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.
Files changed (47) hide show
  1. package/README.md +37 -0
  2. package/dist/cjs/index.js +4 -1
  3. package/dist/cjs/v1/EventBroadcaster.js +1 -0
  4. package/dist/cjs/v1/adyen/terms.js +16 -0
  5. package/dist/cjs/v1/adyen/widget.js +216 -9
  6. package/dist/cjs/v1/index.js +1 -0
  7. package/dist/cjs/v1/stripe/localizedText.js +32 -0
  8. package/dist/cjs/v1/stripe/session.js +9 -0
  9. package/dist/cjs/v1/stripe/shared.js +55 -14
  10. package/dist/cjs/v1/stripe/terms.js +1 -1
  11. package/dist/cjs/v1/stripe/themes.js +2 -2
  12. package/dist/cjs/v1/stripe/widget.js +64 -25
  13. package/dist/cjs/v1/stripe/widgets/deferredMoto.js +203 -0
  14. package/dist/cjs/v1/widget.js +83 -2
  15. package/dist/cjs/version.js +1 -1
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.js +3 -0
  18. package/dist/v1/EventBroadcaster.d.ts +18 -1
  19. package/dist/v1/EventBroadcaster.js +1 -0
  20. package/dist/v1/adyen/session.d.ts +6 -3
  21. package/dist/v1/adyen/terms.d.ts +5 -0
  22. package/dist/v1/adyen/terms.js +12 -0
  23. package/dist/v1/adyen/widget.d.ts +9 -2
  24. package/dist/v1/adyen/widget.js +216 -9
  25. package/dist/v1/common.d.ts +30 -2
  26. package/dist/v1/index.d.ts +12 -3
  27. package/dist/v1/index.js +2 -1
  28. package/dist/v1/stripe/localizedText.d.ts +3 -0
  29. package/dist/v1/stripe/localizedText.js +28 -0
  30. package/dist/v1/stripe/session.d.ts +29 -6
  31. package/dist/v1/stripe/session.js +7 -1
  32. package/dist/v1/stripe/shared.d.ts +8 -5
  33. package/dist/v1/stripe/shared.js +55 -15
  34. package/dist/v1/stripe/terms.d.ts +1 -1
  35. package/dist/v1/stripe/terms.js +1 -1
  36. package/dist/v1/stripe/themes.js +2 -2
  37. package/dist/v1/stripe/widget.js +62 -23
  38. package/dist/v1/stripe/widgets/deferredMoto.d.ts +5 -0
  39. package/dist/v1/stripe/widgets/deferredMoto.js +200 -0
  40. package/dist/v1/stripe/widgets/moto.d.ts +2 -2
  41. package/dist/v1/stripe/widgets/payment.d.ts +2 -2
  42. package/dist/v1/widget.d.ts +8 -1
  43. package/dist/v1/widget.js +82 -2
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/package.json +8 -3
  47. package/styles/lightspeed-global-styles.css +81 -1
@@ -36,13 +36,31 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
36
36
  };
37
37
  import { AdyenCheckout, Dropin, ApplePay, Bancontact, Card, GooglePay, Redirect, Twint, } from '@adyen/adyen-web';
38
38
  import { ResultBuilder } from './ResultBuilder';
39
+ import { buildNoShowConsentText } from './terms';
39
40
  function mount(mountElement, session, eventBroadcaster) {
41
+ return __awaiter(this, void 0, void 0, function () {
42
+ return __generator(this, function (_a) {
43
+ return [2 /*return*/, mountSessionsFlow(mountElement, session.context, eventBroadcaster)];
44
+ });
45
+ });
46
+ }
47
+ // Entry for the multi-step contract (v1.mountMultiStepWidget). Kept separate
48
+ // from `mount` so the event-based Sessions flow keeps its WidgetController shape.
49
+ function mountMultiStep(mountElement, session, eventBroadcaster, onRequiresFinalization) {
50
+ return __awaiter(this, void 0, void 0, function () {
51
+ return __generator(this, function (_a) {
52
+ return [2 /*return*/, mountAdvancedFlow(mountElement, session.context, eventBroadcaster, onRequiresFinalization)];
53
+ });
54
+ });
55
+ }
56
+ function mountSessionsFlow(mountElement, context, eventBroadcaster) {
40
57
  return __awaiter(this, void 0, void 0, function () {
41
58
  var checkoutConfig, checkout, dropinConfiguration, dropIn;
42
59
  return __generator(this, function (_a) {
43
60
  switch (_a.label) {
44
61
  case 0:
45
- checkoutConfig = createAdyenConfiguration(session.context, eventBroadcaster);
62
+ injectAdyenCssModuleFix();
63
+ checkoutConfig = createSessionsConfiguration(context, eventBroadcaster);
46
64
  return [4 /*yield*/, AdyenCheckout(checkoutConfig)];
47
65
  case 1:
48
66
  checkout = _a.sent();
@@ -63,7 +81,163 @@ function mount(mountElement, session, eventBroadcaster) {
63
81
  });
64
82
  });
65
83
  }
66
- function createAdyenConfiguration(context, eventBroadcaster) {
84
+ // Multi-step Advanced flow: submit() surfaces the encrypted payment data for
85
+ // the host to POST; handleNextAction() feeds the server response back to drive
86
+ // native 3DS2. See MultiStepController in common.ts.
87
+ function mountAdvancedFlow(mountElement, context, eventBroadcaster, onRequiresFinalization) {
88
+ return __awaiter(this, void 0, void 0, function () {
89
+ var stashedActions, pendingResolve, isValid, checkoutConfig, checkout, dropinConfiguration, dropIn;
90
+ var _a;
91
+ return __generator(this, function (_b) {
92
+ switch (_b.label) {
93
+ case 0:
94
+ isValid = false;
95
+ checkoutConfig = {
96
+ clientKey: context.clientKey,
97
+ environment: context.env,
98
+ countryCode: context.countryCode,
99
+ locale: context.locale,
100
+ amount: { value: (_a = context.amount) !== null && _a !== void 0 ? _a : 0, currency: context.currency },
101
+ paymentMethodsResponse: context.paymentMethods,
102
+ analytics: { enabled: false },
103
+ onSubmit: function (state, _component, actions) {
104
+ stashedActions = actions;
105
+ var result = {
106
+ status: 'RequiresFinalization',
107
+ paymentData: {
108
+ paymentMethod: state.data.paymentMethod,
109
+ browserInfo: state.data.browserInfo,
110
+ origin: window.location.origin,
111
+ },
112
+ saveSessionId: context.saveSessionId,
113
+ };
114
+ if (pendingResolve) {
115
+ pendingResolve(result);
116
+ pendingResolve = undefined;
117
+ }
118
+ else {
119
+ // Express methods (Google Pay / Apple Pay) fire onSubmit from their own
120
+ // button with no submit() pending — hand the result to the host instead.
121
+ onRequiresFinalization === null || onRequiresFinalization === void 0 ? void 0 : onRequiresFinalization(result);
122
+ }
123
+ },
124
+ onAdditionalDetails: function (state, _component, actions) {
125
+ stashedActions = actions;
126
+ pendingResolve === null || pendingResolve === void 0 ? void 0 : pendingResolve({
127
+ status: 'RequiresAction',
128
+ paymentData: {
129
+ details: state.data.details,
130
+ paymentData: state.data.paymentData,
131
+ },
132
+ saveSessionId: context.saveSessionId,
133
+ });
134
+ pendingResolve = undefined;
135
+ },
136
+ onError: function (error) {
137
+ // Resolve a waiting submit()/handleNextAction() so the host doesn't hang.
138
+ if (pendingResolve) {
139
+ pendingResolve({ status: 'Error', error: error });
140
+ pendingResolve = undefined;
141
+ return;
142
+ }
143
+ eventBroadcaster.publish({
144
+ status: 'Error',
145
+ code: 'Processing',
146
+ loggingEnrichment: error,
147
+ });
148
+ },
149
+ // Surface form validity so the host can gate its own submit button; onSubmit
150
+ // never fires for an invalid form, so this is the host's only validity signal.
151
+ onChange: function (state) {
152
+ var _a;
153
+ isValid = state.isValid;
154
+ var paymentMethodType = (_a = state.data.paymentMethod) === null || _a === void 0 ? void 0 : _a.type;
155
+ eventBroadcaster.publish({
156
+ status: 'Change',
157
+ code: isValid ? 'Complete' : 'Incomplete',
158
+ paymentMethodType: typeof paymentMethodType === 'string' ? paymentMethodType : undefined,
159
+ });
160
+ },
161
+ };
162
+ injectAdyenCssModuleFix();
163
+ return [4 /*yield*/, AdyenCheckout(checkoutConfig)];
164
+ case 1:
165
+ checkout = _b.sent();
166
+ dropinConfiguration = {
167
+ paymentMethodComponents: [Card, ApplePay, GooglePay, Bancontact],
168
+ showStoredPaymentMethods: false,
169
+ paymentMethodsConfiguration: {
170
+ card: {
171
+ // Hide the card's built-in button (host renders its own submit). In
172
+ // adyen-web v6 showPayButton is per-component, not Dropin-level. Express
173
+ // wallets keep their native buttons.
174
+ showPayButton: false,
175
+ // Suppress Click to Pay in a zero-value card-save. `_disableClickToPay`
176
+ // is an internal adyen-web flag; replace on the next upgrade.
177
+ _disableClickToPay: true,
178
+ },
179
+ },
180
+ // Render the consent copy only once the Drop-in has finished loading, so it
181
+ // doesn't flash on its own before the card/wallet options appear.
182
+ onReady: function () { return renderConsentDisclosure(mountElement, context.businessName); },
183
+ };
184
+ dropIn = new Dropin(checkout, dropinConfiguration).mount(mountElement);
185
+ return [2 /*return*/, {
186
+ unmount: function () {
187
+ dropIn.unmount();
188
+ removeConsentDisclosure(mountElement);
189
+ },
190
+ submit: function () {
191
+ return new Promise(function (resolve) {
192
+ // adyen-web renders inline field errors but never calls onSubmit for an
193
+ // invalid form, so waiting on pendingResolve would hang the host. Trigger
194
+ // the field errors and settle deterministically instead.
195
+ if (!isValid) {
196
+ dropIn.submit();
197
+ resolve({ status: 'Invalid' });
198
+ return;
199
+ }
200
+ pendingResolve = resolve;
201
+ dropIn.submit();
202
+ });
203
+ },
204
+ handleNextAction: function (serverResponse) {
205
+ return new Promise(function (resolve) {
206
+ var _a;
207
+ var response = (serverResponse !== null && serverResponse !== void 0 ? serverResponse : {});
208
+ if (response.action) {
209
+ // 3DS1 (redirect) is out of scope: surface as an error so the host can
210
+ // ask the shopper for a different card.
211
+ if (response.action.type === 'redirect') {
212
+ resolve({
213
+ status: 'Error',
214
+ error: { code: 'RedirectNotSupported', resultCode: response.resultCode },
215
+ });
216
+ return;
217
+ }
218
+ // Native 3DS2 step: let the component perform it, then resolve once
219
+ // onAdditionalDetails hands us the next payload for the host to POST.
220
+ pendingResolve = resolve;
221
+ stashedActions === null || stashedActions === void 0 ? void 0 : stashedActions.resolve(response);
222
+ return;
223
+ }
224
+ // Terminal response: let the component finalize its UI (its own result
225
+ // screen), drop the now-stale pre-submission consent, report Complete.
226
+ stashedActions === null || stashedActions === void 0 ? void 0 : stashedActions.resolve(response);
227
+ removeConsentDisclosure(mountElement);
228
+ resolve({ status: 'Complete', resultCode: (_a = response.resultCode) !== null && _a !== void 0 ? _a : 'Unknown' });
229
+ });
230
+ },
231
+ }];
232
+ }
233
+ });
234
+ });
235
+ }
236
+ function createSessionsConfiguration(context, eventBroadcaster) {
237
+ var _a;
238
+ if (!context.sessionId || !context.sessionData) {
239
+ throw new Error('Sessions flow requires sessionId and sessionData; received an advanced-flow session.');
240
+ }
67
241
  return {
68
242
  clientKey: context.clientKey,
69
243
  session: {
@@ -71,15 +245,10 @@ function createAdyenConfiguration(context, eventBroadcaster) {
71
245
  sessionData: context.sessionData,
72
246
  },
73
247
  countryCode: context.countryCode,
74
- amount: {
75
- value: context.amount,
76
- currency: context.currency,
77
- },
248
+ amount: { value: (_a = context.amount) !== null && _a !== void 0 ? _a : 0, currency: context.currency },
78
249
  locale: context.locale,
79
250
  environment: context.env,
80
- analytics: {
81
- enabled: false,
82
- },
251
+ analytics: { enabled: false },
83
252
  onPaymentCompleted: function (data) {
84
253
  eventBroadcaster.publish(ResultBuilder.fromPaymentCompletedData(data));
85
254
  },
@@ -96,6 +265,43 @@ function createAdyenConfiguration(context, eventBroadcaster) {
96
265
  paymentMethodsResponse: context.paymentMethods,
97
266
  };
98
267
  }
268
+ // Appends the SDK-owned no-show consent copy under the Drop-in. Idempotent, so
269
+ // a repeated onReady never duplicates it; a no-op when no business name is set.
270
+ function renderConsentDisclosure(mountElement, businessName) {
271
+ if (!businessName)
272
+ return;
273
+ if (mountElement.querySelector('[data-lspay="consent-disclosure"]'))
274
+ return;
275
+ var disclosure = document.createElement('p');
276
+ disclosure.setAttribute('data-lspay', 'consent-disclosure');
277
+ disclosure.style.marginTop = '16px';
278
+ disclosure.textContent = buildNoShowConsentText(businessName);
279
+ mountElement.appendChild(disclosure);
280
+ }
281
+ // Removes the consent copy once the flow finalizes or unmounts, so it doesn't
282
+ // linger on the Drop-in's result screen (it only applies pre-submission).
283
+ function removeConsentDisclosure(mountElement) {
284
+ var _a;
285
+ (_a = mountElement.querySelector('[data-lspay="consent-disclosure"]')) === null || _a === void 0 ? void 0 : _a.remove();
286
+ }
287
+ // Adyen's Drop-in CSS uses hashed class names (e.g. .loading-input__spinner--abc123),
288
+ // but when the host app's bundler processes the CSS as modules it rehashes them,
289
+ // causing a mismatch with the unhashed class names Adyen writes to the DOM.
290
+ // We inject plain (unhashed) rules so the spinner/loading states render correctly.
291
+ var adyenCssFixInjected = false;
292
+ function injectAdyenCssModuleFix() {
293
+ if (adyenCssFixInjected)
294
+ return;
295
+ adyenCssFixInjected = true;
296
+ var style = document.createElement('style');
297
+ style.setAttribute('data-lspay', 'adyen-css-module-fix');
298
+ style.textContent = [
299
+ ".loading-input__spinner { z-index: 1; width: 100%; height: 100%; display: none; position: absolute; top: 0; left: 0; }",
300
+ ".loading-input__spinner--active { display: flex; align-items: center; justify-content: center; }",
301
+ ".loading-input__form--loading { opacity: 0; }",
302
+ ].join('\n');
303
+ document.head.appendChild(style);
304
+ }
99
305
  function createDropinConfiguration() {
100
306
  return {
101
307
  paymentMethodComponents: [Card, ApplePay, Bancontact, GooglePay, Redirect, Twint],
@@ -103,4 +309,5 @@ function createDropinConfiguration() {
103
309
  }
104
310
  export var AdyenWidget = {
105
311
  mount: mount,
312
+ mountMultiStep: mountMultiStep,
106
313
  };
@@ -1,10 +1,38 @@
1
- import { EventBroadcaster, Event } from './EventBroadcaster';
1
+ import { EventBroadcaster, Event, RequiresFinalizationResult } from './EventBroadcaster';
2
2
  import { Session } from './session';
3
3
  import { PaymentWidgetTheme } from './widget';
4
4
  export type SessionType = 'payment' | 'payment-with-save' | 'moto' | 'moto-with-save' | 'save';
5
+ export type WidgetSubmitResult = Event | RequiresFinalizationResult;
5
6
  export type WidgetController = {
6
7
  unmount: () => void;
7
- submit: () => Promise<Event>;
8
+ submit: () => Promise<WidgetSubmitResult>;
9
+ /**
10
+ * Deferred MOTO only. Reports a definitive backend decline so the widget can
11
+ * present it and collect a fresh PaymentMethod on the next submission.
12
+ */
13
+ reportFinalizationDecline?: (result: RequiresFinalizationResult, message: string) => void;
14
+ };
15
+ export type MultiStepResult = {
16
+ status: 'RequiresFinalization';
17
+ paymentData: unknown;
18
+ saveSessionId?: string;
19
+ } | {
20
+ status: 'RequiresAction';
21
+ paymentData: unknown;
22
+ saveSessionId?: string;
23
+ } | {
24
+ status: 'Complete';
25
+ resultCode: string;
26
+ } | {
27
+ status: 'Invalid';
28
+ } | {
29
+ status: 'Error';
30
+ error: unknown;
31
+ };
32
+ export type MultiStepController = {
33
+ unmount: () => void;
34
+ submit: () => Promise<MultiStepResult>;
35
+ handleNextAction: (serverResponse: unknown) => Promise<MultiStepResult>;
8
36
  };
9
37
  export type DefaultValues = {
10
38
  country?: string;
@@ -4,10 +4,10 @@
4
4
  * and handling errors related to payment sessions.
5
5
  * Don't use * for exports. Be specific to prevent leaking internal APIs.
6
6
  */
7
- import { WidgetController } from './common';
7
+ import { WidgetController, MultiStepController } from './common';
8
8
  import { PaymentWidgetConfiguration } from './widget';
9
- export type { WidgetController, SessionType, DefaultValues } from './common';
10
- export type { Event } from './EventBroadcaster';
9
+ export type { WidgetController, MultiStepController, MultiStepResult, SessionType, DefaultValues, WidgetSubmitResult, } from './common';
10
+ export type { Event, RequiresFinalizationResult } from './EventBroadcaster';
11
11
  export { UnsupportedLocationError, InvalidSessionPayloadError, InvalidSessionError, ProcessingError, } from './error';
12
12
  export type { PaymentWidgetConfiguration, PaymentWidgetTheme } from './widget';
13
13
  export { THEME_COLORS } from './stripe/themes';
@@ -19,5 +19,14 @@ export type LightspeedPaymentsV1 = {
19
19
  * @param {PaymentWidgetConfiguration} config - The configuration options for the payment widget.
20
20
  */
21
21
  mountPaymentWidget(session: string, config: PaymentWidgetConfiguration): Promise<WidgetController>;
22
+ /**
23
+ * Mounts the payment widget using the multi-step contract, where the host
24
+ * owns HTTP transport. Returns a controller whose `submit()` yields the opaque
25
+ * payment data to POST and whose `handleNextAction()` drives the 3DS2 loop.
26
+ * @function mountMultiStepWidget
27
+ * @param {string} session - The base64-encoded session payload from the create-session API.
28
+ * @param {PaymentWidgetConfiguration} config - The configuration options for the payment widget.
29
+ */
30
+ mountMultiStepWidget(session: string, config: PaymentWidgetConfiguration): Promise<MultiStepController>;
22
31
  };
23
32
  export declare const v1: LightspeedPaymentsV1;
package/dist/v1/index.js CHANGED
@@ -4,9 +4,10 @@
4
4
  * and handling errors related to payment sessions.
5
5
  * Don't use * for exports. Be specific to prevent leaking internal APIs.
6
6
  */
7
- import { mountPaymentWidget } from './widget';
7
+ import { mountPaymentWidget, mountMultiStepWidget, } from './widget';
8
8
  export { UnsupportedLocationError, InvalidSessionPayloadError, InvalidSessionError, ProcessingError, } from './error';
9
9
  export { THEME_COLORS } from './stripe/themes';
10
10
  export var v1 = {
11
11
  mountPaymentWidget: mountPaymentWidget,
12
+ mountMultiStepWidget: mountMultiStepWidget,
12
13
  };
@@ -0,0 +1,3 @@
1
+ /** Locale-selected UI text rendered alongside Stripe Elements. */
2
+ export declare function getSavePaymentMethodText(locale?: string): string;
3
+ export declare function getCardDeclinedText(locale?: string): string;
@@ -0,0 +1,28 @@
1
+ /** Locale-selected UI text rendered alongside Stripe Elements. */
2
+ var SAVE_PAYMENT_METHOD_TEXT = {
3
+ de: 'Zahlungsinformationen für zukünftige Einkäufe speichern',
4
+ en: 'Save payment details for future purchases',
5
+ fr: 'Enregistrer les informations de paiement pour vos prochains achats',
6
+ nl: 'Betaalgegevens opslaan voor toekomstige aankopen',
7
+ };
8
+ var CARD_DECLINED_TEXT = {
9
+ de: 'Ihre Karte wurde abgelehnt.',
10
+ en: 'Your card was declined.',
11
+ fr: 'Votre carte a été refusée.',
12
+ nl: 'Uw kaart is geweigerd.',
13
+ };
14
+ function getLanguage(locale) {
15
+ var _a;
16
+ var resolvedLocale = !locale || locale === 'auto'
17
+ ? ((_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.language) || 'en'
18
+ : locale;
19
+ return resolvedLocale.toLowerCase().split('-')[0];
20
+ }
21
+ export function getSavePaymentMethodText(locale) {
22
+ var language = getLanguage(locale);
23
+ return SAVE_PAYMENT_METHOD_TEXT[language] || SAVE_PAYMENT_METHOD_TEXT.en;
24
+ }
25
+ export function getCardDeclinedText(locale) {
26
+ var language = getLanguage(locale);
27
+ return CARD_DECLINED_TEXT[language] || CARD_DECLINED_TEXT.en;
28
+ }
@@ -1,15 +1,38 @@
1
1
  import { StripeElementLocale } from '@stripe/stripe-js';
2
2
  import { BaseSession } from '../session';
3
- export type StripeSession = BaseSession & {
3
+ export type StripeBaseSession = BaseSession & {
4
4
  psp: 'STRIPE';
5
- context: StripeContext;
6
5
  };
7
- export type StripeContext = {
8
- clientSecret: string;
6
+ type StripeContextBase = {
9
7
  publishableKey: string;
8
+ locale?: StripeElementLocale;
9
+ businessName?: string;
10
+ };
11
+ export type IntentBoundStripeContext = StripeContextBase & {
12
+ intentMode?: 'intent-bound';
13
+ clientSecret: string;
10
14
  customerSessionClientSecret?: string;
11
15
  intent: 'save-card' | 'payment';
12
- locale?: StripeElementLocale;
13
16
  redirectUrl?: string;
14
- businessName?: string;
15
17
  };
18
+ export type DeferredStripeContext = StripeContextBase & {
19
+ intentMode: 'deferred';
20
+ intent: 'payment';
21
+ amount: number;
22
+ currency: string;
23
+ paymentMethodTypes: string[];
24
+ onBehalfOf: string;
25
+ clientSecret?: never;
26
+ customerSessionClientSecret?: never;
27
+ };
28
+ export type StripeContext = IntentBoundStripeContext | DeferredStripeContext;
29
+ export type IntentBoundStripeSession = StripeBaseSession & {
30
+ context: IntentBoundStripeContext;
31
+ };
32
+ export type DeferredStripeSession = StripeBaseSession & {
33
+ context: DeferredStripeContext;
34
+ };
35
+ export type StripeSession = IntentBoundStripeSession | DeferredStripeSession;
36
+ export declare function isDeferredStripeSession(session: StripeSession): session is DeferredStripeSession;
37
+ export declare function isIntentBoundStripeSession(session: StripeSession): session is IntentBoundStripeSession;
38
+ export {};
@@ -1 +1,7 @@
1
- export {};
1
+ export function isDeferredStripeSession(session) {
2
+ return session.context.intentMode === 'deferred';
3
+ }
4
+ export function isIntentBoundStripeSession(session) {
5
+ return (session.context.intentMode === undefined ||
6
+ session.context.intentMode === 'intent-bound');
7
+ }
@@ -1,6 +1,6 @@
1
1
  import { StripePaymentElementOptions, StripeAddressElementOptions } from '@stripe/stripe-js';
2
2
  import type { StripeElements, Stripe, StripeAddressElement, StripePaymentElement } from '@stripe/stripe-js';
3
- import { StripeContext, StripeSession } from './session';
3
+ import { IntentBoundStripeSession, StripeContext } from './session';
4
4
  import { DefaultValues } from '../common';
5
5
  import { EventBroadcaster } from '../EventBroadcaster';
6
6
  import { PaymentWidgetTheme } from '../widget';
@@ -45,6 +45,7 @@ export declare function buildCardConsentText(businessName: string, reason: strin
45
45
  * text.
46
46
  */
47
47
  export declare function createCardConsentElement(consentText: string): HTMLElement;
48
+ export declare function createSavePaymentMethodElement(mountElement: HTMLElement, onChange: (checked: boolean) => void, locale?: string): HTMLElement;
48
49
  /**
49
50
  * Create and mount payment element
50
51
  */
@@ -61,7 +62,9 @@ export declare function createPaymentElement(elements: StripeElements, options:
61
62
  * Also includes the current save-payment-method checkbox state (when present) on every Change event,
62
63
  * and emits a Change event when that checkbox state changes.
63
64
  */
64
- export declare function setupChangeEventHandler(paymentElement: StripePaymentElement, eventBroadcaster: EventBroadcaster, addressElement?: StripeAddressElement | null): void;
65
+ export declare function setupChangeEventHandler(paymentElement: StripePaymentElement, eventBroadcaster: EventBroadcaster, addressElement?: StripeAddressElement | null, onPaymentValueChange?: (value: {
66
+ savePaymentMethod?: boolean;
67
+ }) => void, initialSaveCheckboxState?: boolean): (saveCheckboxChecked: boolean) => void;
65
68
  /**
66
69
  * Create and mount address element
67
70
  */
@@ -76,12 +79,12 @@ export declare function cleanupElements(paymentElement: StripePaymentElement, ad
76
79
  /**
77
80
  * Handle save card operation
78
81
  */
79
- export declare function handleSaveCard(stripe: Stripe, elements: StripeElements, session: StripeSession, addressElement?: StripeAddressElement | null): Promise<import("../EventBroadcaster").SucceededResultEvent | import("../EventBroadcaster").PendingResultEvent | import("../EventBroadcaster").DeclinedResultEvent | import("../EventBroadcaster").ErrorResultEvent>;
82
+ export declare function handleSaveCard(stripe: Stripe, elements: StripeElements, session: IntentBoundStripeSession, addressElement?: StripeAddressElement | null): Promise<import("../EventBroadcaster").SucceededResultEvent | import("../EventBroadcaster").PendingResultEvent | import("../EventBroadcaster").DeclinedResultEvent | import("../EventBroadcaster").ErrorResultEvent>;
80
83
  /**
81
84
  * Handle payment operation
82
85
  */
83
- export declare function handlePayment(stripe: Stripe, elements: StripeElements, session: StripeSession, addressElement?: StripeAddressElement | null): Promise<import("../EventBroadcaster").SucceededResultEvent | import("../EventBroadcaster").PendingResultEvent | import("../EventBroadcaster").DeclinedResultEvent | import("../EventBroadcaster").ErrorResultEvent>;
86
+ export declare function handlePayment(stripe: Stripe, elements: StripeElements, session: IntentBoundStripeSession, addressElement?: StripeAddressElement | null): Promise<import("../EventBroadcaster").SucceededResultEvent | import("../EventBroadcaster").PendingResultEvent | import("../EventBroadcaster").DeclinedResultEvent | import("../EventBroadcaster").ErrorResultEvent>;
84
87
  /**
85
88
  * Create common submit handler for widgets
86
89
  */
87
- export declare function createSubmitHandler(stripe: Stripe, elements: StripeElements, session: StripeSession, eventBroadcaster: EventBroadcaster, addressElement?: StripeAddressElement | null): () => Promise<import("../EventBroadcaster").SucceededResultEvent | import("../EventBroadcaster").PendingResultEvent | import("../EventBroadcaster").DeclinedResultEvent | import("../EventBroadcaster").ErrorResultEvent>;
90
+ export declare function createSubmitHandler(stripe: Stripe, elements: StripeElements, session: IntentBoundStripeSession, eventBroadcaster: EventBroadcaster, addressElement?: StripeAddressElement | null): () => Promise<import("../EventBroadcaster").SucceededResultEvent | import("../EventBroadcaster").PendingResultEvent | import("../EventBroadcaster").DeclinedResultEvent | import("../EventBroadcaster").ErrorResultEvent>;
@@ -49,17 +49,18 @@ import { loadStripe, } from '@stripe/stripe-js';
49
49
  import { EventBuilder } from './ResultBuilder';
50
50
  import { InvalidSessionPayloadError, ProcessingError } from '../error';
51
51
  import { getThemeConfig } from './themes';
52
- import { ACH_TERMS_TEXT, ACH_TERMS_URL, PARTNER_CARD_CONSENT_TEXT } from './terms';
52
+ import { ACH_TERMS_TEXT, ACH_TERMS_URL, PARTNER_CARD_CONSENT_TEXT, } from './terms';
53
+ import { getSavePaymentMethodText } from './localizedText';
53
54
  /**
54
55
  * Initialize Stripe with the given context
55
56
  */
56
57
  export function initStripe(context, theme) {
57
58
  return __awaiter(this, void 0, void 0, function () {
58
- var publishableKey, clientSecret, customerSessionClientSecret, loadedStripe, stripe, themeConfig, elementsOptions, elements;
59
+ var publishableKey, loadedStripe, stripe, themeConfig, sharedElementsOptions, elements, elementsOptions, elementsOptions;
59
60
  return __generator(this, function (_a) {
60
61
  switch (_a.label) {
61
62
  case 0:
62
- publishableKey = context.publishableKey, clientSecret = context.clientSecret, customerSessionClientSecret = context.customerSessionClientSecret;
63
+ publishableKey = context.publishableKey;
63
64
  return [4 /*yield*/, loadStripe(publishableKey)];
64
65
  case 1:
65
66
  loadedStripe = _a.sent();
@@ -68,8 +69,15 @@ export function initStripe(context, theme) {
68
69
  }
69
70
  stripe = loadedStripe;
70
71
  themeConfig = getThemeConfig(theme);
71
- elementsOptions = __assign(__assign(__assign({ clientSecret: clientSecret, customerSessionClientSecret: customerSessionClientSecret }, (context.locale && { locale: context.locale })), (themeConfig && { appearance: themeConfig.appearance })), (themeConfig && { fonts: themeConfig.fonts }));
72
- elements = stripe.elements(elementsOptions);
72
+ sharedElementsOptions = __assign(__assign(__assign({}, (context.locale && { locale: context.locale })), (themeConfig && { appearance: themeConfig.appearance })), (themeConfig && { fonts: themeConfig.fonts }));
73
+ if (context.intentMode === 'deferred') {
74
+ elementsOptions = __assign(__assign(__assign({}, sharedElementsOptions), { mode: 'payment', amount: context.amount, currency: context.currency.toLowerCase(), paymentMethodTypes: context.paymentMethodTypes, paymentMethodCreation: 'manual' }), (context.onBehalfOf && { onBehalfOf: context.onBehalfOf }));
75
+ elements = stripe.elements(elementsOptions);
76
+ }
77
+ else {
78
+ elementsOptions = __assign(__assign({}, sharedElementsOptions), { clientSecret: context.clientSecret, customerSessionClientSecret: context.customerSessionClientSecret });
79
+ elements = stripe.elements(elementsOptions);
80
+ }
73
81
  return [2 /*return*/, { stripe: stripe, elements: elements }];
74
82
  }
75
83
  });
@@ -206,6 +214,24 @@ export function createCardConsentElement(consentText) {
206
214
  container.appendChild(text);
207
215
  return container;
208
216
  }
217
+ export function createSavePaymentMethodElement(mountElement, onChange, locale) {
218
+ var label = document.createElement('label');
219
+ label.className = 'stripe-save-payment-method-container';
220
+ var checkbox = document.createElement('input');
221
+ checkbox.type = 'checkbox';
222
+ checkbox.className = 'stripe-save-payment-method-checkbox';
223
+ checkbox.dataset.testid = 'save-payment-method-checkbox';
224
+ var text = document.createElement('span');
225
+ text.className = 'stripe-save-payment-method-text';
226
+ text.textContent = getSavePaymentMethodText(locale);
227
+ label.appendChild(checkbox);
228
+ label.appendChild(text);
229
+ mountElement.appendChild(label);
230
+ // The label must remain clickable, but its consent text is not selectable.
231
+ label.addEventListener('selectstart', function (event) { return event.preventDefault(); });
232
+ checkbox.addEventListener('change', function () { return onChange(checkbox.checked); });
233
+ return label;
234
+ }
209
235
  /**
210
236
  * Create and mount payment element
211
237
  */
@@ -245,12 +271,12 @@ export function createPaymentElement(elements, options, mountElement, eventBroad
245
271
  * Also includes the current save-payment-method checkbox state (when present) on every Change event,
246
272
  * and emits a Change event when that checkbox state changes.
247
273
  */
248
- export function setupChangeEventHandler(paymentElement, eventBroadcaster, addressElement) {
274
+ export function setupChangeEventHandler(paymentElement, eventBroadcaster, addressElement, onPaymentValueChange, initialSaveCheckboxState) {
249
275
  var paymentComplete = false;
250
276
  var addressComplete = !addressElement; // If no address element, consider it complete
251
277
  var previousComplete = null;
252
278
  var previousMethodType;
253
- var previousSaveCheckboxState; // Track save checkbox state
279
+ var previousSaveCheckboxState = initialSaveCheckboxState;
254
280
  var emitChangeEvent = function () {
255
281
  var complete = paymentComplete && addressComplete;
256
282
  previousComplete = complete;
@@ -261,23 +287,30 @@ export function setupChangeEventHandler(paymentElement, eventBroadcaster, addres
261
287
  })));
262
288
  };
263
289
  paymentElement.on('change', function (event) {
264
- var _a, _b;
290
+ var _a, _b, _c;
265
291
  paymentComplete = event.complete;
292
+ onPaymentValueChange === null || onPaymentValueChange === void 0 ? void 0 : onPaymentValueChange({
293
+ savePaymentMethod: (_a = event.value) === null || _a === void 0 ? void 0 : _a.savePaymentMethod,
294
+ });
266
295
  var completenessChanged = (paymentComplete && addressComplete) !== previousComplete;
267
- var methodType = (_a = event.value) === null || _a === void 0 ? void 0 : _a.type;
296
+ var methodType = (_b = event.value) === null || _b === void 0 ? void 0 : _b.type;
268
297
  var methodTypeChanged = methodType !== undefined && methodType !== previousMethodType;
269
298
  if (methodType !== undefined) {
270
299
  previousMethodType = methodType;
271
300
  }
272
301
  // `savePaymentMethod` reflects the "save for future use" checkbox; it is only
273
302
  // present on the change event when the element renders that checkbox.
274
- var saveCheckboxChanged = false;
275
- var saveCheckboxState = (_b = event.value) === null || _b === void 0 ? void 0 : _b.savePaymentMethod;
276
- if (saveCheckboxState !== undefined) {
277
- saveCheckboxChanged = previousSaveCheckboxState !== saveCheckboxState;
278
- previousSaveCheckboxState = saveCheckboxState;
303
+ var stripeSaveCheckboxChanged = false;
304
+ var stripeSaveCheckboxState = (_c = event.value) === null || _c === void 0 ? void 0 : _c.savePaymentMethod;
305
+ if (initialSaveCheckboxState === undefined &&
306
+ stripeSaveCheckboxState !== undefined) {
307
+ stripeSaveCheckboxChanged =
308
+ previousSaveCheckboxState !== stripeSaveCheckboxState;
309
+ previousSaveCheckboxState = stripeSaveCheckboxState;
279
310
  }
280
- if (completenessChanged || methodTypeChanged || saveCheckboxChanged) {
311
+ if (completenessChanged ||
312
+ methodTypeChanged ||
313
+ stripeSaveCheckboxChanged) {
281
314
  emitChangeEvent();
282
315
  }
283
316
  });
@@ -290,6 +323,13 @@ export function setupChangeEventHandler(paymentElement, eventBroadcaster, addres
290
323
  }
291
324
  });
292
325
  }
326
+ return function (saveCheckboxChecked) {
327
+ if (previousSaveCheckboxState === saveCheckboxChecked) {
328
+ return;
329
+ }
330
+ previousSaveCheckboxState = saveCheckboxChecked;
331
+ emitChangeEvent();
332
+ };
293
333
  }
294
334
  /**
295
335
  * Create and mount address element
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Translatable strings for Stripe payment elements.
2
+ * Terms and consent text for Stripe payment elements.
3
3
  * These strings are prepared for Transifex extraction.
4
4
  *
5
5
  * Usage with Transifex:
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Translatable strings for Stripe payment elements.
2
+ * Terms and consent text for Stripe payment elements.
3
3
  * These strings are prepared for Transifex extraction.
4
4
  *
5
5
  * Usage with Transifex: