1/** 2 * ConvesioPay Unified WooCommerce Blocks Integration 3 * Handles both card and BTCPay payments based on detected type 4 */ 5 6(function() { 7 'use strict'; 8 9 // Use the proper WooCommerce Blocks API with error handling 10 const { registerPaymentMethod } = window.wc?.wcBlocksRegistry || {}; 11 const { createElement, useEffect, useState } = wp?.element || {}; 12 const { __ } = wp?.i18n || {}; 13 const getSetting = (window.wc?.wcSettings?.getSetting) || function(key, defaultValue) { return defaultValue; }; 14 15 // Check if required APIs are available 16 if (!registerPaymentMethod) { 17 return; 18 } 19 // Get ConvesioPay settings 20 const settings = getSetting('peachpay_convesiopay_unified_data', {}); 21 22 // Global state management 23 let convesioPayComponent = null; 24 let isComponentMounted = false; 25 let btcPaySession = null; 26 let isSessionCreated = false; 27 let paymentConfirmedData = null; 28 let currentPaymentMethod = null; 29 let lastKnownCartTotal = 0; 30 31 /** 32 * Reset session state for new checkout 33 */ 34 const resetSessionState = () => { 35 convesioPayComponent = null; 36 isComponentMounted = false; 37 btcPaySession = null; 38 isSessionCreated = false; 39 paymentConfirmedData = null; 40 currentPaymentMethod = null; 41 }; 42 43 /** 44 * Label component for ConvesioPay 45 */ 46 const ConvesioPayLabel = () => { 47 return createElement('span', { 48 className: 'wc-block-components-payment-method-label' 49 }, 50 '' 51 ); 52 }; 53 54 /** 55 * Detect payment method based on ConvesioPay events and validation data 56 * 57 * @param {Object} event ConvesioPay component event 58 * @return {string} Detected payment method (card, btcpay, applepay, etc.) 59 */ 60 const detectPaymentMethod = (event) => { 61 // Method 1: Check explicit payment method in event 62 if (event.type) { 63 return event.type; 64 } 65 // Default fallback 66 return 'unknown'; 67 }; 68 69 /** 70 * Create PeachPay transaction for ConvesioPay (unified version) 71 * Exact copy of createPeachPayTransaction from checkout.js 72 */ 73 const createPeachPayTransactionUnified = () => { 74 // Check if we have the AJAX configuration 75 if (typeof window.peachpayConvesioPayAjax === 'undefined') { 76 return; 77 } 78 79 // Prepare transaction data for centralized PeachPay endpoint 80 const transactionData = { 81 gateway_id: 'peachpay_convesiopay_card', 82 checkout_location: 'checkout-blocks' // Always blocks for unified 83 }; 84 85 // Debug: Check what URL we're using 86 const transactionUrl = window.peachpayConvesioPayAjax.create_transaction_url || window.peachpayConvesioPayAjax.ajax_url; 87 88 // Prepare FormData based on endpoint type 89 const formData = new FormData(); 90 91 // Check if we're using the centralized endpoint or falling back to AJAX 92 if (window.peachpayConvesioPayAjax.create_transaction_url && 93 !window.peachpayConvesioPayAjax.create_transaction_url.includes('admin-ajax.php')) { 94 95 // Use centralized PeachPay endpoint 96 formData.append('gateway_id', transactionData.gateway_id); 97 formData.append('checkout_location', transactionData.checkout_location); 98 99 } else { 100 // Fallback to standard AJAX with action parameter 101 formData.append('action', 'pp-create-transaction'); 102 formData.append('gateway_id', transactionData.gateway_id); 103 formData.append('checkout_location', transactionData.checkout_location); 104 formData.append('nonce', window.peachpayConvesioPayAjax.nonce); 105 } 106 107 // Send the request 108 fetch(transactionUrl, { 109 method: 'POST', 110 body: formData 111 }) 112 .then(response => response.json()) 113 .then(data => { 114 if (data.success) { 115 } else { 116 } 117 }) 118 .catch(error => { 119 }); 120 }; 121 122 /** 123 * Store payment token in form (unified blocks version) 124 */ 125 const storePaymentTokenUnified = (token) => { 126 // Store in hidden field for traditional checkout 127 const tokenField = document.getElementById('convesiopay-payment-token'); 128 if (tokenField) { 129 tokenField.value = token; 130 } 131 132 // Store globally for WooCommerce Blocks 133 window.convesiopayPaymentToken = token; 134 135 }; 136 137 /** 138 * Enable submit button (unified blocks version) 139 */ 140 const enableSubmitButtonUnified = () => { 141 // For WooCommerce Blocks 142 const submitButton = document.querySelector('.wc-block-components-checkout-place-order-button'); 143 if (submitButton) { 144 submitButton.disabled = false;
145 } 146 147 // For traditional checkout (fallback) 148 const checkoutForm = document.querySelector('form.checkout'); 149 if (checkoutForm) { 150 const submitBtn = checkoutForm.querySelector('#place_order'); 151 if (submitBtn) { 152 submitBtn.disabled = false; 153 } 154 } 155 }; 156 157 /** 158 * Disable submit button (unified blocks version) 159 */ 160 const disableSubmitButtonUnified = () => { 161 // For WooCommerce Blocks 162 const submitButton = document.querySelector('.wc-block-components-checkout-place-order-button'); 163 // if (submitButton) { 164 // submitButton.disabled = true; 165 // } 166 167 // For traditional checkout (fallback) 168 const checkoutForm = document.querySelector('form.checkout'); 169 if (checkoutForm) { 170 const submitBtn = checkoutForm.querySelector('#place_order'); 171 // if (submitBtn) { 172 // submitBtn.disabled = true; 173 // } 174 } 175 }; 176 177 /** 178 * Show error message (unified blocks version) 179 */ 180 const showErrorUnified = (message) => { 181 182 // Skip WordPress notices system - it's causing compatibility issues 183 // Show error directly in checkout validation area 184 try { 185 const checkoutContainer = document.querySelector('.wc-block-checkout') || 186 document.querySelector('.wp-block-woocommerce-checkout') || 187 document.querySelector('#convesiopay-unified-component-container'); 188 189 if (checkoutContainer) { 190 // Remove existing error 191 const existingError = checkoutContainer.querySelector('.convesiopay-unified-error'); 192 if (existingError) { 193 existingError.remove(); 194 } 195 196 // Add new error message 197 const errorDiv = document.createElement('div'); 198 errorDiv.className = 'convesiopay-unified-error wc-block-components-notice-banner is-error'; 199 errorDiv.style.cssText = 'margin: 10px 0; padding: 12px; background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; border-radius: 4px; font-family: inherit;'; 200 errorDiv.innerHTML = `<strong>Payment Error:</strong> ${message}`; 201 202 // Insert at top of checkout container 203 checkoutContainer.insertBefore(errorDiv, checkoutContainer.firstChild); 204 205 // Auto-remove after 8 seconds 206 setTimeout(() => { 207 if (errorDiv && errorDiv.parentNode) { 208 errorDiv.remove(); 209 } 210 }, 8000); 211 } 212 } catch (error) { 213 // Last resort: alert (for critical errors) 214 if (message.includes('token') || message.includes('payment')) { 215 alert('Payment Error: ' + message); 216 } 217 } 218 }; 219 220 /** 221 * Hide error message (unified blocks version) 222 */ 223 const hideErrorUnified = () => { 224 // Skip WordPress notices system - it's causing compatibility issues 225 // Just clear our custom error messages 226 try { 227 const customErrors = document.querySelectorAll('.convesiopay-unified-error'); 228 customErrors.forEach(error => { 229 if (error.parentNode) { 230 error.remove(); 231 } 232 }); 233 } catch (error) { 234 } 235 }; 236 237 /** 238 * Handle card payment using peachpay_convesiopay_card functions (when event.type === 'card') 239 * This mimics what happens when peachpay_convesiopay_card is selected 240 */ 241 const handleCardPaymentWithPeachPayFunctions = async (event, component) => { 242 243 // STEP 1: Call createPeachPayTransaction() (what peachpay_convesiopay_card does) 244 createPeachPayTransactionUnified(); 245 246 // STEP 2: Handle component change validation (like checkout.js handleComponentChange) 247 if (event.isValid) { 248 // All component fields are valid and we can get the token 249 try { 250 const tokenResult = component.createToken(); 251 252 // Handle both Promise and direct string return 253 let token; 254 if (tokenResult && typeof tokenResult.then === 'function') { 255 // It's a Promise, await it 256 token = await tokenResult; 257 } else { 258 // It's a direct value 259 token = tokenResult; 260 } 261 262 if (token) { 263 // Store payment token (exactly like checkout.js storePaymentToken function) 264 storePaymentTokenUnified(token); 265 } 266 267 // Get order data 268 const orderData = getOrderData(); 269 270 if (!orderData.isValid) { 271 throw new Error('Invalid order data'); 272 } 273 274 // Store payment data for blocks processing 275 paymentConfirmedData = { 276 paymentId: 'card_' + Date.now(), 277 status: 'succeeded', 278 paymentMethod: 'card', 279 amount: orderData.amount, 280 currency: orderData.currency, 281 orderNumber: orderData.orderNumber, 282 token: token 283 }; 284 285 currentPaymentMethod = 'card'; 286 287 // Component is valid, enable checkout (exactly like checkout.js enableSubmitButton) 288 enableSubmitButtonUnified(); 289 hideErrorUnified(); 290 291 292 } catch (error) { 293 showErrorUnified('Failed to create payment token'); 294 disableSubmitButtonUnified(); 295 } 296 } else { 297 // Handle errors, one or more component fields are not valid (like checkout.js) 298 showErrorUnified('Please complete all required payment fields'); 299 disableSubmitButtonUnified(); 300 } 301 }; 302 303 /** 304 * Handle card payment using component.createToken() and ConvesioPay API 305 */ 306 const handleCardPayment = async (component, event) => { 307 try { 308 309 // Create payment token using component (following checkout.js pattern) 310 const tokenResult = component.createToken(); 311 312 // Handle both Promise and direct string return 313 let token; 314 if (tokenResult && typeof tokenResult.then === 'function') { 315 // It's a Promise, await it 316 token = await tokenResult; 317 } else { 318 // It's a direct value 319 token = tokenResult; 320 } 321 322 if (!token) { 323 throw new Error('Failed to create payment token'); 324 } 325 326 327 // Get order data 328 const orderData = getOrderData(); 329 330 if (!orderData.isValid) { 331 throw new Error('Invalid order data'); 332 } 333 334 // Store payment data but DON'T auto-click - let user click Place Order 335 paymentConfirmedData = { 336 paymentId: 'card_' + Date.now(), 337 status: 'succeeded', 338 paymentMethod: 'card', 339 amount: orderData.amount, 340 currency: orderData.currency, 341 orderNumber: orderData.orderNumber, 342 token: token 343 }; 344 345 currentPaymentMethod = 'card'; 346 347 // Show success message instead of auto-clicking 348 349 } catch (error) { 350 } 351 }; 352 353 /** 354 * Handle BTCPay payment (following BTCPay blocks pattern) 355 */ 356 const handleBTCPayPayment = (event) => { 357 358 // For BTCPay, only proceed if status is 'Processing' (not 'Pending') 359 if (event.paymentData && event.paymentData.status === 'Processing') { 360 361 // Store payment data and trigger auto-click 362 paymentConfirmedData = { 363 paymentId: event.paymentData.paymentId, 364 invoiceId: event.paymentData.paymentId, 365 status: event.paymentData.status, 366 amount: event.paymentData.amount, 367 currency: event.paymentData.currency, 368 orderNumber: event.paymentData.orderNumber, 369 paymentMethod: 'btcpay' 370 }; 371 372 currentPaymentMethod = 'btcpay'; 373 374 setTimeout(() => { 375 autoClickPlaceOrderButton(); 376 }, 500); 377 } else if (event.paymentData) { 378 } 379 }; 380 381 /** 382 * Handle Apple Pay payment success with auto-submit (NEW - following ConvesioPay docs) 383 */ 384 const handleApplePayPaymentSuccess = async (component, event) => { 385 try { 386 // Get order data 387 const orderData = getOrderData(); 388 389 if (!orderData.isValid) { 390 showErrorUnified('Invalid order data'); 391 return; 392 } 393 394 // Try to create a payment token if component provides createToken method 395 let paymentToken = null; 396 if (component && typeof component.createToken === 'function') { 397 try { 398 paymentToken = await component.createToken(); 399 } catch (tokenError) { 400 // Token creation failed, continue without token 401 } 402 } 403 404 // Try multiple possible locations for payment ID 405 const possiblePaymentId = event.paymentData?.paymentId || 406 event.paymentData?.id || 407 event.paymentId || 408 event.id || 409 event.payment_id || 410 event.transactionId || 411 'applepay_' + Date.now(); 412 413 paymentConfirmedData = { 414 paymentId: possiblePaymentId, 415 status: event.paymentData?.status || event.status || 'succeeded', 416 paymentMethod: 'applepay', 417 amount: event.paymentData?.amount || orderData.amount, 418 currency: event.paymentData?.currency || orderData.currency, 419 orderNumber: event.paymentData?.orderNumber || orderData.orderNumber, 420 token: paymentToken || event.token || event.paymentData?.token 421 }; 422 423 currentPaymentMethod = 'applepay'; 424 425 // Enable the Place Order button and auto-click it 426 setTimeout(() => { 427 let placeOrderButton = document.querySelector('.wc-block-components-checkout-place-order-button') || 428 document.querySelector('.wp-element-button.wc-block-components-checkout-place-order-button') || 429 document.querySelector('button[type="submit"]'); 430 431 if (placeOrderButton) { 432 placeOrderButton.disabled = false;
433 placeOrderButton.removeAttribute('aria-disabled'); 434 placeOrderButton.style.opacity = '1'; 435 placeOrderButton.style.cursor = 'pointer'; 436 placeOrderButton.style.pointerEvents = 'auto'; 437 438 // Force a reflow to ensure styles are applied 439 placeOrderButton.offsetHeight; 440 441 // Auto-click the button to complete the order 442 placeOrderButton.click(); 443 } 444 }, 500); 445 446 } catch (error) { 447 showErrorUnified('Apple Pay failed: ' + error.message); 448 } 449 }; 450 451 /** 452 * Handle Apple Pay payment (LEGACY - requires manual click) 453 */ 454 const handleApplePayPayment = async (component, event) => { 455 try { 456 457 // Store payment data and trigger auto-click 458 paymentConfirmedData = { 459 paymentId: event.paymentData.paymentId, 460 status: event.paymentData.status, 461 paymentMethod: 'applepay', 462 amount: event.paymentData.amount, 463 currency: event.paymentData.currency, 464 orderNumber: event.paymentData.orderNumber 465 }; 466 467 currentPaymentMethod = 'applepay'; 468 469 setTimeout(() => { 470 autoClickPlaceOrderButton(); 471 }, 500); 472 473 } catch (error) { 474 } 475 }; 476 477 /** 478 * Listen for postMessage events from BTCPay iframe 479 */ 480 const setupBTCPayMessageListener = () => { 481 window.addEventListener('message', (event) => { 482 // Verify the origin is from BTCPay 483 if (!event.origin.includes('btcpay') && !event.origin.includes('convesiopay')) { 484 return; 485 } 486 487 try { 488 let messageData; 489 490 // Handle both string and object message data 491 if (typeof event.data === 'string') { 492 try { 493 messageData = JSON.parse(event.data); 494 } catch (e) { 495 messageData = { type: event.data }; 496 } 497 } else { 498 messageData = event.data; 499 } 500 501 // Check for payment status messages - 'Processing' means payment received 502 if (messageData.status === 'Processing') { 503 504 // Extract payment details from the message 505 const extractedData = { 506 invoiceId: messageData.invoiceId, 507 status: 'Processing', 508 paymentId: messageData.invoiceId, 509 paymentMethod: 'btcpay' 510 }; 511 512 paymentConfirmedData = extractedData; 513 514 // Trigger auto-click of place order button 515 setTimeout(() => { 516 autoClickPlaceOrderButton(); 517 }, 500); 518 } 519 520 } catch (error) { 521 } 522 }); 523 }; 524 525 /** 526 * Auto-click the place order button for WooCommerce Blocks checkout 527 */ 528 const autoClickPlaceOrderButton = () => { 529 530 // Use stored payment data 531 if (!paymentConfirmedData) { 532 533 // Disable the place order button 534 const placeOrderButton = document.querySelector('.components-button.wc-block-components-button.wp-element-button.wc-block-components-checkout-place-order-button.contained'); 535 if (placeOrderButton) { 536 // placeOrderButton.disabled = true; 537 placeOrderButton.style.opacity = '0.5'; 538 placeOrderButton.style.cursor = 'not-allowed'; 539 } 540 return; 541 } 542 543 544 // Reset session state after successful payment 545 setTimeout(() => { 546 resetSessionState(); 547 }, 5000); 548 549 // Find the place order button 550 let placeOrderButton = document.querySelector('.components-button.wc-block-components-button.wp-element-button.wc-block-components-checkout-place-order-button.contained'); 551 552 if (!placeOrderButton) { 553 // Try alternative selectors 554 placeOrderButton = document.querySelector('.wc-block-components-checkout-place-order-button') || 555 document.querySelector('[data-block-name="woocommerce/checkout-order-summary-block"] button') || 556 document.querySelector('button[type="submit"]'); 557 } 558 559 if (placeOrderButton) { 560 561 // IMPORTANT: Enable the button before clicking 562 placeOrderButton.disabled = false;
563 // placeOrderButton.removeAttribute('aria-disabled'); 564 placeOrderButton.style.opacity = '1'; 565 placeOrderButton.style.cursor = 'pointer'; 566 placeOrderButton.style.pointerEvents = 'auto'; 567 568 // Force reflow 569 placeOrderButton.offsetHeight; 570 571 // Click the button 572 placeOrderButton.click(); 573 574 } else { 575 console.log('ERROR: No place order button found!'); 576 } 577 }; 578 579 /** 580 * Get current cart total from WooCommerce Blocks store 581 * @returns {number} Current cart total in cents 582 */ 583 const getCurrentCartTotal = () => { 584 // Try to get cart total from blocks store 585 if (window.wp && window.wp.data && window.wp.data.select) { 586 try { 587 const store = window.wp.data.select('wc/store/cart'); 588 const cartTotals = store.getCartTotals(); 589 590 if (cartTotals && cartTotals.total_price) { 591 return parseInt(cartTotals.total_price); 592 } 593 } catch (error) { 594 // Fallback to DOM 595 } 596 } 597 598 // Fallback: Try to get from DOM 599 const totalElement = document.querySelector('.wc-block-components-totals-item__value'); 600 if (totalElement) { 601 const totalText = totalElement.textContent.replace(/[^\d.]/g, ''); 602 const amount = parseFloat(totalText); 603 if (!isNaN(amount)) { 604 return Math.round(amount * 100); // Convert to cents 605 } 606 } 607 608 return 0; 609 }; 610 611 /** 612 * Monitor cart total changes and update component amount for ApplePay/GooglePay 613 */ 614 const setupCartTotalMonitoring = () => { 615 // Listen for WooCommerce Blocks cart updates 616 if (window.wp && window.wp.data && window.wp.data.subscribe) { 617 let previousTotal = getCurrentCartTotal(); 618 619 window.wp.data.subscribe(() => { 620 const currentTotal = getCurrentCartTotal(); 621 622 // Check if total has changed 623 if (currentTotal !== previousTotal && currentTotal > 0) { 624 previousTotal = currentTotal; 625 lastKnownCartTotal = currentTotal; 626 627 // Update component amount if component exists 628 if (convesioPayComponent) { 629 updateComponentAmount(currentTotal); 630 } 631 } 632 }); 633 } 634 635 // Also monitor checkout form changes as a fallback 636 const observer = new MutationObserver(() => { 637 const currentTotal = getCurrentCartTotal(); 638 639 if (currentTotal !== lastKnownCartTotal && currentTotal > 0) { 640 lastKnownCartTotal = currentTotal; 641 642 // Update component amount if component exists 643 if (convesioPayComponent) { 644 updateComponentAmount(currentTotal); 645 } 646 } 647 }); 648 649 // Observe the totals section for changes 650 const totalsElement = document.querySelector('.wc-block-components-totals-wrapper'); 651 if (totalsElement) { 652 observer.observe(totalsElement, { 653 childList: true, 654 subtree: true, 655 characterData: true 656 }); 657 } 658 }; 659 660 /** 661 * Update component amount when cart total changes 662 * Recreates the ApplePay session with new amount 663 */ 664 const updateComponentAmount = async (newAmountInCents) => { 665 try { 666 667 // Step 1: Unmount and destroy the old component 668 if (convesioPayComponent) { 669 try { 670 // Unmount the component if it has an unmount method 671 if (typeof convesioPayComponent.unmount === 'function') { 672 await convesioPayComponent.unmount(); 673 } 674 675 // Destroy the component if it has a destroy method 676 if (typeof convesioPayComponent.destroy === 'function') { 677 convesioPayComponent.destroy(); 678 } 679 } catch (unmountError) { 680 console.log('[ConvesioPay Blocks] Error during unmount/destroy:', unmountError); 681 } 682 683 // Clear the component reference 684 convesioPayComponent = null; 685 isComponentMounted = false;
686 687 // Clear the container 688 const componentContainer = document.querySelector('#convesiopay-unified-component-container'); 689 if (componentContainer) { 690 componentContainer.innerHTML = ''; 691 } 692 } 693 694 // Step 2: Clear session state 695 isSessionCreated = false; 696 const hadBtcPaySession = btcPaySession !== null; 697 btcPaySession = null; 698 699 // Step 3: Create and mount new component 700 const componentResult = await createAndMountConvesioPayComponent(); 701 702 if (!componentResult.success) { 703 console.error('[ConvesioPay Blocks] Failed to create new component:', componentResult.message); 704 return; 705 } 706 707 // Mount the component in the container 708 const componentContainer = document.querySelector('#convesiopay-unified-component-container'); 709 if (componentContainer) { 710 componentContainer.innerHTML = ''; 711 await convesioPayComponent.mount('#convesiopay-unified-component-container'); 712 } 713 714 // Step 4: Get fresh order data with updated amount 715 const orderData = getOrderData(); 716 717 // Override with the new amount (in case getOrderData() hasn't updated yet) 718 orderData.amount = newAmountInCents; 719 720 // Step 5: Create ApplePay session with new amount 721 try { 722 await convesioPayComponent.createApplePaySession({ 723 integration: settings.integration_name || window.peachpayConvesioPayUnified?.integration_name || 'PeachPay', 724 returnUrl: window.location.href, 725 orderNumber: orderData.orderNumber, 726 amount: orderData.amount, 727 currency: orderData.currency, 728 email: orderData.email, 729 name: orderData.name 730 }); 731 } catch (applePayError) { 732 console.log('[ConvesioPay Blocks] ApplePay session creation failed (might not be available):', applePayError); 733 } 734 735 // Step 6: Create BTCPay session and intent if it was previously active 736 if (hadBtcPaySession) { 737 try { 738 739 // Create new BTCPay session with updated amount 740 const sessionResult = await createBTCPaySession(); 741 742 if (sessionResult.success && sessionResult.session) { 743 // Update the global session variable 744 btcPaySession = sessionResult.session; 745 746 // Prepare BTCPay intent data 747 const btcPayIntentData = { 748 session: btcPaySession 749 }; 750 751 // Create the BTCPay intent with the new session 752 await convesioPayComponent.createBTCPayIntent(btcPayIntentData); 753 754 // Re-setup the message listener 755 setupBTCPayMessageListener(); 756 } 757 } catch (btcPayError) { 758 console.error('[ConvesioPay Blocks] BTCPay session/intent recreation failed:', btcPayError); 759 } 760 } 761 762 } catch (error) { 763 console.error('[ConvesioPay Blocks] Error updating component amount:', error); 764 } 765 }; 766 767 /** 768 * Get order data for ConvesioPay including required address fields 769 */ 770 const getOrderData = () => { 771 const orderData = { 772 isValid: false, 773 orderNumber: getOrderNumber(), 774 amount: 0, 775 currency: 'USD', 776 email: '', 777 name: 'Customer', 778 // Required address fields for ConvesioPay API 779 billingAddress: { 780 street: '123 Default Street', // Must be at least 1 character 781 city: 'Default City', 782 state: 'CA', 783 postalCode: '90210', 784 country: 'US' 785 }, 786 shippingAddress: { 787 street: '123 Default Street', // Must be at least 1 character 788 city: 'Default City', 789 state: 'CA', 790 postalCode: '90210', 791 country: 'US' 792 } 793 }; 794 795 // Try to get cart total from blocks store 796 if (window.wp && window.wp.data && window.wp.data.select) { 797 try { 798 const store = window.wp.data.select('wc/store/cart'); 799 const cartTotals = store.getCartTotals(); 800
801 if (cartTotals && cartTotals.total_price) { 802 orderData.amount = parseInt(cartTotals.total_price); 803 } 804 805 if (cartTotals && cartTotals.currency_code) { 806 orderData.currency = cartTotals.currency_code; 807 } 808 809 // Try to get billing address from checkout store 810 try { 811 const checkoutStore = window.wp.data.select('wc/store/checkout'); 812 if (checkoutStore) { 813 const billingData = checkoutStore.getBillingData(); 814 const shippingData = checkoutStore.getShippingData(); 815 816 if (billingData) { 817 orderData.email = billingData.email || orderData.email; 818 orderData.name = (billingData.first_name + ' ' + billingData.last_name).trim() || orderData.name; 819 820 if (billingData.address_1 && billingData.address_1.trim()) { 821 orderData.billingAddress = { 822 street: billingData.address_1.trim() + (billingData.address_2 ? ' ' + billingData.address_2.trim() : ''), 823 city: billingData.city || 'Default City', 824 state: billingData.state || 'CA', 825 postalCode: billingData.postcode || '90210', 826 country: billingData.country || 'US' 827 }; 828 } 829 } 830 831 if (shippingData && shippingData.address_1 && shippingData.address_1.trim()) { 832 orderData.shippingAddress = { 833 street: shippingData.address_1.trim() + (shippingData.address_2 ? ' ' + shippingData.address_2.trim() : ''), 834 city: shippingData.city || 'Default City', 835 state: shippingData.state || 'CA', 836 postalCode: shippingData.postcode || '90210', 837 country: shippingData.country || 'US' 838 }; 839 } else { 840 // Use billing address as shipping if shipping is empty 841 orderData.shippingAddress = { ...orderData.billingAddress }; 842 } 843 } 844 } catch (checkoutError) { 845 } 846 } catch (error) { 847 } 848 } 849 850 // Try to get amount from DOM if store failed 851 if (orderData.amount === 0) { 852 const totalElement = document.querySelector('.wc-block-components-totals-item__value'); 853 if (totalElement) { 854 const totalText = totalElement.textContent.replace(/[^\d.]/g, ''); 855 const amount = parseFloat(totalText); 856 if (!isNaN(amount)) { 857 orderData.amount = Math.round(amount * 100); // Convert to cents 858 } 859 } 860 } 861 862 // Try to get customer data from checkout form fields if store failed 863 if (orderData.email === '') { 864 const emailField = document.querySelector('input[type="email"]'); 865 if (emailField && emailField.value) { 866 orderData.email = emailField.value; 867 } else { 868 orderData.email = '[email protected]'; 869 } 870 } 871 872 // Ensure we have valid data and valid street addresses (ConvesioPay requirement) 873 orderData.isValid = orderData.amount > 0; 874 875 // Final validation - ensure street addresses are never empty 876 if (!orderData.billingAddress.street || orderData.billingAddress.street.trim().length === 0) { 877 orderData.billingAddress.street = '123 Default Street'; 878 } 879 if (!orderData.shippingAddress.street || orderData.shippingAddress.street.trim().length === 0) { 880 orderData.shippingAddress.street = '123 Default Street'; 881 } 882 883 884 return orderData; 885 }; 886 887 /** 888 * Get order number 889 */ 890 const getOrderNumber = () => { 891 // Try to get order number from blocks checkout store 892 if (window.wp && window.wp.data && window.wp.data.select) { 893 try { 894 const checkoutStore = window.wp.data.select('wc/store/checkout'); 895 896 if (checkoutStore) { 897 const orderId = checkoutStore.getOrderId(); 898 899 if (orderId) { 900 return orderId.toString(); 901 } 902 } 903 } catch (e) { 904 console.log('[ConvesioPay Blocks] Error accessing checkout store:', e); 905 } 906 } 907 908 // Fallback: Try to get from URL parameters
909 const urlParams = new URLSearchParams(window.location.search); 910 const orderId = urlParams.get('order'); 911 912 if (orderId) { 913 return orderId; 914 } 915 916 // Generate session-based identifier 917 const timestamp = Date.now(); 918 const random = Math.floor(Math.random() * 10000); 919 return `SESSION-${timestamp}-${random}`; 920 }; 921 922 /** 923 * Create BTCPay session via AJAX call 924 */ 925 const createBTCPaySession = async () => { 926 if (isSessionCreated && btcPaySession) { 927 return { success: true, session: btcPaySession }; 928 } 929 930 try { 931 const orderData = getOrderData(); 932 if (!orderData.isValid) { 933 throw new Error('Unable to get order details'); 934 } 935 936 const requestData = { 937 action: 'peachpay_convesiopay_create_btcpay_session', 938 nonce: settings.nonce || '', 939 integration: settings.integration_name, 940 returnUrl: window.location.href, 941 orderNumber: orderData.orderNumber, 942 amount: orderData.amount, 943 currency: orderData.currency, 944 email: orderData.email, 945 name: orderData.name, 946 // Include required address fields for ConvesioPay API 947 billingAddress: JSON.stringify(orderData.billingAddress), 948 shippingAddress: JSON.stringify(orderData.shippingAddress) 949 }; 950 951 952 const response = await fetch(settings.ajax_url || '/wp-admin/admin-ajax.php', { 953 method: 'POST', 954 headers: { 955 'Content-Type': 'application/x-www-form-urlencoded', 956 }, 957 body: new URLSearchParams(requestData).toString() 958 }); 959 960 const sessionResponse = await response.json(); 961 962 if (!sessionResponse.success || !sessionResponse.data?.session) { 963 throw new Error(sessionResponse.message || 'Failed to create BTCPay session'); 964 } 965 966 // Store session 967 btcPaySession = sessionResponse.data.session; 968 isSessionCreated = true; 969 970 return { success: true, session: btcPaySession }; 971 972 } catch (error) { 973 return { success: false, message: error.message }; 974 } 975 }; 976 977 /** 978 * Create and mount ConvesioPay component 979 */ 980 const createAndMountConvesioPayComponent = async () => { 981 if (isComponentMounted && convesioPayComponent) { 982 return { success: true, component: convesioPayComponent }; 983 } 984 985 try { 986 // Check if ConvesioPay SDK is available 987 if (typeof window.ConvesioPay === 'undefined') { 988 throw new Error('ConvesioPay SDK not available'); 989 } 990 991 // Get ConvesioPay configuration 992 const config = settings.convesiopay_config || {}; 993 const clientKey = config.clientKey; 994 const apiKey = config.apiKey; 995 const integrationName = settings.integration_name; 996 997 if (!apiKey) { 998 throw new Error('ConvesioPay api key not available'); 999 } 1000 1001 // Create ConvesioPay instance 1002 const cpay = window.ConvesioPay(apiKey); 1003 1004 // Get order data for customer email 1005 const orderData = getOrderData(); 1006 // Initialize component 1007 const component = cpay.component({ 1008 environment: config.api_url?.includes('qa') ? 'test' : 'live', 1009 clientKey: clientKey, 1010 customerEmail: orderData.email || '[email protected]', 1011 integration: settings.integration_name, 1012 theme: settings.checkout_theme || 'light' 1013 }); 1014 1015 // Store globally 1016 convesioPayComponent = component; 1017 isComponentMounted = true; 1018 1019 // Set up event listeners 1020 component.on('change', (event) => { 1021 1022 // Check if this is a card payment event based on event.type 1023 if (event.type === 'card') { 1024 handleCardPaymentWithPeachPayFunctions(event, component); 1025 } else if (event.type === 'btcpay') { 1026 // Handle BTCPay payments directly when event.type is btcpay 1027 handleBTCPayPayment(event); 1028 } else if (event.type === 'applepay') { 1029 // Handle ApplePay payments with auto-submit when successful 1030 if (event.isSuccessful) { 1031 // ApplePay payment intent was successfully authorized 1032 handleApplePayPaymentSuccess(component, event); 1033 } else if (event.errors) { 1034 // Handle ApplePay errors 1035 const errorMessage = event.errors[0]?.message || 'ApplePay payment failed'; 1036 showErrorUnified(errorMessage); 1037 } 1038 } else { 1039 // Detect payment method type and handle accordingly for other methods 1040 const paymentMethod = detectPaymentMethod(event); 1041 1042 // Only process payment when user explicitly submits/confirms, not on input change 1043 if (paymentMethod === 'card' && event.isValid === true && event.isSuccessful === true) { 1044 handleCardPayment(component, event); 1045 } else if (paymentMethod === 'btcpay' && event.isValid === true && event.isSuccessful === true) { 1046 handleBTCPayPayment(event); 1047 } else if (paymentMethod === 'applepay' && event.isValid === true && event.isSuccessful === true) { 1048 handleApplePayPayment(component, event); 1049 } else { 1050 } 1051 } 1052 }); 1053 1054 return { success: true, component: component }; 1055 1056 } catch (error) { 1057 return { success: false, message: error.message }; 1058 } 1059 }; 1060 1061 /** 1062 * Mount ConvesioPay component directly in the content div (following BTCPay blocks pattern) 1063 */ 1064 const mountConvesioPayComponent = async () => {
1065 // Check if component is already mounted in the content 1066 const existingComponent = document.querySelector('#convesiopay-unified-component-container .convesiopay-component'); 1067 if (existingComponent) { 1068 return; 1069 } 1070 1071 try { 1072 // Create and mount the component 1073 const componentResult = await createAndMountConvesioPayComponent(); 1074 1075 if (componentResult.success) { 1076 // Find the content container 1077 const contentContainer = document.querySelector('.wc-convesiopay-unified-blocks-content'); 1078 if (!contentContainer) { 1079 throw new Error('Content container not found'); 1080 } 1081 1082 // The component container already exists in the content div 1083 const componentContainer = contentContainer.querySelector('#convesiopay-unified-component-container'); 1084 if (!componentContainer) { 1085 throw new Error('Component container not found in content'); 1086 } 1087 1088 // Clear the loading content 1089 componentContainer.innerHTML = ''; 1090 1091 // Mount the component in the container 1092 await convesioPayComponent.mount('#convesiopay-unified-component-container'); 1093 1094 // Ensure the mounted ConvesioPay component takes full width 1095 const mountedComponent = document.querySelector('#convesiopay-unified-component-container .convesiopay-component, #convesiopay-unified-component-container iframe, #convesiopay-unified-component-container > div'); 1096 if (mountedComponent) { 1097 mountedComponent.style.width = '100%'; 1098 mountedComponent.style.maxWidth = '100%'; 1099 mountedComponent.style.minWidth = '100%'; 1100 } 1101 1102 // Add CSS to ensure ConvesioPay component takes full width 1103 const styleId = 'convesiopay-full-width-styles'; 1104 if (!document.getElementById(styleId)) { 1105 const style = document.createElement('style'); 1106 style.id = styleId; 1107 style.textContent = ` 1108 #convesiopay-unified-component-container { 1109 width: 100% !important; 1110 } 1111 #convesiopay-unified-component-container .convesiopay-component, 1112 #convesiopay-unified-component-container iframe, 1113 #convesiopay-unified-component-container > div { 1114 width: 100% !important; 1115 max-width: 100% !important; 1116 min-width: 100% !important; 1117 } 1118 .convesiopay-component-container { 1119 width: 100% !important; 1120 } 1121 /* Hide radio button when ConvesioPay is the only payment method */ 1122 .wc-block-components-radio-control-accordion-option:only-child .wc-block-components-radio-control__option { 1123 display: none !important; 1124 } 1125 /* Remove padding from accordion content */ 1126 .wc-block-components-radio-control-accordion-content { 1127 padding: 0 !important; 1128 } 1129 `; 1130 document.head.appendChild(style); 1131 } 1132 1133 const orderData = getOrderData(); 1134 1135 // Prepare the request body for createBTCPayIntent 1136 const btcPayIntentData = { 1137 session: btcPaySession, 1138 integration: settings.integration_name, 1139 returnUrl: window.location.href, 1140 orderNumber: orderData.orderNumber, 1141 amount: orderData.amount, 1142 currency: orderData.currency, 1143 email: orderData.email, 1144 name: orderData.name, 1145 billingAddress: orderData.billingAddress, 1146 shippingAddress: orderData.shippingAddress 1147 }; 1148 1149 const applePaySession = await convesioPayComponent.createApplePaySession({ 1150 integration: settings.integration_name, 1151 returnUrl: window.location.href, 1152 orderNumber: orderData.orderNumber, 1153 amount: orderData.amount, 1154 currency: orderData.currency, 1155 email: orderData.email, 1156 name: orderData.name 1157 }); 1158 1159 1160 // Create BTCPay intent using the mounted component (following BTCPay blocks pattern) 1161 // Include required address fields for ConvesioPay API 1162 await convesioPayComponent.createBTCPayIntent(btcPayIntentData); 1163 1164 // Set up postMessage listener for iframe communication 1165 setupBTCPayMessageListener(); 1166 1167 1168 } else { 1169 throw new Error(componentResult.message || 'Failed to create component'); 1170 } 1171 1172 } catch (error) { 1173 console.error('Failed to create ConvesioPay payment:', error.message) 1174 // // Show error in the content area 1175 // const contentContainer = document.querySelector('.wc-convesiopay-unified-blocks-content'); 1176 // if (contentContainer) { 1177 // const errorDiv = document.createElement('div'); 1178 // errorDiv.style.cssText = `
1179 // color: red; 1180 // font-size: 14px; 1181 // text-align: center; 1182 // padding: 20px; 1183 // border: 1px solid #ff0000; 1184 // border-radius: 5px; 1185 // background: #fff5f5; 1186 // margin: 0 0 10px 0; 1187 // `; 1188 // errorDiv.innerHTML = ` 1189 // <div>â Error</div> 1190 // <small>Failed to create ConvesioPay payment: ${error.message}</small> 1191 // `; 1192 1193 // // Insert error message at the top 1194 // contentContainer.insertBefore(errorDiv, contentContainer.firstChild); 1195 // } 1196 } 1197 }; 1198 1199 /** 1200 * Content component for ConvesioPay Unified (following BTCPay blocks pattern) 1201 */ 1202 const ConvesioPayUnifiedContent = (props) => { 1203 const { eventRegistration, emitResponse } = props; 1204 1205 // Create session and mount component when radio button is selected (following BTCPay pattern) 1206 useEffect(() => { 1207 // Create BTCPay session immediately when radio button is selected 1208 createBTCPaySession().then(sessionResult => { 1209 if (sessionResult.success) { 1210 // After session is created, mount the ConvesioPay component 1211 setTimeout(() => { 1212 mountConvesioPayComponent(); 1213 }, 100); // Small delay to ensure DOM is ready 1214 } else { 1215 } 1216 }).catch(error => { 1217 }); 1218 }, []); 1219 1220 // Handle payment setup (when Place Order is clicked) - USE EXISTING SESSION 1221 useEffect(() => { 1222 if (eventRegistration && eventRegistration.onPaymentSetup) { 1223 const unsubscribe = eventRegistration.onPaymentSetup(async () => { 1224 1225 // For ApplePay and Card, we don't need btcPaySession - only check for BTCPay 1226 if (!btcPaySession && currentPaymentMethod === 'btcpay') { 1227 return { 1228 type: emitResponse.responseTypes.ERROR, 1229 message: 'BTCPay session not ready. Please try again.', 1230 }; 1231 } 1232 1233 // For ApplePay and Card, if no session exists, create a fake one 1234 if (!btcPaySession && (currentPaymentMethod === 'applepay' || currentPaymentMethod === 'card')) { 1235 btcPaySession = 'fake-session-' + Date.now(); 1236 } 1237 1238 // Prepare payment method data based on payment type 1239 // Ensure all values are strings or booleans to avoid REST API validation errors 1240 const paymentMethodData = { 1241 peachpay_transaction_id: String('convesio-tx-' + Date.now()), 1242 payment_method_type: String(currentPaymentMethod || paymentConfirmedData?.paymentMethod || 'btcpay') 1243 }; 1244 1245 // Include payment data based on payment method 1246 if (paymentConfirmedData) { 1247 // Add common payment data that PHP expects (use lowercase for WooCommerce Blocks compatibility) 1248 // Convert all values to strings to avoid REST API validation errors 1249 paymentMethodData.paymentid = String(paymentConfirmedData.paymentId || ''); 1250 paymentMethodData.paymentmethod = String(paymentConfirmedData.paymentMethod || ''); 1251 paymentMethodData.status = String(paymentConfirmedData.status || ''); 1252 paymentMethodData.amount = String(paymentConfirmedData.amount || '0'); 1253 paymentMethodData.currency = String(paymentConfirmedData.currency || ''); 1254 paymentMethodData.ordernumber = String(paymentConfirmedData.orderNumber || ''); 1255 1256 // Also add in camelCase format for PHP compatibility 1257 paymentMethodData.paymentId = String(paymentConfirmedData.paymentId || ''); 1258 paymentMethodData.paymentMethod = String(paymentConfirmedData.paymentMethod || ''); 1259 1260 if (paymentConfirmedData.paymentMethod === 'card') { 1261 // Card payment data 1262 paymentMethodData.convesiopay_payment_token = String(paymentConfirmedData.token || ''); 1263 } else if (paymentConfirmedData.paymentMethod === 'applepay') { 1264 // Apple Pay payment data 1265 paymentMethodData.convesiopay_payment_token = String(paymentConfirmedData.token || ''); 1266 paymentMethodData.convesiopay_payment_id = String(paymentConfirmedData.paymentId || ''); 1267 paymentMethodData.paymentId = String(paymentConfirmedData.paymentId || ''); 1268 paymentMethodData.applepay_payment_id = String(paymentConfirmedData.paymentId || ''); 1269 } else if (paymentConfirmedData.paymentMethod === 'btcpay') { 1270 // BTCPay payment data 1271 paymentMethodData.convesiopay_payment_token = String('btc-session-' + btcPaySession); 1272 paymentMethodData.btcpay_session_id = String(btcPaySession || ''); 1273 paymentMethodData.btcpay_payment_id = String(paymentConfirmedData.paymentId || ''); 1274 paymentMethodData.btcpay_invoice_id = String(paymentConfirmedData.invoiceId || ''); 1275 paymentMethodData.btcpay_status = String(paymentConfirmedData.status || ''); 1276 } 1277 } else { 1278 // Fallback for BTCPay session 1279 paymentMethodData.paymentid = String('btc-session-' + btcPaySession); 1280 paymentMethodData.paymentmethod = String('btcpay'); 1281 paymentMethodData.status = String('pending'); 1282 paymentMethodData.convesiopay_payment_token = String('btc-session-' + btcPaySession); 1283 paymentMethodData.btcpay_session_id = String(btcPaySession || ''); 1284 } 1285 1286 // Return success with payment data for WooCommerce to process 1287 return { 1288 type: emitResponse.responseTypes.SUCCESS, 1289 meta: { 1290 paymentMethodData: paymentMethodData, 1291 }, 1292 }; 1293 }); 1294 return unsubscribe; 1295 } 1296 }, [eventRegistration, emitResponse]); 1297 1298 return createElement( 1299 'div', 1300 { 1301 className: 'wc-convesiopay-unified-blocks-content', 1302 style: { 1303 width: '100%', 1304 padding: '0', 1305 margin: '0' 1306 } 1307 }, 1308 createElement( 1309 'div', 1310 { 1311 id: 'convesiopay-unified-component-container', 1312 className: 'convesiopay-component-container', 1313 style: { 1314 border: '1px solid #e0e0e0', 1315 borderRadius: '5px', 1316 background: '#f8f8f8', 1317 margin: '0', 1318 minHeight: '200px', 1319 display: 'flex', 1320 alignItems: 'center', 1321 justifyContent: 'center', 1322 width: '100%' 1323 } 1324 } 1325 ) 1326 ); 1327 }; 1328 1329 /** 1330 * ConvesioPay Unified payment method configuration 1331 */ 1332 const ConvesioPayUnifiedPaymentMethod = { 1333 name: 'peachpay_convesiopay_unified', 1334 label: createElement(ConvesioPayLabel), 1335 content: createElement(ConvesioPayUnifiedContent), 1336 edit: createElement(ConvesioPayUnifiedContent), 1337 canMakePayment: () => { 1338 return true; 1339 }, 1340 ariaLabel: 'ConvesioPay', 1341 supports: { 1342 features: settings.supports || ['products'] 1343 }, 1344 placeOrderButtonLabel: __('Complete Order', 'peachpay-for-woocommerce'), 1345 }; 1346 1347 // Reset session state when the page loads 1348 document.addEventListener('DOMContentLoaded', function() { 1349 setTimeout(() => { 1350 resetSessionState(); 1351 setupCartTotalMonitoring(); 1352 1353 // Initialize last known cart total 1354 lastKnownCartTotal = getCurrentCartTotal(); 1355 }, 1000); 1356 }); 1357 1358 // Register the payment method 1359 if (typeof registerPaymentMethod === 'function') { 1360 registerPaymentMethod(ConvesioPayUnifiedPaymentMethod); 1361 } else { 1362 } 1363 1364})(); // End IIFE
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.