@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
package/README.md CHANGED
@@ -31,6 +31,7 @@ Mount the widget with the payment session from your integration:
31
31
 
32
32
  ```ts
33
33
  import {LightspeedPayments} from '@lightspeed/online-payments-sdk';
34
+ import '@lightspeed/online-payments-sdk/styles/lightspeed-global-styles.css';
34
35
 
35
36
  const mountPoint = document.getElementById('payment-widget');
36
37
 
@@ -109,6 +110,23 @@ submitButton.addEventListener('click', async () => {
109
110
  controller.unmount();
110
111
  ```
111
112
 
113
+ `submit()` returns `Promise<WidgetSubmitResult>`. Existing intent-bound Stripe
114
+ and Adyen sessions retain their runtime behavior, but TypeScript integrations
115
+ that explicitly treat the submit result as `Event` must first handle or exclude
116
+ the new `RequiresFinalization` status. Listener callbacks are unchanged and
117
+ continue to receive only `Event`.
118
+
119
+ ### Deferred MOTO payments
120
+
121
+ Deferred Stripe `moto` and `moto-with-save` sessions return a
122
+ `RequiresFinalization` result for backend finalization instead of reporting
123
+ payment success. Integrations opting into this mode must forward the returned
124
+ artifact unchanged and handle definitive declines through
125
+ `reportFinalizationDecline`.
126
+
127
+ The complete [deferred Stripe MOTO integration guide](https://github.com/lightspeed-payments/lsp-online-payments-sdk/blob/main/packages/lsp-online-payments-sdk/docs/deferred-stripe-moto.md)
128
+ is maintained in the GitHub repository and is not included in the npm package.
129
+
112
130
  ## Theming
113
131
 
114
132
  Use `theme` to select a widget appearance:
@@ -133,3 +151,22 @@ import {THEME_COLORS} from '@lightspeed/online-payments-sdk';
133
151
  wrapper.style.backgroundColor = THEME_COLORS.DARK.BACKGROUND;
134
152
  wrapper.style.color = THEME_COLORS.DARK.TEXT;
135
153
  ```
154
+
155
+ ## Checking the SDK version
156
+
157
+ The running SDK version is available at runtime, matching the version published to npm:
158
+
159
+ ```ts
160
+ import {LightspeedPayments, SDK_VERSION} from '@lightspeed/online-payments-sdk';
161
+
162
+ console.log(LightspeedPayments.version); // e.g. "1.8.0"
163
+ console.log(SDK_VERSION); // e.g. "1.8.0"
164
+ ```
165
+
166
+ While a widget is mounted, its mount element also carries a `data-lightspeed-payments-sdk-version` attribute, so the running version can be inspected directly in browser devtools without any application code:
167
+
168
+ ```html
169
+ <div id="payment-widget" data-lightspeed-payments-sdk-version="1.8.0"></div>
170
+ ```
171
+
172
+ The attribute is removed when `controller.unmount()` is called.
package/dist/cjs/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LightspeedPayments = exports.THEME_COLORS = void 0;
3
+ exports.LightspeedPayments = exports.SDK_VERSION = exports.THEME_COLORS = void 0;
4
4
  var v1_1 = require("./v1");
5
+ var version_1 = require("./version");
6
+ Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return version_1.SDK_VERSION; } });
5
7
  var v1_2 = require("./v1");
6
8
  Object.defineProperty(exports, "THEME_COLORS", { enumerable: true, get: function () { return v1_2.THEME_COLORS; } });
7
9
  exports.LightspeedPayments = {
8
10
  v1: v1_1.v1,
11
+ version: version_1.SDK_VERSION,
9
12
  };
@@ -66,6 +66,7 @@ var EventBroadcaster = /** @class */ (function () {
66
66
  var processedEvent = this.applyMiddlewares(event);
67
67
  var eventListener = this.listenersRegistry[event.status];
68
68
  eventListener(processedEvent);
69
+ return processedEvent;
69
70
  };
70
71
  return EventBroadcaster;
71
72
  }());
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NO_SHOW_CONSENT_REASON = exports.NO_SHOW_CONSENT_TEXT = void 0;
4
+ exports.buildNoShowConsentText = buildNoShowConsentText;
5
+ // SDK-owned consent copy for the card-save (no-show) flow — the SDK owns the
6
+ // wording rather than taking free-form text from the backend (mirrors
7
+ // `PARTNER_CARD_CONSENT_TEXT` in `stripe/terms.ts`). Plain constants, so they can
8
+ // be extracted for Transifex later. `{business}`/`{reason}` interpolated at render.
9
+ exports.NO_SHOW_CONSENT_TEXT = {
10
+ MANDATORY_TEMPLATE: 'By providing your card information, you authorize {business} to charge your card for {reason} in accordance with their terms.',
11
+ };
12
+ // Hardcoded for now; may become a per-session input from the backend later.
13
+ exports.NO_SHOW_CONSENT_REASON = 'a no-show fee if you do not honor your reservation';
14
+ function buildNoShowConsentText(businessName) {
15
+ return exports.NO_SHOW_CONSENT_TEXT.MANDATORY_TEMPLATE.replace(/\{business\}/g, function () { return businessName; }).replace(/\{reason\}/g, function () { return exports.NO_SHOW_CONSENT_REASON; });
16
+ }
@@ -39,13 +39,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.AdyenWidget = void 0;
40
40
  var adyen_web_1 = require("@adyen/adyen-web");
41
41
  var ResultBuilder_1 = require("./ResultBuilder");
42
+ var terms_1 = require("./terms");
42
43
  function mount(mountElement, session, eventBroadcaster) {
44
+ return __awaiter(this, void 0, void 0, function () {
45
+ return __generator(this, function (_a) {
46
+ return [2 /*return*/, mountSessionsFlow(mountElement, session.context, eventBroadcaster)];
47
+ });
48
+ });
49
+ }
50
+ // Entry for the multi-step contract (v1.mountMultiStepWidget). Kept separate
51
+ // from `mount` so the event-based Sessions flow keeps its WidgetController shape.
52
+ function mountMultiStep(mountElement, session, eventBroadcaster, onRequiresFinalization) {
53
+ return __awaiter(this, void 0, void 0, function () {
54
+ return __generator(this, function (_a) {
55
+ return [2 /*return*/, mountAdvancedFlow(mountElement, session.context, eventBroadcaster, onRequiresFinalization)];
56
+ });
57
+ });
58
+ }
59
+ function mountSessionsFlow(mountElement, context, eventBroadcaster) {
43
60
  return __awaiter(this, void 0, void 0, function () {
44
61
  var checkoutConfig, checkout, dropinConfiguration, dropIn;
45
62
  return __generator(this, function (_a) {
46
63
  switch (_a.label) {
47
64
  case 0:
48
- checkoutConfig = createAdyenConfiguration(session.context, eventBroadcaster);
65
+ injectAdyenCssModuleFix();
66
+ checkoutConfig = createSessionsConfiguration(context, eventBroadcaster);
49
67
  return [4 /*yield*/, (0, adyen_web_1.AdyenCheckout)(checkoutConfig)];
50
68
  case 1:
51
69
  checkout = _a.sent();
@@ -66,7 +84,163 @@ function mount(mountElement, session, eventBroadcaster) {
66
84
  });
67
85
  });
68
86
  }
69
- function createAdyenConfiguration(context, eventBroadcaster) {
87
+ // Multi-step Advanced flow: submit() surfaces the encrypted payment data for
88
+ // the host to POST; handleNextAction() feeds the server response back to drive
89
+ // native 3DS2. See MultiStepController in common.ts.
90
+ function mountAdvancedFlow(mountElement, context, eventBroadcaster, onRequiresFinalization) {
91
+ return __awaiter(this, void 0, void 0, function () {
92
+ var stashedActions, pendingResolve, isValid, checkoutConfig, checkout, dropinConfiguration, dropIn;
93
+ var _a;
94
+ return __generator(this, function (_b) {
95
+ switch (_b.label) {
96
+ case 0:
97
+ isValid = false;
98
+ checkoutConfig = {
99
+ clientKey: context.clientKey,
100
+ environment: context.env,
101
+ countryCode: context.countryCode,
102
+ locale: context.locale,
103
+ amount: { value: (_a = context.amount) !== null && _a !== void 0 ? _a : 0, currency: context.currency },
104
+ paymentMethodsResponse: context.paymentMethods,
105
+ analytics: { enabled: false },
106
+ onSubmit: function (state, _component, actions) {
107
+ stashedActions = actions;
108
+ var result = {
109
+ status: 'RequiresFinalization',
110
+ paymentData: {
111
+ paymentMethod: state.data.paymentMethod,
112
+ browserInfo: state.data.browserInfo,
113
+ origin: window.location.origin,
114
+ },
115
+ saveSessionId: context.saveSessionId,
116
+ };
117
+ if (pendingResolve) {
118
+ pendingResolve(result);
119
+ pendingResolve = undefined;
120
+ }
121
+ else {
122
+ // Express methods (Google Pay / Apple Pay) fire onSubmit from their own
123
+ // button with no submit() pending — hand the result to the host instead.
124
+ onRequiresFinalization === null || onRequiresFinalization === void 0 ? void 0 : onRequiresFinalization(result);
125
+ }
126
+ },
127
+ onAdditionalDetails: function (state, _component, actions) {
128
+ stashedActions = actions;
129
+ pendingResolve === null || pendingResolve === void 0 ? void 0 : pendingResolve({
130
+ status: 'RequiresAction',
131
+ paymentData: {
132
+ details: state.data.details,
133
+ paymentData: state.data.paymentData,
134
+ },
135
+ saveSessionId: context.saveSessionId,
136
+ });
137
+ pendingResolve = undefined;
138
+ },
139
+ onError: function (error) {
140
+ // Resolve a waiting submit()/handleNextAction() so the host doesn't hang.
141
+ if (pendingResolve) {
142
+ pendingResolve({ status: 'Error', error: error });
143
+ pendingResolve = undefined;
144
+ return;
145
+ }
146
+ eventBroadcaster.publish({
147
+ status: 'Error',
148
+ code: 'Processing',
149
+ loggingEnrichment: error,
150
+ });
151
+ },
152
+ // Surface form validity so the host can gate its own submit button; onSubmit
153
+ // never fires for an invalid form, so this is the host's only validity signal.
154
+ onChange: function (state) {
155
+ var _a;
156
+ isValid = state.isValid;
157
+ var paymentMethodType = (_a = state.data.paymentMethod) === null || _a === void 0 ? void 0 : _a.type;
158
+ eventBroadcaster.publish({
159
+ status: 'Change',
160
+ code: isValid ? 'Complete' : 'Incomplete',
161
+ paymentMethodType: typeof paymentMethodType === 'string' ? paymentMethodType : undefined,
162
+ });
163
+ },
164
+ };
165
+ injectAdyenCssModuleFix();
166
+ return [4 /*yield*/, (0, adyen_web_1.AdyenCheckout)(checkoutConfig)];
167
+ case 1:
168
+ checkout = _b.sent();
169
+ dropinConfiguration = {
170
+ paymentMethodComponents: [adyen_web_1.Card, adyen_web_1.ApplePay, adyen_web_1.GooglePay, adyen_web_1.Bancontact],
171
+ showStoredPaymentMethods: false,
172
+ paymentMethodsConfiguration: {
173
+ card: {
174
+ // Hide the card's built-in button (host renders its own submit). In
175
+ // adyen-web v6 showPayButton is per-component, not Dropin-level. Express
176
+ // wallets keep their native buttons.
177
+ showPayButton: false,
178
+ // Suppress Click to Pay in a zero-value card-save. `_disableClickToPay`
179
+ // is an internal adyen-web flag; replace on the next upgrade.
180
+ _disableClickToPay: true,
181
+ },
182
+ },
183
+ // Render the consent copy only once the Drop-in has finished loading, so it
184
+ // doesn't flash on its own before the card/wallet options appear.
185
+ onReady: function () { return renderConsentDisclosure(mountElement, context.businessName); },
186
+ };
187
+ dropIn = new adyen_web_1.Dropin(checkout, dropinConfiguration).mount(mountElement);
188
+ return [2 /*return*/, {
189
+ unmount: function () {
190
+ dropIn.unmount();
191
+ removeConsentDisclosure(mountElement);
192
+ },
193
+ submit: function () {
194
+ return new Promise(function (resolve) {
195
+ // adyen-web renders inline field errors but never calls onSubmit for an
196
+ // invalid form, so waiting on pendingResolve would hang the host. Trigger
197
+ // the field errors and settle deterministically instead.
198
+ if (!isValid) {
199
+ dropIn.submit();
200
+ resolve({ status: 'Invalid' });
201
+ return;
202
+ }
203
+ pendingResolve = resolve;
204
+ dropIn.submit();
205
+ });
206
+ },
207
+ handleNextAction: function (serverResponse) {
208
+ return new Promise(function (resolve) {
209
+ var _a;
210
+ var response = (serverResponse !== null && serverResponse !== void 0 ? serverResponse : {});
211
+ if (response.action) {
212
+ // 3DS1 (redirect) is out of scope: surface as an error so the host can
213
+ // ask the shopper for a different card.
214
+ if (response.action.type === 'redirect') {
215
+ resolve({
216
+ status: 'Error',
217
+ error: { code: 'RedirectNotSupported', resultCode: response.resultCode },
218
+ });
219
+ return;
220
+ }
221
+ // Native 3DS2 step: let the component perform it, then resolve once
222
+ // onAdditionalDetails hands us the next payload for the host to POST.
223
+ pendingResolve = resolve;
224
+ stashedActions === null || stashedActions === void 0 ? void 0 : stashedActions.resolve(response);
225
+ return;
226
+ }
227
+ // Terminal response: let the component finalize its UI (its own result
228
+ // screen), drop the now-stale pre-submission consent, report Complete.
229
+ stashedActions === null || stashedActions === void 0 ? void 0 : stashedActions.resolve(response);
230
+ removeConsentDisclosure(mountElement);
231
+ resolve({ status: 'Complete', resultCode: (_a = response.resultCode) !== null && _a !== void 0 ? _a : 'Unknown' });
232
+ });
233
+ },
234
+ }];
235
+ }
236
+ });
237
+ });
238
+ }
239
+ function createSessionsConfiguration(context, eventBroadcaster) {
240
+ var _a;
241
+ if (!context.sessionId || !context.sessionData) {
242
+ throw new Error('Sessions flow requires sessionId and sessionData; received an advanced-flow session.');
243
+ }
70
244
  return {
71
245
  clientKey: context.clientKey,
72
246
  session: {
@@ -74,15 +248,10 @@ function createAdyenConfiguration(context, eventBroadcaster) {
74
248
  sessionData: context.sessionData,
75
249
  },
76
250
  countryCode: context.countryCode,
77
- amount: {
78
- value: context.amount,
79
- currency: context.currency,
80
- },
251
+ amount: { value: (_a = context.amount) !== null && _a !== void 0 ? _a : 0, currency: context.currency },
81
252
  locale: context.locale,
82
253
  environment: context.env,
83
- analytics: {
84
- enabled: false,
85
- },
254
+ analytics: { enabled: false },
86
255
  onPaymentCompleted: function (data) {
87
256
  eventBroadcaster.publish(ResultBuilder_1.ResultBuilder.fromPaymentCompletedData(data));
88
257
  },
@@ -99,6 +268,43 @@ function createAdyenConfiguration(context, eventBroadcaster) {
99
268
  paymentMethodsResponse: context.paymentMethods,
100
269
  };
101
270
  }
271
+ // Appends the SDK-owned no-show consent copy under the Drop-in. Idempotent, so
272
+ // a repeated onReady never duplicates it; a no-op when no business name is set.
273
+ function renderConsentDisclosure(mountElement, businessName) {
274
+ if (!businessName)
275
+ return;
276
+ if (mountElement.querySelector('[data-lspay="consent-disclosure"]'))
277
+ return;
278
+ var disclosure = document.createElement('p');
279
+ disclosure.setAttribute('data-lspay', 'consent-disclosure');
280
+ disclosure.style.marginTop = '16px';
281
+ disclosure.textContent = (0, terms_1.buildNoShowConsentText)(businessName);
282
+ mountElement.appendChild(disclosure);
283
+ }
284
+ // Removes the consent copy once the flow finalizes or unmounts, so it doesn't
285
+ // linger on the Drop-in's result screen (it only applies pre-submission).
286
+ function removeConsentDisclosure(mountElement) {
287
+ var _a;
288
+ (_a = mountElement.querySelector('[data-lspay="consent-disclosure"]')) === null || _a === void 0 ? void 0 : _a.remove();
289
+ }
290
+ // Adyen's Drop-in CSS uses hashed class names (e.g. .loading-input__spinner--abc123),
291
+ // but when the host app's bundler processes the CSS as modules it rehashes them,
292
+ // causing a mismatch with the unhashed class names Adyen writes to the DOM.
293
+ // We inject plain (unhashed) rules so the spinner/loading states render correctly.
294
+ var adyenCssFixInjected = false;
295
+ function injectAdyenCssModuleFix() {
296
+ if (adyenCssFixInjected)
297
+ return;
298
+ adyenCssFixInjected = true;
299
+ var style = document.createElement('style');
300
+ style.setAttribute('data-lspay', 'adyen-css-module-fix');
301
+ style.textContent = [
302
+ ".loading-input__spinner { z-index: 1; width: 100%; height: 100%; display: none; position: absolute; top: 0; left: 0; }",
303
+ ".loading-input__spinner--active { display: flex; align-items: center; justify-content: center; }",
304
+ ".loading-input__form--loading { opacity: 0; }",
305
+ ].join('\n');
306
+ document.head.appendChild(style);
307
+ }
102
308
  function createDropinConfiguration() {
103
309
  return {
104
310
  paymentMethodComponents: [adyen_web_1.Card, adyen_web_1.ApplePay, adyen_web_1.Bancontact, adyen_web_1.GooglePay, adyen_web_1.Redirect, adyen_web_1.Twint],
@@ -106,4 +312,5 @@ function createDropinConfiguration() {
106
312
  }
107
313
  exports.AdyenWidget = {
108
314
  mount: mount,
315
+ mountMultiStep: mountMultiStep,
109
316
  };
@@ -17,4 +17,5 @@ var themes_1 = require("./stripe/themes");
17
17
  Object.defineProperty(exports, "THEME_COLORS", { enumerable: true, get: function () { return themes_1.THEME_COLORS; } });
18
18
  exports.v1 = {
19
19
  mountPaymentWidget: widget_1.mountPaymentWidget,
20
+ mountMultiStepWidget: widget_1.mountMultiStepWidget,
20
21
  };
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ /** Locale-selected UI text rendered alongside Stripe Elements. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.getSavePaymentMethodText = getSavePaymentMethodText;
5
+ exports.getCardDeclinedText = getCardDeclinedText;
6
+ var SAVE_PAYMENT_METHOD_TEXT = {
7
+ de: 'Zahlungsinformationen für zukünftige Einkäufe speichern',
8
+ en: 'Save payment details for future purchases',
9
+ fr: 'Enregistrer les informations de paiement pour vos prochains achats',
10
+ nl: 'Betaalgegevens opslaan voor toekomstige aankopen',
11
+ };
12
+ var CARD_DECLINED_TEXT = {
13
+ de: 'Ihre Karte wurde abgelehnt.',
14
+ en: 'Your card was declined.',
15
+ fr: 'Votre carte a été refusée.',
16
+ nl: 'Uw kaart is geweigerd.',
17
+ };
18
+ function getLanguage(locale) {
19
+ var _a;
20
+ var resolvedLocale = !locale || locale === 'auto'
21
+ ? ((_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.language) || 'en'
22
+ : locale;
23
+ return resolvedLocale.toLowerCase().split('-')[0];
24
+ }
25
+ function getSavePaymentMethodText(locale) {
26
+ var language = getLanguage(locale);
27
+ return SAVE_PAYMENT_METHOD_TEXT[language] || SAVE_PAYMENT_METHOD_TEXT.en;
28
+ }
29
+ function getCardDeclinedText(locale) {
30
+ var language = getLanguage(locale);
31
+ return CARD_DECLINED_TEXT[language] || CARD_DECLINED_TEXT.en;
32
+ }
@@ -1,2 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isDeferredStripeSession = isDeferredStripeSession;
4
+ exports.isIntentBoundStripeSession = isIntentBoundStripeSession;
5
+ function isDeferredStripeSession(session) {
6
+ return session.context.intentMode === 'deferred';
7
+ }
8
+ function isIntentBoundStripeSession(session) {
9
+ return (session.context.intentMode === undefined ||
10
+ session.context.intentMode === 'intent-bound');
11
+ }
@@ -52,6 +52,7 @@ exports.createPaymentElementOptions = createPaymentElementOptions;
52
52
  exports.createAddressElementOptions = createAddressElementOptions;
53
53
  exports.buildCardConsentText = buildCardConsentText;
54
54
  exports.createCardConsentElement = createCardConsentElement;
55
+ exports.createSavePaymentMethodElement = createSavePaymentMethodElement;
55
56
  exports.createPaymentElement = createPaymentElement;
56
57
  exports.setupChangeEventHandler = setupChangeEventHandler;
57
58
  exports.createAddressElement = createAddressElement;
@@ -64,16 +65,17 @@ var ResultBuilder_1 = require("./ResultBuilder");
64
65
  var error_1 = require("../error");
65
66
  var themes_1 = require("./themes");
66
67
  var terms_1 = require("./terms");
68
+ var localizedText_1 = require("./localizedText");
67
69
  /**
68
70
  * Initialize Stripe with the given context
69
71
  */
70
72
  function initStripe(context, theme) {
71
73
  return __awaiter(this, void 0, void 0, function () {
72
- var publishableKey, clientSecret, customerSessionClientSecret, loadedStripe, stripe, themeConfig, elementsOptions, elements;
74
+ var publishableKey, loadedStripe, stripe, themeConfig, sharedElementsOptions, elements, elementsOptions, elementsOptions;
73
75
  return __generator(this, function (_a) {
74
76
  switch (_a.label) {
75
77
  case 0:
76
- publishableKey = context.publishableKey, clientSecret = context.clientSecret, customerSessionClientSecret = context.customerSessionClientSecret;
78
+ publishableKey = context.publishableKey;
77
79
  return [4 /*yield*/, (0, stripe_js_1.loadStripe)(publishableKey)];
78
80
  case 1:
79
81
  loadedStripe = _a.sent();
@@ -82,8 +84,15 @@ function initStripe(context, theme) {
82
84
  }
83
85
  stripe = loadedStripe;
84
86
  themeConfig = (0, themes_1.getThemeConfig)(theme);
85
- elementsOptions = __assign(__assign(__assign({ clientSecret: clientSecret, customerSessionClientSecret: customerSessionClientSecret }, (context.locale && { locale: context.locale })), (themeConfig && { appearance: themeConfig.appearance })), (themeConfig && { fonts: themeConfig.fonts }));
86
- elements = stripe.elements(elementsOptions);
87
+ sharedElementsOptions = __assign(__assign(__assign({}, (context.locale && { locale: context.locale })), (themeConfig && { appearance: themeConfig.appearance })), (themeConfig && { fonts: themeConfig.fonts }));
88
+ if (context.intentMode === 'deferred') {
89
+ elementsOptions = __assign(__assign(__assign({}, sharedElementsOptions), { mode: 'payment', amount: context.amount, currency: context.currency.toLowerCase(), paymentMethodTypes: context.paymentMethodTypes, paymentMethodCreation: 'manual' }), (context.onBehalfOf && { onBehalfOf: context.onBehalfOf }));
90
+ elements = stripe.elements(elementsOptions);
91
+ }
92
+ else {
93
+ elementsOptions = __assign(__assign({}, sharedElementsOptions), { clientSecret: context.clientSecret, customerSessionClientSecret: context.customerSessionClientSecret });
94
+ elements = stripe.elements(elementsOptions);
95
+ }
87
96
  return [2 /*return*/, { stripe: stripe, elements: elements }];
88
97
  }
89
98
  });
@@ -220,6 +229,24 @@ function createCardConsentElement(consentText) {
220
229
  container.appendChild(text);
221
230
  return container;
222
231
  }
232
+ function createSavePaymentMethodElement(mountElement, onChange, locale) {
233
+ var label = document.createElement('label');
234
+ label.className = 'stripe-save-payment-method-container';
235
+ var checkbox = document.createElement('input');
236
+ checkbox.type = 'checkbox';
237
+ checkbox.className = 'stripe-save-payment-method-checkbox';
238
+ checkbox.dataset.testid = 'save-payment-method-checkbox';
239
+ var text = document.createElement('span');
240
+ text.className = 'stripe-save-payment-method-text';
241
+ text.textContent = (0, localizedText_1.getSavePaymentMethodText)(locale);
242
+ label.appendChild(checkbox);
243
+ label.appendChild(text);
244
+ mountElement.appendChild(label);
245
+ // The label must remain clickable, but its consent text is not selectable.
246
+ label.addEventListener('selectstart', function (event) { return event.preventDefault(); });
247
+ checkbox.addEventListener('change', function () { return onChange(checkbox.checked); });
248
+ return label;
249
+ }
223
250
  /**
224
251
  * Create and mount payment element
225
252
  */
@@ -259,12 +286,12 @@ function createPaymentElement(elements, options, mountElement, eventBroadcaster)
259
286
  * Also includes the current save-payment-method checkbox state (when present) on every Change event,
260
287
  * and emits a Change event when that checkbox state changes.
261
288
  */
262
- function setupChangeEventHandler(paymentElement, eventBroadcaster, addressElement) {
289
+ function setupChangeEventHandler(paymentElement, eventBroadcaster, addressElement, onPaymentValueChange, initialSaveCheckboxState) {
263
290
  var paymentComplete = false;
264
291
  var addressComplete = !addressElement; // If no address element, consider it complete
265
292
  var previousComplete = null;
266
293
  var previousMethodType;
267
- var previousSaveCheckboxState; // Track save checkbox state
294
+ var previousSaveCheckboxState = initialSaveCheckboxState;
268
295
  var emitChangeEvent = function () {
269
296
  var complete = paymentComplete && addressComplete;
270
297
  previousComplete = complete;
@@ -275,23 +302,30 @@ function setupChangeEventHandler(paymentElement, eventBroadcaster, addressElemen
275
302
  })));
276
303
  };
277
304
  paymentElement.on('change', function (event) {
278
- var _a, _b;
305
+ var _a, _b, _c;
279
306
  paymentComplete = event.complete;
307
+ onPaymentValueChange === null || onPaymentValueChange === void 0 ? void 0 : onPaymentValueChange({
308
+ savePaymentMethod: (_a = event.value) === null || _a === void 0 ? void 0 : _a.savePaymentMethod,
309
+ });
280
310
  var completenessChanged = (paymentComplete && addressComplete) !== previousComplete;
281
- var methodType = (_a = event.value) === null || _a === void 0 ? void 0 : _a.type;
311
+ var methodType = (_b = event.value) === null || _b === void 0 ? void 0 : _b.type;
282
312
  var methodTypeChanged = methodType !== undefined && methodType !== previousMethodType;
283
313
  if (methodType !== undefined) {
284
314
  previousMethodType = methodType;
285
315
  }
286
316
  // `savePaymentMethod` reflects the "save for future use" checkbox; it is only
287
317
  // present on the change event when the element renders that checkbox.
288
- var saveCheckboxChanged = false;
289
- var saveCheckboxState = (_b = event.value) === null || _b === void 0 ? void 0 : _b.savePaymentMethod;
290
- if (saveCheckboxState !== undefined) {
291
- saveCheckboxChanged = previousSaveCheckboxState !== saveCheckboxState;
292
- previousSaveCheckboxState = saveCheckboxState;
318
+ var stripeSaveCheckboxChanged = false;
319
+ var stripeSaveCheckboxState = (_c = event.value) === null || _c === void 0 ? void 0 : _c.savePaymentMethod;
320
+ if (initialSaveCheckboxState === undefined &&
321
+ stripeSaveCheckboxState !== undefined) {
322
+ stripeSaveCheckboxChanged =
323
+ previousSaveCheckboxState !== stripeSaveCheckboxState;
324
+ previousSaveCheckboxState = stripeSaveCheckboxState;
293
325
  }
294
- if (completenessChanged || methodTypeChanged || saveCheckboxChanged) {
326
+ if (completenessChanged ||
327
+ methodTypeChanged ||
328
+ stripeSaveCheckboxChanged) {
295
329
  emitChangeEvent();
296
330
  }
297
331
  });
@@ -304,6 +338,13 @@ function setupChangeEventHandler(paymentElement, eventBroadcaster, addressElemen
304
338
  }
305
339
  });
306
340
  }
341
+ return function (saveCheckboxChecked) {
342
+ if (previousSaveCheckboxState === saveCheckboxChecked) {
343
+ return;
344
+ }
345
+ previousSaveCheckboxState = saveCheckboxChecked;
346
+ emitChangeEvent();
347
+ };
307
348
  }
308
349
  /**
309
350
  * Create and mount address element
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Translatable strings for Stripe payment elements.
3
+ * Terms and consent text for Stripe payment elements.
4
4
  * These strings are prepared for Transifex extraction.
5
5
  *
6
6
  * Usage with Transifex:
@@ -53,7 +53,7 @@ function ensureThemeStylesheet() {
53
53
  }
54
54
  var styleElement = document.createElement('style');
55
55
  styleElement.id = THEME_STYLESHEET_ID;
56
- styleElement.textContent = "\n /* Dark theme styles for the mount element */\n .lsp-theme-dark {\n background-color: ".concat(exports.THEME_COLORS.DARK.BACKGROUND, " !important;\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n /* Dark theme styles for Stripe custom terms elements */\n .lsp-theme-dark .stripe-terms-chevron-icon {\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n .lsp-theme-dark .stripe-custom-terms-text {\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n .lsp-theme-dark .stripe-expandable-terms-content p {\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n ");
56
+ styleElement.textContent = "\n /* Dark theme styles for the mount element */\n .lsp-theme-dark {\n background-color: ".concat(exports.THEME_COLORS.DARK.BACKGROUND, " !important;\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n /* Dark theme styles for Stripe custom terms elements */\n .lsp-theme-dark .stripe-terms-chevron-icon {\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n .lsp-theme-dark .stripe-custom-terms-text {\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n .lsp-theme-dark .stripe-expandable-terms-content p {\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n .lsp-theme-dark .stripe-save-payment-method-container {\n color: ").concat(exports.THEME_COLORS.DARK.TEXT, " !important;\n }\n .lsp-theme-dark .stripe-finalization-error {\n color: #ff8a8a !important;\n }\n .lsp-theme-dark .stripe-save-payment-method-checkbox {\n border-color: #697386;\n background-color: #30313d;\n }\n .lsp-theme-dark .stripe-save-payment-method-checkbox:checked {\n border-color: #007ea3;\n background-color: #007ea3;\n }\n .lsp-theme-dark .stripe-save-payment-method-checkbox:focus-visible {\n border-color: #00d4ff;\n box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.25);\n }\n ");
57
57
  document.head.appendChild(styleElement);
58
58
  }
59
59
  /**
@@ -77,7 +77,7 @@ function ensureInvoicingDarkStylesheet() {
77
77
  }
78
78
  var styleElement = document.createElement('style');
79
79
  styleElement.id = INVOICING_DARK_STYLESHEET_ID;
80
- styleElement.textContent = "\n /*\n * Mirror the non-colour .lsp-theme-invoicing rules from\n * lightspeed-global-styles.css. Class selectors match whole tokens, so an\n * element carrying the class lsp-theme-invoicing-dark is never matched by\n * the selector .lsp-theme-invoicing; without these the terms text would\n * fall back to the system font while the Elements iframe renders Inter.\n */\n .lsp-theme-invoicing-dark {\n color: #e3e4e9;\n padding-bottom: 0 !important;\n }\n .lsp-theme-invoicing-dark .stripe-custom-terms-text,\n .lsp-theme-invoicing-dark .stripe-expandable-terms-content p {\n color: #e3e4e9;\n font-family: 'Inter', system-ui, sans-serif;\n }\n /*\n * Links and the expand toggle. lightspeed-global-styles.css colours these\n * #635bff, which is close to unreadable on a dark surface, so they take the\n * consumer dark interactive colour instead. The chevron inherits from its\n * toggle so the two can never drift apart.\n */\n .lsp-theme-invoicing-dark .stripe-custom-terms-text a,\n .lsp-theme-invoicing-dark .stripe-expandable-terms-content a,\n .lsp-theme-invoicing-dark .stripe-expandable-terms-toggle {\n color: #6cb5fb;\n }\n .lsp-theme-invoicing-dark .stripe-terms-chevron-icon {\n color: inherit;\n }\n ";
80
+ styleElement.textContent = "\n /*\n * Mirror the non-colour .lsp-theme-invoicing rules from\n * lightspeed-global-styles.css. Class selectors match whole tokens, so an\n * element carrying the class lsp-theme-invoicing-dark is never matched by\n * the selector .lsp-theme-invoicing; without these the terms text would\n * fall back to the system font while the Elements iframe renders Inter.\n */\n .lsp-theme-invoicing-dark {\n color: #e3e4e9;\n padding-bottom: 0 !important;\n }\n .lsp-theme-invoicing-dark .stripe-custom-terms-text,\n .lsp-theme-invoicing-dark .stripe-expandable-terms-content p,\n .lsp-theme-invoicing-dark .stripe-save-payment-method-container,\n .lsp-theme-invoicing-dark .stripe-finalization-error {\n color: #e3e4e9;\n font-family: 'Inter', system-ui, sans-serif;\n }\n .lsp-theme-invoicing-dark .stripe-finalization-error {\n color: #ffa597;\n }\n .lsp-theme-invoicing-dark .stripe-save-payment-method-checkbox {\n border-color: #525256;\n background-color: #242427;\n }\n .lsp-theme-invoicing-dark .stripe-save-payment-method-checkbox:checked {\n border-color: #286ea8;\n background-color: #286ea8;\n }\n .lsp-theme-invoicing-dark .stripe-save-payment-method-checkbox:focus-visible {\n border-color: #6cb5fb;\n box-shadow: 0 0 0 3px rgba(108, 181, 251, 0.25);\n }\n /*\n * Links and the expand toggle. lightspeed-global-styles.css colours these\n * #635bff, which is close to unreadable on a dark surface, so they take the\n * consumer dark interactive colour instead. The chevron inherits from its\n * toggle so the two can never drift apart.\n */\n .lsp-theme-invoicing-dark .stripe-custom-terms-text a,\n .lsp-theme-invoicing-dark .stripe-expandable-terms-content a,\n .lsp-theme-invoicing-dark .stripe-expandable-terms-toggle {\n color: #6cb5fb;\n }\n .lsp-theme-invoicing-dark .stripe-terms-chevron-icon {\n color: inherit;\n }\n ";
81
81
  document.head.appendChild(styleElement);
82
82
  }
83
83
  /**