PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.6.10
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.6.10
5.6.10 5.6.9 5.6.8 5.6.7 5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / assets / javascripts / bin / front / _paypal_connect.js
latepoint / lib / assets / javascripts / bin / front Last commit date
_customer.js 1 month ago _events.js 1 year ago _paypal_connect.js 3 weeks ago _razorpay_connect.js 2 months ago _stripe_connect.js 1 year ago main.js 1 week ago
_paypal_connect.js
576 lines
1 /*
2 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
3 */
4
5 class LatepointPaypalConnectFront {
6
7 constructor() {
8 this.sdkInstance = null;
9 this.sdkInitPromise = null;
10 this.paypalOrderId = null;
11 this.continueOrderIntentURL = null;
12 this.continueTransactionIntentURL = null;
13 this.ready();
14 }
15
16 ready() {
17 jQuery(document).ready(() => {
18
19 // INITIALIZE PAYMENT METHOD on booking form
20 jQuery('body').on('latepoint:initPaymentMethod', '.latepoint-booking-form-element', (e, data) => {
21 if (data.payment_method === 'paypal_buttons') {
22 latepoint_add_action(data.callbacks_list, async () => {
23 return await this.initPaypalPayment(jQuery(e.currentTarget), data.payment_method, false);
24 });
25 }
26 });
27
28 // INITIALIZE PAYMENT METHOD on order payment form
29 jQuery('body').on('latepoint:initOrderPaymentMethod', '.latepoint-transaction-payment-form', (e, data) => {
30 if (data.payment_processor === 'paypal_ppcp' && data.payment_method === 'paypal_buttons') {
31 latepoint_add_action(data.callbacks_list, async () => {
32 return await this.initPaypalPayment(jQuery(e.currentTarget), data.payment_method, true);
33 });
34 }
35 });
36 });
37 }
38
39 async ensureSdkInitialized() {
40 if (this.sdkInstance) return this.sdkInstance;
41 if (this.sdkInitPromise) return this.sdkInitPromise;
42
43 const config = latepoint_helper.paypal_connect_config;
44
45 const ppOptions = {
46 clientId: config.clientId,
47 merchantId: config.merchantId,
48 partnerAttributionId: config.partnerAttributionId,
49 components: config.components,
50 pageType: 'checkout',
51 clientMetadataId: crypto.randomUUID(),
52 };
53
54 if (config.locale) {
55 ppOptions.locale = config.locale;
56 }
57
58 this.sdkInitPromise = window.paypal.createInstance(ppOptions);
59
60 try {
61 this.sdkInstance = await this.sdkInitPromise;
62 } catch (err) {
63 this.sdkInitPromise = null;
64 throw err;
65 }
66 return this.sdkInstance;
67 }
68
69 _buildEligibilityOptions($element) {
70 const config = latepoint_helper.paypal_connect_config;
71 const opts = { currencyCode: config.currency };
72 const amount = $element.find('.lp-payment-method-content[data-charge-amount]').data('charge-amount');
73 if (amount) opts.amount = String(amount);
74 return opts;
75 }
76
77 async initPaypalPayment($element, paymentMethod, isTransaction) {
78 if (isTransaction) {
79 await this.initPaypalButtonsForTransaction($element);
80 } else {
81 await this.initPaypalButtons($element, paymentMethod);
82 await this.initCardFields($element);
83 }
84 }
85
86 // === BOOKING FORM: PAYPAL / VENMO / PAY LATER BUTTONS ===
87
88 async initPaypalButtons($booking_form_element, payment_method) {
89 const $container = $booking_form_element.find('.lp-paypal-connect-button-container');
90 if (!$container.length) return;
91
92 const sdk = await this.ensureSdkInitialized();
93 const config = latepoint_helper.paypal_connect_config;
94
95 const eligibility = await sdk.findEligibleMethods(this._buildEligibilityOptions($booking_form_element));
96
97 const self = this;
98
99 const makeCallbacks = (sourceType) => ({
100 onApprove: async (data) => {
101 $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
102 try {
103 const response = await self.captureOrder(data.orderId);
104 if (response.status === 'success') {
105 sessionStorage.setItem('lp_paypal_payment_source', response.payment_source_type || sourceType);
106 $booking_form_element.find('input[name="cart[payment_token]"]').val(response.capture_id);
107 latepoint_submit_booking_form($booking_form_element.find('.latepoint-form'));
108 } else {
109 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
110 latepoint_show_message_inside_element(response.message, $booking_form_element.find('.latepoint-body'), 'error');
111 }
112 } catch (err) {
113 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
114 latepoint_show_message_inside_element(err.message || 'Payment error', $booking_form_element.find('.latepoint-body'), 'error');
115 }
116 },
117 onCancel: () => {
118 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
119 },
120 onError: (err) => {
121 console.error('PayPal error:', err);
122 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
123 const errorMessage = (err && err.message) || 'An error occurred with PayPal. Please try again or use a different payment method.';
124 latepoint_show_message_inside_element(errorMessage, $booking_form_element.find('.latepoint-body'), 'error');
125 },
126 });
127
128 // PayPal button
129 if (eligibility.isEligible('paypal')) {
130 const paypalBtn = $container.find('#latepoint-paypal-button')[0];
131 if (paypalBtn) {
132 paypalBtn.hidden = false;
133 const session = sdk.createPayPalOneTimePaymentSession(makeCallbacks('paypal'));
134 paypalBtn.addEventListener('click', async () => {
135 $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
136 try {
137 await session.start(
138 { presentationMode: 'auto' },
139 self.createOrderForBooking($booking_form_element, payment_method)
140 );
141 } catch (err) {
142 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
143 latepoint_show_message_inside_element(
144 (err && err.message) || 'An error occurred with PayPal. Please try again.',
145 $booking_form_element.find('.latepoint-body'),
146 'error'
147 );
148 }
149 });
150 }
151 }
152
153 // Venmo button
154 if (config.enableVenmo && eligibility.isEligible('venmo')) {
155 const venmoBtn = $container.find('#latepoint-venmo-button')[0];
156 if (venmoBtn) {
157 venmoBtn.hidden = false;
158 const session = sdk.createVenmoOneTimePaymentSession(makeCallbacks('venmo'));
159 venmoBtn.addEventListener('click', async () => {
160 $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
161 try {
162 await session.start(
163 { presentationMode: 'auto' },
164 self.createOrderForBooking($booking_form_element, payment_method)
165 );
166 } catch (err) {
167 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
168 latepoint_show_message_inside_element(
169 (err && err.message) || 'An error occurred with PayPal. Please try again.',
170 $booking_form_element.find('.latepoint-body'),
171 'error'
172 );
173 }
174 });
175 }
176 }
177 console.log('Eligibility for Pay Later:', eligibility.isEligible('paylater'));
178 // Pay Later button
179 if (config.enablePayLater && eligibility.isEligible('paylater')) {
180 const paylaterDetails = eligibility.getDetails('paylater');
181 const paylaterBtn = $container.find('#latepoint-paylater-button')[0];
182 if (paylaterBtn) {
183 if (paylaterDetails) {
184 paylaterBtn.productCode = paylaterDetails.productCode;
185 paylaterBtn.countryCode = paylaterDetails.countryCode;
186 }
187 paylaterBtn.hidden = false;
188 const session = sdk.createPayLaterOneTimePaymentSession(makeCallbacks('paylater'));
189 paylaterBtn.addEventListener('click', async () => {
190 $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
191 try {
192 await session.start(
193 { presentationMode: 'auto' },
194 self.createOrderForBooking($booking_form_element, payment_method)
195 );
196 } catch (err) {
197 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
198 latepoint_show_message_inside_element(
199 (err && err.message) || 'An error occurred with PayPal. Please try again.',
200 $booking_form_element.find('.latepoint-body'),
201 'error'
202 );
203 }
204 });
205 }
206 }
207 }
208
209 async createOrderForBooking($element, paymentMethod) {
210 const data = latepoint_create_form_data($element.find('.latepoint-form'), latepoint_helper.paypal_connect_route_create_order, {booking_form_page_url: window.location.href});
211
212 const response = await jQuery.ajax({
213 type: 'post',
214 dataType: 'json',
215 processData: false,
216 contentType: false,
217 url: latepoint_timestamped_ajaxurl(),
218 data
219 });
220
221 if (response.status !== 'success') {
222 throw new Error(response.message);
223 }
224
225 this.paypalOrderId = response.paypal_order_id;
226 this.continueOrderIntentURL = response.continue_order_intent_url;
227 return { orderId: response.paypal_order_id };
228 }
229
230 // === BOOKING FORM: CARD FIELDS (ACDC) ===
231
232 async initCardFields($booking_form_element) {
233 const $container = $booking_form_element.find('.lp-paypal-card-fields-container');
234 if (!$container.length) return;
235
236 const config = latepoint_helper.paypal_connect_config;
237 if (!config.acdcEligible) {
238 $container.hide();
239 $booking_form_element.find('.lp-paypal-card-separator').hide();
240 return;
241 }
242
243 const sdk = await this.ensureSdkInitialized();
244
245 const eligibility = await sdk.findEligibleMethods(this._buildEligibilityOptions($booking_form_element));
246
247 if (!eligibility.isEligible('advanced_cards')) {
248 $container.hide();
249 $booking_form_element.find('.lp-paypal-card-separator').hide();
250 return;
251 }
252
253 const cardSession = sdk.createCardFieldsOneTimePaymentSession();
254
255 const numberField = cardSession.createCardFieldsComponent({
256 type: 'number',
257 placeholder: 'Card number',
258 });
259 const expiryField = cardSession.createCardFieldsComponent({
260 type: 'expiry',
261 placeholder: 'MM / YY',
262 });
263 const cvvField = cardSession.createCardFieldsComponent({
264 type: 'cvv',
265 placeholder: 'CVV',
266 });
267
268 const numberContainer = $container.find('.lp-card-number-field')[0];
269 const expiryContainer = $container.find('.lp-card-expiry-field')[0];
270 const cvvContainer = $container.find('.lp-card-cvv-field')[0];
271
272 if (numberContainer) numberContainer.appendChild(numberField);
273 if (expiryContainer) expiryContainer.appendChild(expiryField);
274 if (cvvContainer) cvvContainer.appendChild(cvvField);
275
276 const self = this;
277
278 $container.find('.lp-card-submit-btn').on('click', async () => {
279 $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
280 try {
281 const orderId = await self.createOrderForCardFields($booking_form_element);
282
283 const { data, state } = await cardSession.submit(orderId);
284
285 switch (state) {
286 case 'succeeded': {
287 const { orderId: approvedOrderId, ...liabilityShift } = data;
288 console.log('3DS liability shift:', liabilityShift);
289
290 if (liabilityShift.liabilityShift === 'NO') {
291 throw new Error('Card authentication failed. Please try a different card or payment method.');
292 }
293
294 const captureResponse = await self.captureOrder(approvedOrderId);
295 if (captureResponse.status === 'success') {
296 sessionStorage.setItem('lp_paypal_payment_source', 'card');
297 $booking_form_element.find('input[name="cart[payment_token]"]').val(captureResponse.capture_id);
298 latepoint_submit_booking_form($booking_form_element.find('.latepoint-form'));
299 } else {
300 throw new Error(captureResponse.message || 'Capture failed');
301 }
302 break;
303 }
304 case 'canceled': {
305 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
306 break;
307 }
308 case 'failed': {
309 throw new Error((data && data.message) || 'Card payment failed. Please try again.');
310 }
311 default: {
312 console.warn('Unhandled card submit state:', state, data);
313 throw new Error('An unexpected error occurred. Please try again.');
314 }
315 }
316 } catch (err) {
317 console.error('Card payment error:', err);
318 $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
319 const errorMessage = (err && err.message) || 'An error occurred. Please try again or use a different payment method.';
320 latepoint_show_message_inside_element(errorMessage, $booking_form_element.find('.latepoint-body'), 'error');
321 }
322 });
323 }
324
325 async createOrderForCardFields($element) {
326 const $methodInput = $element.find('input[name="cart[payment_method]"]');
327 const originalMethod = $methodInput.val();
328 $methodInput.val('paypal_card');
329
330 const data = latepoint_create_form_data(
331 $element.find('.latepoint-form'),
332 latepoint_helper.paypal_connect_route_create_order,
333 {booking_form_page_url: window.location.href}
334 );
335
336 $methodInput.val(originalMethod);
337
338 const response = await jQuery.ajax({
339 type: 'post',
340 dataType: 'json',
341 processData: false,
342 contentType: false,
343 url: latepoint_timestamped_ajaxurl(),
344 data
345 });
346
347 if (response.status !== 'success') {
348 throw new Error(response.message);
349 }
350
351 this.paypalOrderId = response.paypal_order_id;
352 this.continueOrderIntentURL = response.continue_order_intent_url;
353 return response.paypal_order_id;
354 }
355
356 // === TRANSACTION PAYMENT: BUTTONS ONLY ===
357
358 async initPaypalButtonsForTransaction($transaction_intent_form) {
359 const $container = $transaction_intent_form.find('.lp-paypal-connect-button-container');
360 if (!$container.length) return;
361 $transaction_intent_form.find('.latepoint-lightbox-footer').hide();
362 $transaction_intent_form.find('.clean-layout-content-footer').hide();
363
364 const sdk = await this.ensureSdkInitialized();
365 const config = latepoint_helper.paypal_connect_config;
366
367 const eligibility = await sdk.findEligibleMethods(this._buildEligibilityOptions($transaction_intent_form));
368
369 const self = this;
370
371 const makeCallbacks = (sourceType) => ({
372 onApprove: async (data) => {
373 try {
374 const response = await self.captureOrder(data.orderId);
375 if (response.status === 'success') {
376 $transaction_intent_form.find('input[name="payment_token"]').val(response.capture_id);
377 $transaction_intent_form.submit();
378 } else {
379 latepoint_show_message_inside_element(response.message, $transaction_intent_form, 'error');
380 }
381 } catch (err) {
382 latepoint_show_message_inside_element(err.message || 'Payment error', $transaction_intent_form, 'error');
383 }
384 },
385 onCancel: () => {},
386 onError: (err) => {
387 console.error('PayPal transaction error:', err);
388 const errorMessage = (err && err.message) || 'An error occurred with PayPal. Please try again or use a different payment method.';
389 latepoint_show_message_inside_element(errorMessage, $transaction_intent_form, 'error');
390 },
391 });
392
393 // PayPal button
394 if (eligibility.isEligible('paypal')) {
395 const paypalBtn = $container.find('#latepoint-paypal-button')[0];
396 if (paypalBtn) {
397 paypalBtn.hidden = false;
398 const session = sdk.createPayPalOneTimePaymentSession(makeCallbacks('paypal'));
399 paypalBtn.addEventListener('click', async () => {
400 try {
401 await session.start(
402 { presentationMode: 'auto' },
403 self.createOrderForTransaction($transaction_intent_form)
404 );
405 } catch (err) {
406 latepoint_show_message_inside_element(
407 (err && err.message) || 'An error occurred with PayPal. Please try again.',
408 $transaction_intent_form,
409 'error'
410 );
411 }
412 });
413 }
414 }
415
416 // Venmo button
417 if (config.enableVenmo && eligibility.isEligible('venmo')) {
418 const venmoBtn = $container.find('#latepoint-venmo-button')[0];
419 if (venmoBtn) {
420 venmoBtn.hidden = false;
421 const session = sdk.createVenmoOneTimePaymentSession(makeCallbacks('venmo'));
422 venmoBtn.addEventListener('click', async () => {
423 try {
424 await session.start(
425 { presentationMode: 'auto' },
426 self.createOrderForTransaction($transaction_intent_form)
427 );
428 } catch (err) {
429 latepoint_show_message_inside_element(
430 (err && err.message) || 'An error occurred with PayPal. Please try again.',
431 $transaction_intent_form,
432 'error'
433 );
434 }
435 });
436 }
437 }
438
439 // Pay Later button
440 if (config.enablePayLater && eligibility.isEligible('paylater')) {
441 const paylaterDetails = eligibility.getDetails('paylater');
442 const paylaterBtn = $container.find('#latepoint-paylater-button')[0];
443 if (paylaterBtn) {
444 if (paylaterDetails) {
445 paylaterBtn.productCode = paylaterDetails.productCode;
446 paylaterBtn.countryCode = paylaterDetails.countryCode;
447 }
448 paylaterBtn.hidden = false;
449 const session = sdk.createPayLaterOneTimePaymentSession(makeCallbacks('paylater'));
450 paylaterBtn.addEventListener('click', async () => {
451 try {
452 await session.start(
453 { presentationMode: 'auto' },
454 self.createOrderForTransaction($transaction_intent_form)
455 );
456 } catch (err) {
457 latepoint_show_message_inside_element(
458 (err && err.message) || 'An error occurred with PayPal. Please try again.',
459 $transaction_intent_form,
460 'error'
461 );
462 }
463 });
464 }
465 }
466
467 // Card fields (ACDC)
468 const $cardContainer = $transaction_intent_form.find('.lp-paypal-card-fields-container');
469 if ($cardContainer.length && config.acdcEligible && eligibility.isEligible('advanced_cards')) {
470 const cardSession = sdk.createCardFieldsOneTimePaymentSession();
471
472 const numberField = cardSession.createCardFieldsComponent({ type: 'number', placeholder: 'Card number' });
473 const expiryField = cardSession.createCardFieldsComponent({ type: 'expiry', placeholder: 'MM / YY' });
474 const cvvField = cardSession.createCardFieldsComponent({ type: 'cvv', placeholder: 'CVV' });
475
476 const numberEl = $cardContainer.find('.lp-card-number-field')[0];
477 const expiryEl = $cardContainer.find('.lp-card-expiry-field')[0];
478 const cvvEl = $cardContainer.find('.lp-card-cvv-field')[0];
479
480 if (numberEl) numberEl.appendChild(numberField);
481 if (expiryEl) expiryEl.appendChild(expiryField);
482 if (cvvEl) cvvEl.appendChild(cvvField);
483
484 $cardContainer.find('.lp-card-submit-btn').on('click', async () => {
485 $transaction_intent_form.addClass('os-loading');
486 try {
487 const createResponse = await self.createOrderForTransaction($transaction_intent_form);
488 const orderId = createResponse.orderId;
489
490 const { data, state } = await cardSession.submit(orderId);
491
492 switch (state) {
493 case 'succeeded': {
494 const { orderId: approvedOrderId, ...liabilityShift } = data;
495 if (liabilityShift.liabilityShift === 'NO') {
496 throw new Error('Card authentication failed. Please try a different card or payment method.');
497 }
498 const captureResponse = await self.captureOrder(approvedOrderId);
499 if (captureResponse.status === 'success') {
500 $transaction_intent_form.find('input[name="payment_token"]').val(captureResponse.capture_id);
501 $transaction_intent_form.removeClass('os-loading');
502 $transaction_intent_form.submit();
503 } else {
504 throw new Error(captureResponse.message || 'Capture failed');
505 }
506 break;
507 }
508 case 'canceled': {
509 $transaction_intent_form.removeClass('os-loading');
510 break;
511 }
512 case 'failed': {
513 throw new Error((data && data.message) || 'Card payment failed. Please try again.');
514 }
515 default: {
516 throw new Error('An unexpected error occurred. Please try again.');
517 }
518 }
519 } catch (err) {
520 console.error('Card payment error:', err);
521 $transaction_intent_form.removeClass('os-loading');
522 latepoint_show_message_inside_element(
523 (err && err.message) || 'An error occurred. Please try again.',
524 $transaction_intent_form,
525 'error'
526 );
527 }
528 });
529 } else if ($cardContainer.length) {
530 $cardContainer.hide();
531 $transaction_intent_form.find('.lp-paypal-card-separator').hide();
532 }
533 }
534
535 async createOrderForTransaction($form) {
536 const data = latepoint_create_form_data($form, latepoint_helper.paypal_connect_route_create_order_for_transaction);
537
538 const response = await jQuery.ajax({
539 type: 'post',
540 dataType: 'json',
541 processData: false,
542 contentType: false,
543 url: latepoint_timestamped_ajaxurl(),
544 data
545 });
546
547 if (response.status !== 'success') {
548 throw new Error(response.message);
549 }
550
551 this.paypalOrderId = response.paypal_order_id;
552 this.continueTransactionIntentURL = response.continue_transaction_intent_url;
553 return { orderId: response.paypal_order_id };
554 }
555
556 // === SHARED: CAPTURE ORDER ===
557
558 async captureOrder(paypalOrderId) {
559 return await jQuery.ajax({
560 type: 'post',
561 dataType: 'json',
562 url: latepoint_timestamped_ajaxurl(),
563 data: {
564 action: 'latepoint_route_call',
565 route_name: latepoint_helper.paypal_connect_route_capture_order,
566 params: {paypal_order_id: paypalOrderId},
567 layout: 'none',
568 return_format: 'json'
569 }
570 });
571 }
572 }
573
574
575 if (latepoint_helper.is_paypal_connect_enabled) window.latepointPaypalConnectFront = new LatepointPaypalConnectFront();
576