diff --git a/assets/css/admin/facebook-for-woocommerce-whatsapp-utility.css b/assets/css/admin/facebook-for-woocommerce-whatsapp-utility.css new file mode 100644 index 000000000..76fa2dea5 --- /dev/null +++ b/assets/css/admin/facebook-for-woocommerce-whatsapp-utility.css @@ -0,0 +1,249 @@ +.onboarding-card { + background-color: #f7f7f7; + border: 1px solid #ccc; + border-radius: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + width: 680px; + margin: 40px auto 0; /* Top margin 5px, horizontal centering */ +} +.custom-dashicon-check { + position: relative; + display: inline-block; + width: 26px; /* Set the size of the circle */ + height: 26px; /* Set the size of the circle */ + background-color: #1a805b; /* Fill the circle with green */ + border-radius: 50%; /* Make it a circle */ + margin-right: 20px; + top: 50%; + transform: translateY(-50%); +} +.custom-dashicon-check::before { + content: '\f147'; /* Unicode for dashicons-yes-alt */ + font-family: 'Dashicons'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-55%, -45%) scale(1.2); /* Center and slightly enlarge the checkmark */ + font-size: 20px; /* Set the size of the checkmark */ + color: white; /* Make the checkmark white */ + text-shadow: + -4px 0 #1a805b, + 4px 0 #1a805b, + 0 -2px #1a805b, + 0 2px #1a805b; /* Increase shadow offsets to thin the checkmark more */ +} + +.custom-dashicon-circle { + position: relative; + display: inline-block; + width: 20px; /* Set the size of the circle */ + height: 20px; /* Set the size of the circle */ + border-radius: 50%; /* Make it a circle */ + margin-right: 20px; + border: 3px solid #222121ab; + top: 50%; + transform: translateY(-50%); +} +.custom-dashicon-halfcircle { + position: relative; + display: inline-block; + width: 20px; /* Set the size of the circle */ + height: 20px; /* Set the size of the circle */ + border-radius: 50%; /* Make it a circle */ + margin-right: 20px; + border: 3px solid #222121ab; + background-image: linear-gradient(to left, #222121ab 50%, transparent 50%); + background-clip: padding-box; /* Add this line */ + top: 50%; + transform: translateY(-50%); +} +.card-content-icon { + display: flex; +} +.card-item { + padding: 10px 24px; + justify-content: space-between; + display: flex; +} +.divider { + border-bottom: 1px solid #ccc; +} +.review-payment-content { + padding: 20px; + margin-bottom: 10px; +} +.whatsapp-onboarding-button { + margin-left: auto; + position: relative; + top: 50%; + margin: auto 0; /* Ensure button is centered */ +} +.whatsapp-onboarding-done-button { + margin-left: auto; + padding: 6px 0; +} +.card-content { + max-width: 90%; +} +.card-content-icon h2 { + top: 50%; +} +.card-content-icon p { + margin-top: -10px; /* Remove margin top */ +} +.event-config { + display: flex; + flex-direction: row; + padding-top: 10px; +} +.event-config-heading-container { + display: flex; + flex-direction: row; +} +.event-config-manage-button { + position: relative; + margin-left: auto; + top: 50%; + padding-left: 20px; + padding-right: 10px; +} +.event-config-status { + background-color: #FFFFFF; + border: 1px solid #9f9f9f; + color: #9f9f9f; + padding: 4px 10px; + text-align: center; + display: inline-block; + border-radius: 16px; + margin-left: 10px; + font-size: small; + align-self: center; +} +.on-status { + background-color: #00A32A; + color: #FFFFFF; + border:none; +} +.manage-event-card-item { + padding: 20px; + justify-content: space-between; +} +.manage-event-selector { + min-width: 100%; +} +.manage-event-template-block { + border: 1px solid #c4c3c3; + margin-bottom: 20px; +} +.manage-event-template-header { + position: relative; + display: block; + padding: 20px; + font-size: medium; +} +.manage-event-template-footer { + padding: 20px; + display: flex; + flex-direction: row-reverse; + justify-content: flex-start; +} +.manage-event-button { + margin-left: 20px; +} +.fbwa-hidden-element { + display: none; +} +.error-notice-wrapper { + justify-content: left; + padding-left: 20px; + padding-bottom: 10px; + margin-right: 20px; /* Add the right margin */ +} +.notice-error { + background-color: #f7f7f7; + border: 1px solid #EF0000; /* Red border */ + border-radius: 0; /* No curvature */ + border-left-width: 5px; /* Thicker left border */ + width: 100%; /* Take up full width */ +} +.notice-error p { + margin: 5px; +} +.warning-custom-modal { + display: none; /* Hidden by default */ + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.4); /* Black with opacity */ +} +/* Modal content */ +.warning-modal-content { + background-color: #fefefe; + margin: 25% auto; + padding: 20px; + border: 1px solid #ddd; + top: 10%; + width: 50%; + max-width: 500px; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} +/* Modal body */ +.warning-modal-body { + padding: 20px 0; +} +/* Modal footer */ +.warning-modal-footer { + padding: 10px 0; + text-align: right; +} +/* Close button */ +.warning-modal-close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; + position: absolute; + right: 0; + top: 0; +} +.warning-modal-close:hover { + color: #000; +} +.whatsapp-icon { + width: 45px; + height: 40px; + flex-shrink: 0; /* Prevents icon from shrinking */ +} +.contact-info { + padding-left: 10px; + display: flex; + flex-direction: column; +} +.contact-info h3 { + margin: 0; + font-size: 1.1em; +} +.contact-info p { + margin: 0; + font-size: 1.1em; + color: #666; +} +.disconnect-footer-left { + display: flex; + padding: 25px; +} +.disconnect-footer-right-separator { + margin-right:10px; +} +.disconnect-footer-right { + padding: 30px; + margin-left: auto; +} +.disconnect-footer { + display: flex; + position: relative; +} diff --git a/assets/images/whatsapp_icon.png b/assets/images/whatsapp_icon.png new file mode 100644 index 000000000..7637f5b15 Binary files /dev/null and b/assets/images/whatsapp_icon.png differ diff --git a/assets/js/admin/whatsapp-billing.js b/assets/js/admin/whatsapp-billing.js new file mode 100644 index 000000000..3ba8c2bb6 --- /dev/null +++ b/assets/js/admin/whatsapp-billing.js @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @package FacebookCommerce + */ + +jQuery( document ).ready( function( $ ) { + var $billingStepInProgress = $('#wc-fb-whatsapp-billing-inprogress'); + var $billingStepNotStarted = $('#wc-fb-whatsapp-billing-notstarted'); + var $billingStepSuccess = $('#wc-fb-whatsapp-billing-success'); + var $billingSubcontent = $('#wc-fb-whatsapp-billing-subcontent'); + var $billingButtonWrapper = $('#wc-fb-whatsapp-billing-button-wrapper'); + var $whatsappOnboardingDoneButton = $('#whatsapp-onboarding-done-button'); + if (facebook_for_woocommerce_whatsapp_billing.consent_collection_enabled) { + facebook_for_woocommerce_whatsapp_billing.is_payment_setup ? $billingStepSuccess.show() : $billingStepInProgress.show(); + $whatsappOnboardingDoneButton.show(); + $billingStepNotStarted.hide(); + } else { + $billingStepInProgress.hide(); + $billingStepNotStarted.show(); + $billingSubcontent.hide(); + $whatsappOnboardingDoneButton.hide(); + $billingButtonWrapper.hide() + } + + // handle the whatsapp add payment button click should open billing flow in Meta + $('#wc-whatsapp-add-payment').click(function(event) { + + $.post( facebook_for_woocommerce_whatsapp_billing.ajax_url, { + action: 'wc_facebook_whatsapp_fetch_url_info', + nonce: facebook_for_woocommerce_whatsapp_billing.nonce + }, function ( response ) { + if ( response.success ) { + console.log( 'Whatsapp Billing Url Info Fetched Successfully', response ); + var business_id = response.data.business_id; + var asset_id = response.data.waba_id; + const BILLING_URL = `https://business.facebook.com/billing_hub/accounts/details/?business_id=${business_id}&asset_id=${asset_id}&account_type=whatsapp-business-account`; + window.open( BILLING_URL); + } else { + console.log( 'Whatsapp Billing Url Info Fetch Failure', response ); + } + } ); + + + }); + +} ); diff --git a/assets/js/admin/whatsapp-connection.js b/assets/js/admin/whatsapp-connection.js new file mode 100644 index 000000000..e6674fcf6 --- /dev/null +++ b/assets/js/admin/whatsapp-connection.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @package FacebookCommerce + */ + +jQuery( document ).ready( function( $ ) { + var $connectSuccess = $('#wc-fb-whatsapp-connect-success'); + var $connectInProgress = $('#wc-fb-whatsapp-connect-inprogress'); + var $connectSubcontent = $('#wc-fb-whatsapp-onboarding-subcontent'); + var $connectButtonWrapper = $('#wc-fb-whatsapp-onboarding-button-wrapper'); + if (facebook_for_woocommerce_whatsapp_onboarding_progress.whatsapp_onboarding_complete) { + $connectSuccess.show(); + $connectInProgress.hide(); + $connectSubcontent.hide(); + $connectButtonWrapper.hide(); + } else { + $connectSuccess.hide(); + $connectInProgress.show(); + } + + // handle the whatsapp connect button click should open hosted ES flow + $( '#woocommerce-whatsapp-connection' ).click( function( event ) { + const APP_ID = '474166926521348'; // WOO_COMMERCE_APP_ID + const CONFIG_ID = '1237758981048330'; // WOO_COMMERCE_WHATSAPP_CONFIG_ID + const HOSTED_ES_URL = `https://business.facebook.com/messaging/whatsapp/onboard/?app_id=${APP_ID}&config_id=${CONFIG_ID}`; + window.open( HOSTED_ES_URL); + updateProgress(0,1800000); // retry for 30 minutes + }); + + function updateProgress(retryCount = 0, pollingTimeout = 1800000) { + $.post( facebook_for_woocommerce_whatsapp_onboarding_progress.ajax_url, { + action: 'wc_facebook_whatsapp_onboarding_progress_check', + nonce: facebook_for_woocommerce_whatsapp_onboarding_progress.nonce + }, function ( response ) { + + // check if the response is success (i.e. onboarding is completed) + if ( response.success ) { + console.log( 'Whatsapp Connection is Complete', response ); + // update the progress for connect whatsapp step + $connectInProgress.remove(); + $connectSuccess.show(); + // collapse whatsapp onboarding step subcontect and button on success + $connectSubcontent.hide(); + $connectButtonWrapper.hide(); + // update the progress for collect consent step and show button and subcontent + $('#wc-fb-whatsapp-consent-collection-inprogress').show(); + $('#wc-fb-whatsapp-consent-collection-notstarted').hide(); + $('#wc-fb-whatsapp-consent-subcontent').show(); + $('#wc-fb-whatsapp-consent-button-wrapper').show(); + + // update the progress of payment step if payment already setup + if(response.data['is_payment_setup'] === true) { + $('#wc-fb-whatsapp-billing-inprogress').hide(); + $('#wc-fb-whatsapp-billing-notstarted').hide(); + $('#wc-fb-whatsapp-billing-success').show(); + } + } else { + console.log('Whatsapp connection is not complete. Checking again in 5 seconds:', response, ', retry attempt:', retryCount, 'pollingTimeout', pollingTimeout); + if(retryCount >= pollingTimeout) { + console.log('Max retries reached. Aborting.'); + return; + } + setTimeout( function() { updateProgress(retryCount + 1, pollingTimeout); }, 5000 ); + } + } ); + + } + +} ); diff --git a/assets/js/admin/whatsapp-consent-remove.js b/assets/js/admin/whatsapp-consent-remove.js new file mode 100644 index 000000000..de76d9eaf --- /dev/null +++ b/assets/js/admin/whatsapp-consent-remove.js @@ -0,0 +1,94 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @package FacebookCommerce + */ + +jQuery( document ).ready( function( $ ) { + // Get the modal and related elements + var modal = document.getElementById("wc-fb-warning-modal"); + var cancelButton = document.getElementById("wc-fb-warning-modal-cancel"); + var confirmButton = document.getElementById("wc-fb-warning-modal-confirm"); + var $statusElement = $('#wc-whatsapp-collect-consent-status'); + + // On click of the remove button, show the warning modal + $("#wc-whatsapp-collect-consent-remove").click(function(event) { + // Show the modal + modal.style.display = "block"; + + // Prevent default action + event.preventDefault(); + }); + + if (cancelButton) { + // Close modal when clicking the Cancel button + cancelButton.onclick = function() { + modal.style.display = "none"; + }; + } + + if (confirmButton) { + // Handle confirm action + confirmButton.onclick = function() { + // Send the AJAX request to disable WhatsApp consent collection + $.post(facebook_for_woocommerce_whatsapp_consent_remove.ajax_url, { + action: 'wc_facebook_whatsapp_consent_collection_disable', + nonce: facebook_for_woocommerce_whatsapp_consent_remove.nonce + }, function(response) { + if (response.success) { + console.log( 'Whatsapp Consent Collection Disabled Successfully', response ); + // Change the status from "on-status" to "off-status" for the specific element. + $statusElement.removeClass('on-status').addClass('off-status'); + // Update the text to "Off". + $statusElement.text('Off'); + + // Hide the original "Remove" button + $('#wc-whatsapp-collect-consent-remove-container').addClass('fbwa-hidden-element'); + + // Show the "Add" button + $('#wc-whatsapp-collect-consent-add-container').removeClass('fbwa-hidden-element'); + } else { + console.log( 'Whatsapp Consent Collection Disabling Failed', response ); + } + }); + + // Close the modal + modal.style.display = "none"; + }; + } + + // Add event listener to the "Add" button + $('#wc-whatsapp-collect-consent-add').click(function() { + // Send the AJAX request to enable WhatsApp consent collection + $.post(facebook_for_woocommerce_whatsapp_consent.ajax_url, { + action: 'wc_facebook_whatsapp_consent_collection_enable', + nonce: facebook_for_woocommerce_whatsapp_consent.nonce + }, function(response) { + if (response.success) { + console.log( 'Whatsapp Consent Collection Enabled Successfully', response ); + // Change the status from "off-status" to "on-status" for the specific element. + $statusElement.removeClass('off-status').addClass('on-status'); + // Update the text to "On". + $statusElement.text('On'); + + // Hide the "Add" button + $('#wc-whatsapp-collect-consent-add-container').addClass('fbwa-hidden-element'); + + // Show the original "Remove" button + $('#wc-whatsapp-collect-consent-remove-container').removeClass('fbwa-hidden-element'); + } else { + console.log( 'Whatsapp Consent Collection Enabling Failed', response ); + } + }); + }); + + // Close modal when clicking outside of it + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none"; + } + }; +}); diff --git a/assets/js/admin/whatsapp-consent.js b/assets/js/admin/whatsapp-consent.js new file mode 100644 index 000000000..ee64c60c2 --- /dev/null +++ b/assets/js/admin/whatsapp-consent.js @@ -0,0 +1,83 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @package FacebookCommerce + */ + +jQuery( document ).ready( function( $ ) { + var $consentCollectSuccess = $('#wc-fb-whatsapp-consent-collection-success'); + var $consentCollectInProgress = $('#wc-fb-whatsapp-consent-collection-inprogress'); + var $consentCollectNotStarted = $('#wc-fb-whatsapp-consent-collection-notstarted'); + var $consentSubcontent = $('#wc-fb-whatsapp-consent-subcontent'); + var $consentButtonWrapper = $('#wc-fb-whatsapp-consent-button-wrapper'); + if (facebook_for_woocommerce_whatsapp_consent.whatsapp_onboarding_complete) { + if (facebook_for_woocommerce_whatsapp_consent.consent_collection_enabled) { + showConsentCollectionProgressIcon(true, false, false); + $consentSubcontent.hide(); + $consentButtonWrapper.hide(); + } else { + showConsentCollectionProgressIcon(false, true, false); + } + } else { + showConsentCollectionProgressIcon(false, false, true); + $consentSubcontent.hide(); + $consentButtonWrapper.hide(); + } + + // handle the whatsapp consent collect button click should save setting to wp_options table + $( '#wc-whatsapp-collect-consent' ).click( function( event ) { + + $.post( facebook_for_woocommerce_whatsapp_consent.ajax_url, { + action: 'wc_facebook_whatsapp_consent_collection_enable', + nonce: facebook_for_woocommerce_whatsapp_consent.nonce + }, function ( response ) { + if ( response.success ) { + console.log( 'Whatsapp Consent Collection is Enabled in Checkout Flow', response ); + // update the progress for collect consent step and hide the button and subcontent + showConsentCollectionProgressIcon(true, false, false); + $consentSubcontent.hide(); + $consentButtonWrapper.hide(); + // update the progress of billing step and show the button and subcontent + if(response.data['is_payment_setup'] === true) { + $('#wc-fb-whatsapp-billing-inprogress').hide(); + $('#wc-fb-whatsapp-billing-notstarted').hide(); + $('#wc-fb-whatsapp-billing-success').show(); + } else { + $('#wc-fb-whatsapp-billing-inprogress').show(); + $('#wc-fb-whatsapp-billing-notstarted').hide(); + + } + $('#wc-fb-whatsapp-billing-subcontent').show(); + $('#wc-fb-whatsapp-billing-button-wrapper').show(); + $('#whatsapp-onboarding-done-button').show(); + } else { + console.log( 'Whatsapp Consent Collection Enabling has Failed', response ); + } + } ); + + }); + + function showConsentCollectionProgressIcon(success, inProgress, notStarted) { + if (success) { + $consentCollectSuccess.show(); + } else { + $consentCollectSuccess.hide(); + } + + if (inProgress) { + $consentCollectInProgress.show(); + } else { + $consentCollectInProgress.hide(); + } + + if (notStarted) { + $consentCollectNotStarted.show(); + } else { + $consentCollectNotStarted.hide(); + } + } + +} ); diff --git a/assets/js/admin/whatsapp-disconnect.js b/assets/js/admin/whatsapp-disconnect.js new file mode 100644 index 000000000..91cbde3de --- /dev/null +++ b/assets/js/admin/whatsapp-disconnect.js @@ -0,0 +1,75 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @package FacebookCommerce + */ + +jQuery( document ).ready( function( $ ) { + // Get the modal and related elements + var modal = document.getElementById("wc-fb-disconnect-warning-modal"); + var cancelButton = document.getElementById("wc-fb-disconnect-warning-modal-cancel"); + var confirmButton = document.getElementById("wc-fb-disconnect-warning-modal-confirm"); + + // On click of the remove button, show the warning modal + $("#wc-whatsapp-disconnect-button").click(function(event) { + // Show the modal + modal.style.display = "block"; + + // Prevent default action + event.preventDefault(); + }); + + if (cancelButton) { + // Close modal when clicking the Cancel button + cancelButton.onclick = function() { + modal.style.display = "none"; + }; + } + + if (confirmButton) { + // Handle confirm action + confirmButton.onclick = function() { + $.post( facebook_for_woocommerce_whatsapp_disconnect.ajax_url, { + action: 'wc_facebook_disconnect_whatsapp', + nonce: facebook_for_woocommerce_whatsapp_disconnect.nonce + }, function ( response ) { + if ( response.success ) { + let url = new URL(window.location.href); + let params = new URLSearchParams(url.search); + params.delete('view'); + url.search = params.toString(); + window.location.href = url.toString(); + console.log( 'Whatsapp Disconnect Success', response ); + } else { + console.log("Whatsapp Disconnect Failure!!!",response); + } + } ); + + // Close the modal + modal.style.display = "none"; + }; + } + + // handle whatsapp disconnect widget edit link click should open business manager with whatsapp asset selected + $( '#wc-whatsapp-disconnect-edit' ).click( function( event ) { + $.post( facebook_for_woocommerce_whatsapp_disconnect.ajax_url, { + action: 'wc_facebook_whatsapp_fetch_url_info', + nonce: facebook_for_woocommerce_whatsapp_disconnect.nonce + }, function ( response ) { + + if ( response.success ) { + console.log( 'Whatsapp Edit Url Info Fetched Successfully', response ); + var business_id = response.data.business_id; + var asset_id = response.data.waba_id; + const WHATSAPP_MANAGER_URL = `https://business.facebook.com/latest/whatsapp_manager/phone_numbers/?asset_id=${asset_id}&business_id=${business_id}`; + window.open(WHATSAPP_MANAGER_URL); + } else { + console.log( 'Whatsapp Edit Url Info Fetch Failure', response ); + } + } ); + }); + +} ); diff --git a/assets/js/admin/whatsapp-events.js b/assets/js/admin/whatsapp-events.js new file mode 100644 index 000000000..bfaed97df --- /dev/null +++ b/assets/js/admin/whatsapp-events.js @@ -0,0 +1,129 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @package FacebookCommerce + */ + +jQuery( document ).ready( function( $ ) { + // Set Event Status for Order Placed + var orderPlacedActiveStatus = $('#order-placed-active-status'); + var orderPlacedInactiveStatus = $('#order-placed-inactive-status'); + if(facebook_for_woocommerce_whatsapp_events.order_placed_enabled){ + orderPlacedInactiveStatus.hide(); + orderPlacedActiveStatus.show(); + } + else { + orderPlacedActiveStatus.hide(); + orderPlacedInactiveStatus.show(); + } + + // Set Event Status for Order FulFilled + var orderFulfilledActiveStatus = $('#order-fulfilled-active-status'); + var orderFulfilledInactiveStatus = $('#order-fulfilled-inactive-status'); + if(facebook_for_woocommerce_whatsapp_events.order_fulfilled_enabled){ + orderFulfilledInactiveStatus.hide(); + orderFulfilledActiveStatus.show(); + } + else { + orderFulfilledActiveStatus.hide(); + orderFulfilledInactiveStatus.show(); + } + + // Set Event Status for Order Refunded + var orderRefundedActiveStatus = $('#order-refunded-active-status'); + var orderRefundedInactiveStatus = $('#order-refunded-inactive-status'); + if(facebook_for_woocommerce_whatsapp_events.order_refunded_enabled){ + orderRefundedInactiveStatus.hide(); + orderRefundedActiveStatus.show(); + } + else { + orderRefundedActiveStatus.hide(); + orderRefundedInactiveStatus.show(); + } + + var eventConfiglanguage = getEventLanguage(facebook_for_woocommerce_whatsapp_events.event); + $("#manage-event-language").val(eventConfiglanguage); + + $('#woocommerce-whatsapp-manage-order-placed, #woocommerce-whatsapp-manage-order-fulfilled, #woocommerce-whatsapp-manage-order-refunded').click(function (event) { + var clickedButtonId = $(event.target).attr("id"); + let view=clickedButtonId.replace("woocommerce-whatsapp-", ""); + view = view.replaceAll("-", "_"); + let url = new URL(window.location.href); + let params = new URLSearchParams(url.search); + params.set('view', view); + url.search = params.toString(); + window.location.href = url.toString(); + }); + + // call template library get API to show message template header, body and button text configured for the event. + $("#library-template-content").load(facebook_for_woocommerce_whatsapp_events.ajax_url, function () { + $.post(facebook_for_woocommerce_whatsapp_events.ajax_url, { + action: 'wc_facebook_whatsapp_fetch_library_template_info', + nonce: facebook_for_woocommerce_whatsapp_events.nonce, + event: facebook_for_woocommerce_whatsapp_events.event, + }, function (response) { + if (response.success) { + const parsedData = JSON.parse(response.data); + const apiResponseData = parsedData.data[0]; + // Parse template strings as HTML and extract text content to sanitize text + const header = $.parseHTML(apiResponseData.header)[0].textContent; + const body = $.parseHTML(apiResponseData.body)[0].textContent; + if (facebook_for_woocommerce_whatsapp_events.event === "ORDER_REFUNDED") { + $('#library-template-content').html(` +

Header

+

${header}

+

Body

+

${body}

+ `).show(); + } + else { + const button = $.parseHTML(apiResponseData.buttons[0].text)[0].textContent; + $('#library-template-content').html(` +

Header

+

${header}

+

Body

+

${body}

+

Call to action

+

${button}

+ `).show(); + } + } + }); + }); + + $('#woocommerce-whatsapp-save-order-confirmation').click(function (event) { + var languageValue = $("#manage-event-language").val(); + var statusValue = $('input[name="template-status"]:checked').val(); + console.log('Save confirmation clicked: ', languageValue, statusValue); + $.post(facebook_for_woocommerce_whatsapp_events.ajax_url, { + action: 'wc_facebook_whatsapp_upsert_event_config', + nonce: facebook_for_woocommerce_whatsapp_events.nonce, + event: facebook_for_woocommerce_whatsapp_events.event, + language: languageValue, + status: statusValue + }, function (response) { + //TODO: Add Error Handling + let url = new URL(window.location.href); + let params = new URLSearchParams(url.search); + params.set('view', 'utility_settings'); + url.search = params.toString(); + window.location.href = url.toString(); + }); + }); + + function getEventLanguage(event) { + switch (event) { + case "ORDER_PLACED": + return facebook_for_woocommerce_whatsapp_events.order_placed_language; + case "ORDER_FULFILLED": + return facebook_for_woocommerce_whatsapp_events.order_fulfilled_language; + case "ORDER_REFUNDED": + return facebook_for_woocommerce_whatsapp_events.order_refunded_language; + default: + return null; + } + } +}); \ No newline at end of file diff --git a/assets/js/admin/whatsapp-finish.js b/assets/js/admin/whatsapp-finish.js new file mode 100644 index 000000000..98d22e8ea --- /dev/null +++ b/assets/js/admin/whatsapp-finish.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @package FacebookCommerce + */ + +jQuery( document ).ready( function( $ ) { + // handle the whatsapp finish button click + $( '#wc-whatsapp-onboarding-finish' ).click( function( event ) { + // call the connect API to create configs and check payment + $.post( facebook_for_woocommerce_whatsapp_finish.ajax_url, { + action: 'wc_facebook_whatsapp_finish_onboarding', + nonce: facebook_for_woocommerce_whatsapp_finish.nonce + }, function ( response ) { + if ( response.success ) { + // If success, redirect to utility settings page + let url = new URL(window.location.href); + let params = new URLSearchParams(url.search); + params.set('view', 'utility_settings'); + url.search = params.toString(); + window.location.href = url.toString(); + console.log( 'Whatsapp Connect Success', response ); + } else { + var message; + const error = response.data; + console.log( 'Whatsapp Connect Failure', response ); + + switch (error) { + case "Incorrect payment setup": + message = facebook_for_woocommerce_whatsapp_finish.i18n.payment_setup_error; + break; + case "Onboarding is not complete or has failed.": + message = facebook_for_woocommerce_whatsapp_finish.i18n.onboarding_incomplete_error; + break; + default: + message = facebook_for_woocommerce_whatsapp_finish.i18n.generic_error; + } + + + const errorNoticeHtml = ` +
+

${message}

+
+ `; + $( '#payment-method-error-notice' ).html( errorNoticeHtml ).show(); + } + } ); + }); + +} ); diff --git a/assets/js/admin/whatsapp-templates.js b/assets/js/admin/whatsapp-templates.js new file mode 100644 index 000000000..be01ba924 --- /dev/null +++ b/assets/js/admin/whatsapp-templates.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @package FacebookCommerce + */ + +jQuery( document ).ready( function( $ ) { + // handle whatsapp view insights link click should open template insights in WhatsSpp Manager + $( '#woocommerce-whatsapp-manager-insights' ).click( function( event ) { + $.post( facebook_for_woocommerce_whatsapp_templates.ajax_url, { + action: 'wc_facebook_whatsapp_fetch_url_info', + nonce: facebook_for_woocommerce_whatsapp_templates.nonce + }, function ( response ) { + console.log(response); + if ( response.success ) { + var business_id = response.data.business_id; + var asset_id = response.data.waba_id; + const MANAGE_TEMPLATES_URL = `https://business.facebook.com/latest/whatsapp_manager/message_templates?business_id=${business_id}&asset_id=${asset_id}`; + window.open(MANAGE_TEMPLATES_URL); + } + } ); + }); +} ); diff --git a/class-wc-facebookcommerce.php b/class-wc-facebookcommerce.php index d1291a640..e45a2ce82 100644 --- a/class-wc-facebookcommerce.php +++ b/class-wc-facebookcommerce.php @@ -99,6 +99,9 @@ class WC_Facebookcommerce extends WooCommerce\Facebook\Framework\Plugin { /** @var WooCommerce\Facebook\Handlers\WebHook webhook handler */ private $webhook_handler; + /** @var WooCommerce\Facebook\Handlers\Whatsapp_WebHook whatsapp webhook handler */ + private $whatsapp_webhook_handler; + /** @var WooCommerce\Facebook\Commerce commerce handler */ private $commerce_handler; @@ -172,6 +175,7 @@ public function init() { add_action( 'init', array( $this, 'get_integration' ) ); add_action( 'init', array( $this, 'register_custom_taxonomy' ) ); add_action( 'add_meta_boxes_product', array( $this, 'remove_product_fb_product_set_metabox' ), 50 ); + add_action( 'woocommerce_init', array($this, 'add_whatsapp_consent_checkout_fields')); add_filter( 'fb_product_set_row_actions', array( $this, 'product_set_links' ) ); add_filter( 'manage_edit-fb_product_set_columns', array( $this, 'manage_fb_product_set_columns' ) ); @@ -195,7 +199,6 @@ public function init() { $this->heartbeat = new Heartbeat( WC()->queue() ); $this->heartbeat->init(); - $this->feed_manager = new WooCommerce\Facebook\Feed\FeedManager(); $this->checkout = new WooCommerce\Facebook\Checkout(); $this->product_feed = new WooCommerce\Facebook\Products\Feed(); @@ -225,16 +228,19 @@ public function init() { $this->background_remove_duplicate_visibility_meta = new Background_Remove_Duplicate_Visibility_Meta(); } + new WooCommerce\Facebook\API\Plugin\InitializeRestAPI(); $this->connection_handler = new WooCommerce\Facebook\Handlers\Connection( $this ); new WooCommerce\Facebook\Handlers\MetaExtension(); - $this->webhook_handler = new WooCommerce\Facebook\Handlers\WebHook( $this ); - $this->tracker = new WooCommerce\Facebook\Utilities\Tracker(); - $this->rollout_switches = new WooCommerce\Facebook\RolloutSwitches( $this ); + $this->webhook_handler = new WooCommerce\Facebook\Handlers\WebHook( $this ); + $this->whatsapp_webhook_handler = new WooCommerce\Facebook\Handlers\Whatsapp_Webhook( $this ); + $this->tracker = new WooCommerce\Facebook\Utilities\Tracker(); + $this->rollout_switches = new WooCommerce\Facebook\RolloutSwitches( $this ); // Init jobs $this->job_manager = new WooCommerce\Facebook\Jobs\JobManager(); add_action( 'init', [ $this->job_manager, 'init' ] ); + add_action( 'admin_init', [ $this->rollout_switches, 'init' ] ); // Instantiate the debug tools. $this->debug_tools = new DebugTools(); @@ -242,15 +248,16 @@ public function init() { // load admin handlers, before admin_init if ( is_admin() ) { if ($this->use_enhanced_onboarding()) { - $this->admin_enhanced_settings = new WooCommerce\Facebook\Admin\Enhanced_Settings( $this->connection_handler->is_connected() ); + $this->admin_enhanced_settings = new WooCommerce\Facebook\Admin\Enhanced_Settings( $this ); } else { - $this->admin_settings = new WooCommerce\Facebook\Admin\Settings( $this->connection_handler->is_connected() ); + $this->admin_settings = new WooCommerce\Facebook\Admin\Settings( $this ); } } } } + /** * Initializes the admin handling. * @@ -266,7 +273,6 @@ function () { }, 0 ); - add_action( 'admin_init', [ $this->rollout_switches, 'init' ] ); } /** @@ -876,6 +882,30 @@ protected function get_current_page_id() { return $current_screen_id; } + /** + * Add checkout fields to collect whatsapp consent if consent collection is enabled + * + * @since 2.3.0 + * + * @param array $fields + * + * @return array + */ + function add_whatsapp_consent_checkout_fields($fields) { + if (get_option('wc_facebook_whatsapp_consent_collection_setting_status', 'disabled') === 'enabled') { + woocommerce_register_additional_checkout_field( + array( + 'id' => 'wc_facebook/whatsapp_consent_checkbox', // id = namespace/field_name + 'label' => esc_html('Get order updates on WhatsApp'), + 'location' => 'address', + 'type' => 'checkbox', + 'optionalLabel' => esc_html('Get order updates on WhatsApp') + ) + ); + } + return $fields; + } + /** * Determines if the enhanced onboarding (iframe) should be used. * diff --git a/facebook-commerce-whatsapp-utility-event.php b/facebook-commerce-whatsapp-utility-event.php new file mode 100644 index 000000000..9fef14afa --- /dev/null +++ b/facebook-commerce-whatsapp-utility-event.php @@ -0,0 +1,131 @@ + 'ORDER_PLACED', + 'completed' => 'ORDER_FULFILLED', + 'refunded' => 'ORDER_REFUNDED', + ); + + public function __construct() { + if ( ! $this->is_whatsapp_utility_enabled() ) { + return; + } + add_action( 'woocommerce_order_status_changed', array( $this, 'process_wc_order_status_changed' ), 10, 3 ); + } + + /** + * Determines if WhatsApp Utility Messages are enabled + * TODO: Update this function to check for gating logic for Alpha businesses + * + * @since 2.3.0 + * + * @return bool + */ + private function is_whatsapp_utility_enabled() { + return true; + } + + + /** + * Hook to process Order Processing, Order Completed and Order Refunded events for WhatsApp Utility Messages + * + * @param string $order_id Order id + * @param string $old_status Old Order Status + * @param string $new_status New Order Status + * + * @return void + * @since 2.3.0 + */ + public function process_wc_order_status_changed( $order_id, $old_status, $new_status ) { + // WhatsApp Utility Messages are supported only for Processing status + $supported_statuses = array_keys( self::ORDER_STATUS_TO_EVENT_MAPPING ); + if ( ! in_array( $new_status, $supported_statuses, true ) ) { + return; + } + + wc_get_logger()->info( + sprintf( + /* translators: %s $order_id */ + __( 'Processing Order id %1$s to send Whatsapp Utility messages', 'facebook-for-woocommerce' ), + $order_id, + ) + ); + $event = self::ORDER_STATUS_TO_EVENT_MAPPING[ $new_status ]; + + // Check WhatsApp Event Config is active + $event_config_id_option_name = implode( '_', array( WhatsAppUtilityConnection::WA_UTILITY_OPTION_PREFIX, strtolower( $event ), 'event_config_id' ) ); + $event_config_language_option_name = implode( '_', array( WhatsAppUtilityConnection::WA_UTILITY_OPTION_PREFIX, strtolower( $event ), 'language' ) ); + $event_config_id = get_option( $event_config_id_option_name, null ); + $language_code = get_option( $event_config_language_option_name, null ); + if ( empty( $event_config_id ) || empty( $language_code ) ) { + wc_get_logger()->info( + sprintf( + /* translators: %s $order_id */ + __( 'Messages Post API call for Order id %1$s skipped due to no active event config', 'facebook-for-woocommerce' ), + $order_id, + ) + ); + return; + } + + $order = wc_get_order( $order_id ); + // Check WhatsApp Consent Checkbox is selected in shipping and billing + $billing_consent_value = $order->get_meta( '_wc_billing/wc_facebook/whatsapp_consent_checkbox' ); + $shipping_consent_value = $order->get_meta( '_wc_shipping/wc_facebook/whatsapp_consent_checkbox' ); + $has_whatsapp_consent = $billing_consent_value && $shipping_consent_value; + // Get WhatsApp Phone number from entered Billing and Shipping phone number + $billing_phone_number = $order->get_billing_phone(); + $shipping_phone_number = $order->get_shipping_phone(); + $phone_number = ( isset( $billing_phone_number ) && $billing_consent_value ) ? $billing_phone_number : $shipping_phone_number; + // Get Customer first name + $first_name = $order->get_billing_first_name(); + // Get Total Refund Amount for Order Refunded event + $total_refund = 0; + foreach ( $order->get_refunds() as $refund ) { + $total_refund += $refund->get_amount(); + } + $currency = $order->get_currency(); + $refund_amount = $total_refund * 1000; + if ( empty( $phone_number ) || ! $has_whatsapp_consent || empty( $event ) || empty( $first_name ) ) { + wc_get_logger()->info( + sprintf( + /* translators: %s $order_id */ + __( 'Messages Post API call for Order id %1$s skipped due to missing whatsapp consent or Order info', 'facebook-for-woocommerce' ), + $order_id, + ) + ); + return; + } + + // Check Access token and WACS is available + $bisu_token = get_option( 'wc_facebook_wa_integration_bisu_access_token', null ); + $wacs_id = get_option( 'wc_facebook_wa_integration_wacs_id', null ); + if ( empty( $bisu_token ) || empty( $wacs_id ) ) { + wc_get_logger()->info( + sprintf( + /* translators: %s $order_id */ + __( 'Messages Post API call for Order id %1$s Failed due to missing access token or wacs info', 'facebook-for-woocommerce' ), + $order_id, + ) + ); + return; + } + WhatsAppUtilityConnection::post_whatsapp_utility_messages_events_call( $event, $event_config_id, $language_code, $wacs_id, $order_id, $phone_number, $first_name, $refund_amount, $currency, $bisu_token ); + } +} diff --git a/facebook-commerce.php b/facebook-commerce.php index a823af178..ae1f30497 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -204,6 +204,9 @@ class WC_Facebookcommerce_Integration extends WC_Integration { /** @var WC_Facebook_Product_Feed instance. */ private $fbproductfeed; + /** @var WC_Facebookcommerce_Whatsapp_Utility_Event instance. */ + private $wa_utility_event_processor; + /** * Init and hook in the integration. * @@ -397,6 +400,9 @@ public function __construct( WC_Facebookcommerce $facebook_for_woocommerce ) { // Product Set hooks. add_action( 'fb_wc_product_set_sync', [ $this, 'create_or_update_product_set_item' ], 99, 2 ); add_action( 'fb_wc_product_set_delete', [ $this, 'delete_product_set_item' ], 99 ); + + // Init Whatsapp Utility Event Processor + $this->wa_utility_event_processor = $this->load_whatsapp_utility_event_processor(); } /** @@ -3146,4 +3152,19 @@ public function ajax_display_test_result() { wp_die(); } + /** + * Init WhatsApp Utility Event Processor. + * + * @return void + */ + public function load_whatsapp_utility_event_processor() { + // Attempt to load WhatsApp Utility Event Processor + include_once 'facebook-commerce-whatsapp-utility-event.php'; + if ( class_exists( 'WC_Facebookcommerce_Whatsapp_Utility_Event' ) ) { + if ( ! isset( $this->wa_utility_event_processor ) ) { + $this->wa_utility_event_processor = new WC_Facebookcommerce_Whatsapp_Utility_Event( $this ); + } + } + } + } diff --git a/includes/AJAX.php b/includes/AJAX.php index 405987f5f..09060b4c7 100644 --- a/includes/AJAX.php +++ b/includes/AJAX.php @@ -14,6 +14,7 @@ use WooCommerce\Facebook\Admin\Settings_Screens\Product_Sync; use WooCommerce\Facebook\Admin\Settings_Screens\Shops; use WooCommerce\Facebook\Framework\Plugin\Exception as PluginException; +use WooCommerce\Facebook\Handlers\WhatsAppUtilityConnection; defined( 'ABSPATH' ) || exit; @@ -51,8 +52,32 @@ public function __construct() { // get the current sync status add_action( 'wp_ajax_wc_facebook_get_sync_status', array( $this, 'get_sync_status' ) ); + // check the status of whatsapp onboarding and update the progress + add_action( 'wp_ajax_wc_facebook_whatsapp_onboarding_progress_check', array( $this, 'whatsapp_onboarding_progress_check' ) ); + + // update the wp_options with wc_facebook_whatsapp_consent_collection_setting_status to enabled + add_action( 'wp_ajax_wc_facebook_whatsapp_consent_collection_enable', array( $this, 'whatsapp_consent_collection_enable' ) ); + + // fetch url info - waba id and business id + add_action( 'wp_ajax_wc_facebook_whatsapp_fetch_url_info', array( $this, 'wc_facebook_whatsapp_fetch_url_info' ) ); + + // action to fetch required info and make api call to meta to finish onboarding + add_action( 'wp_ajax_wc_facebook_whatsapp_finish_onboarding', array( $this, 'wc_facebook_whatsapp_finish_onboarding' ) ); + + // fetch configured library template info + add_action( 'wp_ajax_wc_facebook_whatsapp_fetch_library_template_info', array( $this, 'whatsapp_fetch_library_template_info' ) ); + + // action to create or update utility event config info + add_action( 'wp_ajax_wc_facebook_whatsapp_upsert_event_config', array( $this, 'whatsapp_upsert_event_config' ) ); + // search a product's attributes for the given term add_action( 'wp_ajax_' . self::ACTION_SEARCH_PRODUCT_ATTRIBUTES, array( $this, 'admin_search_product_attributes' ) ); + + // update the wp_options with wc_facebook_whatsapp_consent_collection_setting_status to disabled + add_action( 'wp_ajax_wc_facebook_whatsapp_consent_collection_disable', array( $this, 'whatsapp_consent_collection_disable' ) ); + + // disconnect whatsapp account from woocommcerce app + add_action( 'wp_ajax_wc_facebook_disconnect_whatsapp', array( $this, 'wc_facebook_disconnect_whatsapp' ) ); } @@ -189,6 +214,241 @@ public function get_sync_status() { wp_send_json_success( $remaining_products ); } + /** + * Get data for creating the billing or whatsapp manager url for whatsapp account. + * + * @internal + * + * @since 1.10.0 + */ + public function wc_facebook_whatsapp_fetch_url_info() { + wc_get_logger()->info( + sprintf( + __( 'Fetching url info(WABA ID+BusinessID) for whatsapp pages', 'facebook-for-woocommerce' ) + ) + ); + facebook_for_woocommerce()->log( '' ); + if ( ! check_ajax_referer( 'facebook-for-wc-whatsapp-billing-nonce', 'nonce', false ) && ! check_ajax_referer( 'facebook-for-wc-whatsapp-templates-nonce', 'nonce', false ) && ! check_ajax_referer( 'facebook-for-wc-whatsapp-disconnect-nonce', 'nonce', false ) ) { + wc_get_logger()->info( + sprintf( + __( 'Nonce Verification Error while Fetching Url Info', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_error( 'Invalid security token sent.' ); + } + + $waba_id = get_option( 'wc_facebook_wa_integration_waba_id', null ); + $business_id = get_option( 'wc_facebook_wa_integration_business_id', null ); + + if ( empty( $waba_id ) || empty( $business_id ) ) { + wc_get_logger()->info( + sprintf( + __( 'Missing Waba ID + Business ID during Fetch Url Info. Whatsapp Onboarding is not complete or has failed.', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_error( 'Whatsapp onboarding is not complete or has failed.' ); + } + + $response = array( + 'waba_id' => $waba_id, + 'business_id' => $business_id, + ); + + wp_send_json_success( $response ); + } + + /** + * Get data for for finish onboarding call and make api call. + * + * @internal + * + * @since 1.10.0 + */ + public function wc_facebook_whatsapp_finish_onboarding() { + wc_get_logger()->info( + sprintf( + __( 'Getting data for Whatsapp Finish Onboarding Done Button Click', 'facebook-for-woocommerce' ) + ) + ); + if ( ! check_ajax_referer( 'facebook-for-wc-whatsapp-finish-nonce', 'nonce', false ) ) { + wc_get_logger()->info( + sprintf( + __( 'Nonce Verification Error in Finish Onboarding Flow', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_error( 'Invalid security token sent.' ); + } + $external_business_id = get_option( 'wc_facebook_external_business_id', null ); + $wacs_id = get_option( 'wc_facebook_wa_integration_wacs_id', null ); + $waba_id = get_option( 'wc_facebook_wa_integration_waba_id', null ); + $bisu_token = get_option( 'wc_facebook_wa_integration_bisu_access_token', null ); + if ( empty( $external_business_id ) || empty( $wacs_id ) || empty( $waba_id ) || empty( $bisu_token ) ) { + wc_get_logger()->info( + sprintf( + __( 'Finish Onboarding - Onboarding is not complete or has failed.', 'facebook-for-woocommerce' ), + ) + ); + wp_send_json_error( 'Onboarding Flow is not complete or has failed.' ); + } + WhatsAppUtilityConnection::wc_facebook_whatsapp_connect_utility_messages_call( $waba_id, $wacs_id, $external_business_id, $bisu_token ); + } + + + /** + * Checks if the onboarding for whatsapp is complete once business has initiated onboarding. + * + * @internal + * + * @since 1.10.0 + */ + public function whatsapp_onboarding_progress_check() { + if ( ! check_ajax_referer( 'facebook-for-wc-whatsapp-onboarding-progress-nonce', 'nonce', false ) ) { + wp_send_json_error( 'Invalid security token sent.' ); + } + $waba_id = get_option( 'wc_facebook_wa_integration_waba_id', null ); + $is_payment_setup = (bool) get_option( 'wc_facebook_wa_integration_is_payment_setup', null ); + if ( ! empty( $waba_id ) ) { + wp_send_json_success( + array( + 'message' => 'WhatsApp onboarding is complete', + 'is_payment_setup' => $is_payment_setup, + ) + ); + } + wp_send_json_error( 'WhatsApp onboarding is not complete' ); + } + + public function whatsapp_consent_collection_enable() { + wc_get_logger()->info( + sprintf( + __( 'Enabling Whatsapp Consent Collection in Checkout Flow', 'facebook-for-woocommerce' ) + ) + ); + if ( ! check_ajax_referer( 'facebook-for-wc-whatsapp-consent-nonce', 'nonce', false ) ) { + wc_get_logger()->info( + sprintf( + __( 'Nonce Verification Error in Whatsapp Consent Collection', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_error( 'Invalid security token sent.' ); + } + if ( get_option( 'wc_facebook_whatsapp_consent_collection_setting_status' ) !== 'enabled' ) { + update_option( 'wc_facebook_whatsapp_consent_collection_setting_status', 'enabled' ); + } + $is_payment_setup = (bool) get_option( 'wc_facebook_wa_integration_is_payment_setup', null ); + wc_get_logger()->info( + sprintf( + __( 'Whatsapp Consent Collection Enabled Successfully in Checkout Flow', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_success( + array( + 'message' => 'Whatsapp Consent Collection Enabled Successfully in Checkout Flow', + 'is_payment_setup' => $is_payment_setup, + ) + ); + } + + public function whatsapp_consent_collection_disable() { + wc_get_logger()->info( + sprintf( + __( 'Disabling Whatsapp Consent Collection in Utility Settings View', 'facebook-for-woocommerce' ) + ) + ); + if ( ! check_ajax_referer( 'facebook-for-wc-whatsapp-consent-disable-nonce', 'nonce', false ) ) { + wp_send_json_error( 'Invalid security token sent.' ); + } + if ( get_option( 'wc_facebook_whatsapp_consent_collection_setting_status' ) !== 'disabled' ) { + update_option( 'wc_facebook_whatsapp_consent_collection_setting_status', 'disabled' ); + } + wc_get_logger()->info( + sprintf( + __( 'Whatsapp Consent Collection Disabled Successfully in Utility Settings View', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_success(); + } + + /** + * Disconnect Whatsapp from WooCommerce. + * + * @internal + * + * @since 1.10.0 + */ + public function wc_facebook_disconnect_whatsapp() { + wc_get_logger()->info( + sprintf( + __( 'Diconnecting Whatsapp From Woocommerce', 'facebook-for-woocommerce' ) + ) + ); + if ( ! check_ajax_referer( 'facebook-for-wc-whatsapp-disconnect-nonce', 'nonce', false ) ) { + wc_get_logger()->info( + sprintf( + __( 'Nonce Verification Failed while Diconnecting Whatsapp From Woocommerce', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_error( 'Invalid security token sent.' ); + } + + $integration_config_id = get_option( 'wc_facebook_wa_integration_config_id', null ); + $bisu_token = get_option( 'wc_facebook_wa_integration_bisu_access_token', null ); + $waba_id = get_option( 'wc_facebook_wa_integration_waba_id', null ); + if ( empty( $integration_config_id ) || empty( $bisu_token ) || empty( $waba_id ) ) { + wc_get_logger()->info( + sprintf( + __( 'Missing Integration Config ID, BISU token, WABA ID while Diconnecting Whatsapp From Woocommerce', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_error( 'Missing integration_config_id or bisu_token or waba_id for Disconnect API call' ); + } + WhatsAppUtilityConnection::wc_facebook_disconnect_whatsapp( $waba_id, $integration_config_id, $bisu_token ); + } + + public function whatsapp_fetch_library_template_info() { + facebook_for_woocommerce()->log( 'Fetching library template data for whatsapp utility event' ); + if ( ! check_ajax_referer( 'facebook-for-wc-whatsapp-events-nonce', 'nonce', false ) ) { + wp_send_json_error( 'Invalid security token sent.' ); + } + $bisu_token = get_option( 'wc_facebook_wa_integration_bisu_access_token', null ); + if ( empty( $bisu_token ) ) { + wp_send_json_error( 'Missing access token for Library template API call' ); + } + // Get POST parameters from the request + $event = isset( $_POST['event'] ) ? wc_clean( wp_unslash( $_POST['event'] ) ) : ''; + WhatsAppUtilityConnection::get_template_library_content( $event, $bisu_token ); + } + /** + * Creates or Updates WhatsApp Utility Event Configs + * + * @internal + * + * @since 1.10.0 + */ + public function whatsapp_upsert_event_config() { + facebook_for_woocommerce()->log( 'Calling POST API to upsert whatsapp utility event' ); + if ( ! check_ajax_referer( 'facebook-for-wc-whatsapp-events-nonce', 'nonce', false ) ) { + wp_send_json_error( 'Invalid security token sent.' ); + } + // Get BISU token + $bisu_token = get_option( 'wc_facebook_wa_integration_bisu_access_token', null ); + if ( empty( $bisu_token ) ) { + wp_send_json_error( 'Missing access token for Event Configs POST API call' ); + } + // Get Integration Config id + $integration_config_id = get_option( 'wc_facebook_wa_integration_config_id', null ); + if ( empty( $integration_config_id ) ) { + wp_send_json_error( 'Missing Integration Config for Event Configs POST API call' ); + } + // Get POST parameters from the request + $event = isset( $_POST['event'] ) ? wc_clean( wp_unslash( $_POST['event'] ) ) : ''; + $language = isset( $_POST['language'] ) ? wc_clean( wp_unslash( $_POST['language'] ) ) : ''; + $status = isset( $_POST['status'] ) ? wc_clean( wp_unslash( $_POST['status'] ) ) : ''; + if ( empty( $event ) || empty( $language ) || empty( $status ) ) { + wp_send_json_error( 'Missing request parameters for Event Configs POST API call' ); + } + WhatsAppUtilityConnection::post_whatsapp_utility_messages_event_configs_call( $event, $integration_config_id, $language, $status, $bisu_token ); + } /** * Maybe triggers a modal warning when the merchant toggles sync enabled status in bulk. diff --git a/includes/Admin/Enhanced_Settings.php b/includes/Admin/Enhanced_Settings.php index 1f7009746..d1f0297f4 100644 --- a/includes/Admin/Enhanced_Settings.php +++ b/includes/Admin/Enhanced_Settings.php @@ -15,6 +15,8 @@ use WooCommerce\Facebook\Admin\Settings_Screens\Shops; use WooCommerce\Facebook\Framework\Helper; use WooCommerce\Facebook\Framework\Plugin\Exception as PluginException; +use WooCommerce\Facebook\Admin\Settings_Screens\Whatsapp_Utility; +use WooCommerce\Facebook\RolloutSwitches; defined( 'ABSPATH' ) || exit; @@ -38,16 +40,24 @@ class Enhanced_Settings { */ const SUBMENU_PAGE_ID = 'edit-tags.php?taxonomy=fb_product_set&post_type=product'; + /** @var \WC_Facebookcommerce */ + private $plugin; + + /** * Enhanced settings constructor. * * @since 3.5.0 * - * @param bool $is_connected + * @param \WC_Facebookcommerce $plugin is the plugin instance of WC_Facebookcommerce */ - public function __construct( bool $is_connected ) { - $this->screens = $this->build_menu_item_array( $is_connected ); + public function __construct( \WC_Facebookcommerce $plugin ) { + $this->plugin = $plugin; + $this->screens = $this->build_menu_item_array(); + + add_action( 'admin_menu', array( $this, 'build_menu_item_array' ) ); + add_action( 'admin_init', array( $this, 'add_extra_screens' ) ); add_action( 'admin_menu', array( $this, 'add_menu_item' ) ); add_action( 'wp_loaded', array( $this, 'save' ) ); @@ -61,21 +71,39 @@ public function __construct( bool $is_connected ) { * * @since 3.5.0 * - * @param bool $is_connected is Facebook connected * @return array */ - private function build_menu_item_array( bool $is_connected ): array { + public function build_menu_item_array(): array { + $is_connected = $this->plugin->get_connection_handler()->is_connected(); + if ( $is_connected ) { - // TODO: Add Utility messaging tab // TODO: Remove Product sync and Product sets tab once catalog changes are complete - return array( + $screens = array( Settings_Screens\Shops::ID => new Settings_Screens\Shops(), Settings_Screens\Product_Sync::ID => new Settings_Screens\Product_Sync(), Settings_Screens\Product_Sets::ID => new Settings_Screens\Product_Sets(), ); } else { - // TODO: Add Utility messaging tab - return [ Settings_Screens\Shops::ID => new Settings_Screens\Shops() ]; + $screens = [ Settings_Screens\Shops::ID => new Settings_Screens\Shops() ]; + } + + return $screens; + } + + /** + * Add extra screens to $this->screens - basic settings_screens + * + * @since 3.5.0 + * + * @return void + */ + public function add_extra_screens(): void { + $rollout_switches = $this->plugin->get_rollout_switches(); + $is_connected = $this->plugin->get_connection_handler()->is_connected(); + $is_whatsapp_utility_messaging_enabled = $rollout_switches->is_switch_enabled( RolloutSwitches::WHATSAPP_UTILITY_MESSAGING ); + + if ( true === $is_connected && true === $is_whatsapp_utility_messaging_enabled ) { + $this->screens[ Settings_Screens\Whatsapp_Utility::ID ] = new Settings_Screens\Whatsapp_Utility(); } } @@ -186,7 +214,18 @@ public function render_tabs( $current_tab ) { ?> plugin = $plugin; - $this->screens = $this->build_menu_item_array( $is_connected ); + $this->screens = $this->build_menu_item_array(); + add_action( 'admin_menu', array( $this, 'build_menu_item_array' ) ); + add_action( 'admin_init', array( $this, 'add_extra_screens' ) ); add_action( 'admin_menu', array( $this, 'add_menu_item' ) ); add_action( 'wp_loaded', array( $this, 'save' ) ); add_filter( 'parent_file', array( $this, 'set_parent_and_submenu_file' ) ); @@ -58,15 +67,15 @@ public function __construct( bool $is_connected ) { /** * Arranges the tabs. If the plugin is connected to FB, Advertise tab will be first, otherwise the Connection tab will be the first tab. * - * @param bool $is_connected is Facebook connected * @since 3.0.7 */ - private function build_menu_item_array( bool $is_connected ): array { + public function build_menu_item_array(): array { $advertise = [ Settings_Screens\Advertise::ID => new Settings_Screens\Advertise() ]; $connection = [ Settings_Screens\Connection::ID => new Settings_Screens\Connection() ]; - $first = ( $is_connected ) ? $advertise : $connection; - $last = ( $is_connected ) ? $connection : $advertise; + $is_connected = $this->plugin->get_connection_handler()->is_connected(); + $first = ( $is_connected ) ? $advertise : $connection; + $last = ( $is_connected ) ? $connection : $advertise; $screens = array( Settings_Screens\Product_Sync::ID => new Settings_Screens\Product_Sync(), @@ -76,6 +85,15 @@ private function build_menu_item_array( bool $is_connected ): array { return array_merge( array_merge( $first, $screens ), $last ); } + public function add_extra_screens(): void { + $rollout_switches = $this->plugin->get_rollout_switches(); + $is_connected = $this->plugin->get_connection_handler()->is_connected(); + $is_whatsapp_utility_messaging_enabled = $rollout_switches->is_switch_enabled( RolloutSwitches::WHATSAPP_UTILITY_MESSAGING ); + if ( true === $is_connected && true === $is_whatsapp_utility_messaging_enabled ) { + $this->screens[ Settings_Screens\Whatsapp_Utility::ID ] = new Settings_Screens\Whatsapp_Utility(); + } + } + /** * Adds the Facebook menu item. * @@ -219,7 +237,18 @@ public function render_tabs( $current_tab ) { ?> initHook(); + + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); + } + + /** + * Initializes this whatsapp utility settings page's properties. + */ + public function initHook(): void { + $this->id = self::ID; + $this->label = __( 'Utility messages', 'facebook-for-woocommerce' ); + $this->title = __( 'Utility messages', 'facebook-for-woocommerce' ); + } + + /** + * Enqueue the assets. + * + * @internal + * + * @since 2.0.0 + */ + public function enqueue_assets() { + + if ( ! $this->is_current_screen_page() ) { + return; + } + + wp_enqueue_style( 'wc-facebook-admin-whatsapp-settings', facebook_for_woocommerce()->get_plugin_url() . '/assets/css/admin/facebook-for-woocommerce-whatsapp-utility.css', array(), \WC_Facebookcommerce::VERSION ); + wp_enqueue_script( + 'facebook-for-woocommerce-connect-whatsapp', + facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/whatsapp-connection.js', + array( 'jquery', 'jquery-blockui', 'jquery-tiptip', 'wc-enhanced-select' ), + \WC_Facebookcommerce::PLUGIN_VERSION + ); + $waba_id = get_option( 'wc_facebook_wa_integration_waba_id', '' ); + $whatsapp_connected = ! empty( $waba_id ); + wp_localize_script( + 'facebook-for-woocommerce-connect-whatsapp', + 'facebook_for_woocommerce_whatsapp_onboarding_progress', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'facebook-for-wc-whatsapp-onboarding-progress-nonce' ), + 'whatsapp_onboarding_complete' => $whatsapp_connected, + 'i18n' => array( + 'result' => true, + ), + ) + ); + wp_enqueue_script( + 'facebook-for-woocommerce-whatsapp-consent', + facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/whatsapp-consent.js', + array( 'jquery', 'jquery-blockui', 'jquery-tiptip', 'wc-enhanced-select' ), + \WC_Facebookcommerce::PLUGIN_VERSION + ); + $consent_collection_enabled = get_option( 'wc_facebook_whatsapp_consent_collection_setting_status', null ) === 'enabled'; + wp_localize_script( + 'facebook-for-woocommerce-whatsapp-consent', + 'facebook_for_woocommerce_whatsapp_consent', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'facebook-for-wc-whatsapp-consent-nonce' ), + 'whatsapp_onboarding_complete' => $whatsapp_connected, + 'consent_collection_enabled' => $consent_collection_enabled, + 'i18n' => array( + 'result' => true, + ), + ) + ); + $is_payment_setup = (bool) get_option( 'wc_facebook_wa_integration_is_payment_setup', null ); + wp_enqueue_script( + 'facebook-for-woocommerce-whatsapp-billing', + facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/whatsapp-billing.js', + array( 'jquery', 'jquery-blockui', 'jquery-tiptip', 'wc-enhanced-select' ), + \WC_Facebookcommerce::PLUGIN_VERSION + ); + wp_localize_script( + 'facebook-for-woocommerce-whatsapp-billing', + 'facebook_for_woocommerce_whatsapp_billing', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'facebook-for-wc-whatsapp-billing-nonce' ), + 'consent_collection_enabled' => $consent_collection_enabled, + 'is_payment_setup' => $is_payment_setup, + 'i18n' => array( + 'result' => true, + ), + ) + ); + $order_placed_event_config_id = get_option( 'wc_facebook_wa_order_placed_event_config_id', null ); + $order_placed_language = get_option( 'wc_facebook_wa_order_placed_language', 'en' ); + $order_fulfilled_event_config_id = get_option( 'wc_facebook_wa_order_fulfilled_event_config_id', null ); + $order_fulfilled_language = get_option( 'wc_facebook_wa_order_fulfilled_language', 'en' ); + $order_refunded_event_config_id = get_option( 'wc_facebook_wa_order_refunded_event_config_id', null ); + $order_refunded_language = get_option( 'wc_facebook_wa_order_refunded_language', 'en' ); + wp_enqueue_script( + 'facebook-for-woocommerce-whatsapp-events', + facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/whatsapp-events.js', + array( 'jquery', 'jquery-blockui', 'jquery-tiptip', 'wc-enhanced-select' ), + \WC_Facebookcommerce::PLUGIN_VERSION + ); + wp_localize_script( + 'facebook-for-woocommerce-whatsapp-events', + 'facebook_for_woocommerce_whatsapp_events', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'facebook-for-wc-whatsapp-events-nonce' ), + 'event' => $this->get_current_event(), + 'order_placed_enabled' => ! empty( $order_placed_event_config_id ), + 'order_placed_language' => $order_placed_language, + 'order_fulfilled_enabled' => ! empty( $order_fulfilled_event_config_id ), + 'order_fulfilled_language' => $order_fulfilled_language, + 'order_refunded_enabled' => ! empty( $order_refunded_event_config_id ), + 'order_refunded_language' => $order_refunded_language, + 'i18n' => array( + 'result' => true, + ), + ) + ); + wp_enqueue_script( + 'facebook-for-woocommerce-whatsapp-finish', + facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/whatsapp-finish.js', + array( 'jquery', 'jquery-blockui', 'jquery-tiptip', 'wc-enhanced-select' ), + \WC_Facebookcommerce::PLUGIN_VERSION + ); + wp_localize_script( + 'facebook-for-woocommerce-whatsapp-finish', + 'facebook_for_woocommerce_whatsapp_finish', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'facebook-for-wc-whatsapp-finish-nonce' ), + 'i18n' => array( // will generate i18 pot translation + 'payment_setup_error' => __( 'To proceed, add a payment method to make future purchases on your accounts.', 'facebook-for-woocommerce' ), + 'onboarding_incomplete_error' => __( 'Whatsapp Business Account Onboarding is not complete or has failed.', 'facebook-for-woocommerce' ), + 'generic_error' => __( 'Something went wrong. Please try again.', 'facebook-for-woocommerce' ), + ), + ) + ); + wp_enqueue_script( + 'facebook-for-woocommerce-whatsapp-consent-remove', + facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/whatsapp-consent-remove.js', + array( 'jquery', 'jquery-blockui', 'jquery-tiptip', 'wc-enhanced-select' ), + \WC_Facebookcommerce::PLUGIN_VERSION + ); + wp_localize_script( + 'facebook-for-woocommerce-whatsapp-consent-remove', + 'facebook_for_woocommerce_whatsapp_consent_remove', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'facebook-for-wc-whatsapp-consent-disable-nonce' ), + 'i18n' => array( + 'result' => true, + ), + ) + ); + wp_enqueue_script( + 'facebook-for-woocommerce-whatsapp-templates', + facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/whatsapp-templates.js', + array( 'jquery', 'jquery-blockui', 'jquery-tiptip', 'wc-enhanced-select' ), + \WC_Facebookcommerce::PLUGIN_VERSION + ); + wp_localize_script( + 'facebook-for-woocommerce-whatsapp-templates', + 'facebook_for_woocommerce_whatsapp_templates', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'facebook-for-wc-whatsapp-templates-nonce' ), + 'i18n' => array( + 'result' => true, + ), + ) + ); + wp_enqueue_script( + 'facebook-for-woocommerce-whatsapp-disconnect', + facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/whatsapp-disconnect.js', + array( 'jquery', 'jquery-blockui', 'jquery-tiptip', 'wc-enhanced-select' ), + \WC_Facebookcommerce::PLUGIN_VERSION + ); + wp_localize_script( + 'facebook-for-woocommerce-whatsapp-disconnect', + 'facebook_for_woocommerce_whatsapp_disconnect', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'facebook-for-wc-whatsapp-disconnect-nonce' ), + 'i18n' => array( + 'result' => true, + ), + ) + ); + } + + + /** + * Renders the screen. + * + * @since 2.0.0 + */ + public function render() { + $view = $this->get_current_view(); + if ( 'utility_settings' === $view ) { + $this->render_utility_message_overview(); + } elseif ( in_array( $view, self::MANAGE_EVENT_VIEWS, true ) ) { + $this->render_manage_events_view(); + } else { + $this->render_utility_message_onboarding(); + } + parent::render(); + } + + /** + * Renders the WhatsApp Utility Onboarding screen. + */ + public function render_utility_message_onboarding() { + + ?> + +
+
+
+

+ +
+
+
+
+
+
+
+
+

+

+
+
+
+ +
+
+
+
+
+ + + +
+

+ +
+
+ +
+
+
+
+
+
+
+
+

+
+

+ + +

+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+

+

+ +

+
+
+
+
+
+
+

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+

+ +
+ + + + + +
+ + +
+
+
+

+
+ +
+ +
+
+
+
+
+
+
+

+
+ +
+
+
+ +
+
+

+
+ +
+ +
+
+
+ get_current_event(); + ?> +
+
+
+

+ + + + + + + +

+

+ + + + + + + +

+
+
+
+
+

+ +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+ +
+ 'order_management_4', + 'ORDER_FULFILLED' => 'shipment_confirmation_4', + 'ORDER_REFUNDED' => 'refund_confirmation_1', + ); + + /** @var string Default language for Library Template */ + const DEFAULT_LANGUAGE = 'en'; + + /** + * Makes an API call to Template Library API + * + * @param string $event Order Management Event + * @param string $bisu_token the BISU token received in the webhook + */ + public static function get_template_library_content( $event, $bisu_token ) { + wc_get_logger()->info( + sprintf( + __( 'In Template Library Get API call ', 'facebook-for-woocommerce' ), + ) + ); + $base_url = array( self::GRAPH_API_BASE_URL, self::API_VERSION, 'message_template_library' ); + $base_url = esc_url( implode( '/', $base_url ) ); + $library_name = self::EVENT_TO_LIBRARY_TEMPLATE_MAPPING[ $event ]; + + $params = array( + 'name' => $library_name, + 'language' => self::DEFAULT_LANGUAGE, + 'access_token' => $bisu_token, + ); + $url = add_query_arg( $params, $base_url ); + $options = array( + 'headers' => array( + 'Authorization' => $bisu_token, + ), + 'body' => array(), + ); + + $response = wp_remote_request( $url, $options ); + $status_code = wp_remote_retrieve_response_code( $response ); + $data = wp_remote_retrieve_body( $response ); + if ( is_wp_error( $response ) || 200 !== $status_code ) { + wc_get_logger()->info( + sprintf( + /* translators: %s $error_message */ + __( 'Template Library GET API call Failed %1$s ', 'facebook-for-woocommerce' ), + $data, + ) + ); + wp_send_json_error( $response, 'Template Library GET API call Failed' ); + } else { + wc_get_logger()->info( + sprintf( + __( 'Template Library GET API call Succeeded', 'facebook-for-woocommerce' ) + ) + ); + wp_send_json_success( $data, 'Finish Template Library API Call' ); + } + } + + /** + * Makes an API call to Whatsapp Utility Message Connect API + * + * @param string $waba_id WABA ID + * @param string $wacs_id WACS ID + * @param string $external_business_id external business ID + * @param string $bisu_token BISU token + */ + public static function wc_facebook_whatsapp_connect_utility_messages_call( $waba_id, $wacs_id, $external_business_id, $bisu_token ) { + $base_url = array( self::GRAPH_API_BASE_URL, self::API_VERSION, $waba_id, 'connect_utility_messages' ); + $base_url = esc_url( implode( '/', $base_url ) ); + $query_params = array( + 'external_integration_id' => $external_business_id, + 'wacs_id' => $wacs_id, + 'access_token' => $bisu_token, + ); + $base_url = add_query_arg( $query_params, $base_url ); + $options = array( + 'headers' => array( + 'Authorization' => $bisu_token, + ), + 'body' => array(), + ); + $response = wp_remote_post( $base_url, $options ); + wc_get_logger()->info( + sprintf( + /* translators: %s $response */ + __( 'Connect Whatsapp Utility Message API Response: %1$s ', 'facebook-for-woocommerce' ), + json_encode( $response ), + ) + ); + $response_body = explode( "\n", wp_remote_retrieve_body( $response ) ); + $response_data = json_decode( $response_body[0] ); + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + $error_message = $response_data->error->error_user_title ?? $response_data->error->message ?? 'Something went wrong. Please try again later!'; + + wc_get_logger()->info( + sprintf( + /* translators: %s $error_message */ + __( 'Connect Whatsapp Utility Message API Call Failure %1$s ', 'facebook-for-woocommerce' ), + $error_message, + ) + ); + wp_send_json_error( $error_message, 'Finish Onboarding Failure' ); + } else { + $integration_config_id = $response_data->id; + wc_get_logger()->info( + sprintf( + /* translators: %s $integration_config_id */ + __( 'Connect Whatsapp Utility Message API Call Success!!! Integration ID: %1$s!!!', 'facebook-for-woocommerce' ), + $integration_config_id, + ) + ); + update_option( 'wc_facebook_wa_integration_config_id', $integration_config_id ); + wp_send_json_success( $response, 'Finish Onboarding Success' ); + } + } + + /** + * Makes an API call to Whatsapp Utility Message Disconnect API and delete the options in DB + * + * @param string $waba_id WABA ID + * @param string $integration_config_id whatsapp integration config ID + * @param string $bisu_token BISU token + */ + public static function wc_facebook_disconnect_whatsapp( $waba_id, $integration_config_id, $bisu_token ) { + $base_url = array( self::GRAPH_API_BASE_URL, self::API_VERSION, $waba_id, 'disconnect_utility_messages' ); + $base_url = esc_url( implode( '/', $base_url ) ); + $query_params = array( + 'integration_config_id' => $integration_config_id, + 'access_token' => $bisu_token, + ); + $base_url = add_query_arg( $query_params, $base_url ); + $options = array( + 'headers' => array( + 'Authorization' => $bisu_token, + ), + 'body' => array(), + ); + $response = wp_remote_post( $base_url, $options ); + wc_get_logger()->info( + sprintf( + /* translators: %s $error_message */ + __( 'Disconnect Whatsapp Utility Message API Call Response: %1$s ', 'facebook-for-woocommerce' ), + json_encode( $response ), + ) + ); + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + $error_data = explode( "\n", wp_remote_retrieve_body( $response ) ); + $error_object = json_decode( $error_data[0] ); + $error_message = $error_object->error->error_user_title ?? $error_object->error->message ?? 'Something went wrong. Please try again later!'; + + wc_get_logger()->info( + sprintf( + /* translators: %s $error_message */ + __( 'Disconnect Whatsapp Utility Message API Call Error: %1$s ', 'facebook-for-woocommerce' ), + $error_message, + ) + ); + wp_send_json_error( $error_message, 'Disconnect Whatsapp Failure' ); + } else { + wc_get_logger()->info( + sprintf( + __( 'Disconnect Whatsapp Utility Message API Call Success!!!', 'facebook-for-woocommerce' ) + ) + ); + + // delete all the whatsapp setting options in DB + $wa_settings = array( + 'wc_facebook_wa_integration_waba_id', + 'wc_facebook_wa_integration_bisu_access_token', + 'wc_facebook_wa_integration_business_id', + 'wc_facebook_wa_integration_wacs_phone_number', + 'wc_facebook_wa_integration_is_payment_setup', + 'wc_facebook_wa_integration_wacs_id', + 'wc_facebook_wa_integration_waba_profile_picture_url', + 'wc_facebook_wa_integration_waba_display_name', + 'wc_facebook_whatsapp_consent_collection_setting_status', + 'wc_facebook_wa_integration_config_id', + 'wc_facebook_wa_order_placed_event_config_id', + 'wc_facebook_wa_order_placed_language', + 'wc_facebook_wa_order_fulfilled_event_config_id', + 'wc_facebook_wa_order_fulfilled_language', + 'wc_facebook_wa_order_refunded_event_config_id', + 'wc_facebook_wa_order_refunded_language', + ); + + self::wc_facebook_whatsapp_settings_delete( $wa_settings ); + + wc_get_logger()->info( + sprintf( + __( 'Disconnect Whatsapp Utility Message - Whatsapp Settings Deletion Success!!!', 'facebook-for-woocommerce' ) + ) + ); + + wp_send_json_success( $response, 'Disconnect Whatsapp Success' ); + } + } + + public static function wc_facebook_whatsapp_settings_delete( $wa_settings ) { + foreach ( $wa_settings as $setting ) { + delete_option( $setting ); // this only deletes if option exists, no error on failure + } + } + + /** + * Makes an API call to Whatsapp Utility Event Configs Post API to create or update Event Configs + * + * @param string $event Order Management Event + * @param string $integration_config_id Integration Config Id + * @param string $language Language Code + * @param string $status ACTIVE or INACTIVE + * @param string $bisu_token the BISU token received in the webhook + */ + public static function post_whatsapp_utility_messages_event_configs_call( $event, $integration_config_id, $language, $status, $bisu_token ) { + $base_url = array( self::GRAPH_API_BASE_URL, self::API_VERSION, $integration_config_id, 'event_configs' ); + $base_url = esc_url( implode( '/', $base_url ) ); + $account_url = get_permalink( get_option( 'woocommerce_myaccount_page_id' ) ); + $view_orders_endpoint = get_option( 'woocommerce_myaccount_view_order_endpoint' ); + $view_orders_base_url = esc_url( $account_url . $view_orders_endpoint ); + // Order Refunded template has no CTA + $library_template_button_inputs = 'ORDER_REFUNDED' === $event ? array() : array( + array( + 'type' => 'URL', + 'url' => array( + // View Url is dynamic and has order_id as suffix + 'base_url' => "$view_orders_base_url/{{1}}", + // Example view orders url with order id: 1234 + 'url_suffix_example' => "$view_orders_base_url/1234", + ), + ), + ); + $query_params = array( + 'event' => $event, + 'language' => $language, + 'status' => $status, + 'library_template_name' => self::EVENT_TO_LIBRARY_TEMPLATE_MAPPING[ $event ], + 'library_template_button_inputs' => $library_template_button_inputs, + 'access_token' => $bisu_token, + ); + $base_url = add_query_arg( $query_params, $base_url ); + $options = array( + 'headers' => array( + 'Authorization' => $bisu_token, + ), + 'body' => array(), + 'timeout' => 300, // 5 minutes + ); + $response = wp_remote_post( $base_url, $options ); + $status_code = wp_remote_retrieve_response_code( $response ); + $data = explode( "\n", wp_remote_retrieve_body( $response ) ); + $response_object = json_decode( $data[0] ); + $is_error = is_wp_error( $response ); + if ( is_wp_error( $response ) || 200 !== $status_code ) { + $error_message = $response_object->error->error_user_title ?? $response_object->error->message ?? 'Something went wrong. Please try again later!'; + wc_get_logger()->info( + sprintf( + /* translators: %s $error_message %s status code %s is_wp_error value*/ + __( 'Event Configs Post API call Failed with Error: %1$s, Status code: %2$d, Is Wp Error: %3$s', 'facebook-for-woocommerce' ), + $error_message, + $status_code, + (string) $is_error, + ) + ); + wp_send_json_error( $response, 'Event Configs Post API call Failed' ); + } else { + $event_config_id_option_name = implode( '_', array( self::WA_UTILITY_OPTION_PREFIX, strtolower( $event ), 'event_config_id' ) ); + $event_config_language_option_name = implode( '_', array( self::WA_UTILITY_OPTION_PREFIX, strtolower( $event ), 'language' ) ); + $event_config_id = $response_object->id; + $event_status = $response_object->status; + $language = $response_object->language; + wc_get_logger()->info( + sprintf( + /* translators: %s $option_name %s $event_config_id %s $event_status */ + __( 'Event Configs Post API call Succeeded. API Response Event Config id: %1$s, Event Status: %2$s, Language: %3$s', 'facebook-for-woocommerce' ), + $event_config_id, + $event_status, + $language, + ) + ); + if ( 'ACTIVE' === $event_status ) { + update_option( $event_config_id_option_name, $event_config_id ); + update_option( $event_config_language_option_name, $language ); + } else { + $settings = array( + $event_config_id_option_name, + $event_config_language_option_name, + ); + self::wc_facebook_whatsapp_settings_delete( + $settings + ); + } + wp_send_json_success( 'Event Configs Post API call Completed' ); + } + } + + + /** + * Makes an API call to Event Processor: Message Events Post API to send whatsapp utility messages + * TODO: Update API Endpoint from Messages to Message Events + * + * @param string $event Order Managerment event + * @param string $event_config_id Event Config Id + * @param string $language_code Language code + * @param string $wacs_id Whatsapp Phone Number id + * @param string $order_id Order id + * @param string $phone_number Customer phone number + * @param string $first_name Customer first name + * @param int $refund_value Amount refunded to the Customer + * @param string $currency Currency code + * @param string $bisu_token the BISU token received in the webhook + */ + public static function post_whatsapp_utility_messages_events_call( $event, $event_config_id, $language_code, $wacs_id, $order_id, $phone_number, $first_name, $refund_value, $currency, $bisu_token ) { + $base_url = array( self::GRAPH_API_BASE_URL, self::API_VERSION, $wacs_id, "messages?access_token=$bisu_token" ); + $base_url = esc_url( implode( '/', $base_url ) ); + $name = self::EVENT_TO_LIBRARY_TEMPLATE_MAPPING[ $event ]; + $components = self::get_components_for_event( $event, $order_id, $first_name, $refund_value, $currency ); + $options = array( + 'body' => array( + 'messaging_product' => 'whatsapp', + 'to' => $phone_number, + 'template' => array( + 'name' => $name, + 'language' => array( + 'code' => $language_code, + ), + 'components' => $components, + ), + 'type' => 'template', + ), + ); + $response = wp_remote_post( $base_url, $options ); + $status_code = wp_remote_retrieve_response_code( $response ); + $data = explode( "\n", wp_remote_retrieve_body( $response ) ); + $response_object = json_decode( $data[0] ); + if ( is_wp_error( $response ) || 200 !== $status_code ) { + $error_message = $response_object->error->error_user_title ?? $response_object->error->message ?? 'Something went wrong. Please try again later!'; + wc_get_logger()->info( + sprintf( + /* translators: %s $order_id %s $error_message */ + __( 'Messages Post API call for Order id %1$s Failed %2$s ', 'facebook-for-woocommerce' ), + $order_id, + $error_message, + ) + ); + } else { + wc_get_logger()->info( + sprintf( + /* translators: %s $order_id */ + __( 'Messages Post API call for Order id %1$s Succeeded.', 'facebook-for-woocommerce' ), + $order_id + ) + ); + } + } + + + /** + * Gets Component Objects for Order Management Events + * + * @param string $event Order Management event + * @param string $order_id Order id + * @param string $first_name Customer first name + * @param string $refund_value Amount refunded to the Customer + * @param string $currency Currency code + */ + public static function get_components_for_event( $event, $order_id, $first_name, $refund_value, $currency ) { + if ( 'ORDER_REFUNDED' === $event ) { + return array( + array( + 'type' => 'HEADER', + 'parameters' => array( + array( + 'type' => 'currency', + 'currency' => array( + 'fallback_value' => 'VALUE', + 'code' => $currency, + 'amount_1000' => $refund_value, + ), + ), + ), + ), + array( + 'type' => 'BODY', + 'parameters' => array( + array( + 'type' => 'text', + 'text' => $first_name, + ), + array( + 'type' => 'currency', + 'currency' => array( + 'fallback_value' => 'VALUE', + 'code' => $currency, + 'amount_1000' => $refund_value, + ), + ), + array( + 'type' => 'text', + 'text' => "#$order_id", + ), + ), + ), + ); + } else { + return array( + array( + 'type' => 'BODY', + 'parameters' => array( + array( + 'type' => 'text', + 'text' => $first_name, + ), + array( + 'type' => 'text', + 'text' => "#$order_id", + ), + ), + ), + array( + 'type' => 'BUTTON', + 'sub_type' => 'url', + 'index' => 0, + 'parameters' => array( + array( + 'type' => 'text', + 'text' => "$order_id", + ), + ), + ), + ); + } + } +} diff --git a/includes/Handlers/Whatsapp_Webhook.php b/includes/Handlers/Whatsapp_Webhook.php new file mode 100644 index 000000000..12d9605ef --- /dev/null +++ b/includes/Handlers/Whatsapp_Webhook.php @@ -0,0 +1,209 @@ + array( 'POST' ), + 'callback' => array( $this, 'whatsapp_webhook_callback' ), + 'permission_callback' => '__return_true', + ), + ) + ); + } + + /** + * Updates Facebook settings options. + * + * @param array $settings Array of settings to update. + * + * @return bool + * @internal + */ + private static function update_settings( $settings ) { + $updated = array(); + foreach ( $settings as $key => $value ) { + if ( ! empty( $key ) ) { + $updated[ $key ] = update_option( $key, $value ); + } + } + // if any of setting updates failed, return false + return ! in_array( false, $updated, true ); + } + + /** + * Authenticates Whatsapp Webhook using the SHA1 of the external business ID and BISU token + * + * @param string $auth_key the auth key received in the webhook + * @param string $bisu_token the BISU token received in the webhook + * + * @return bool + * @internal + */ + private static function authenticate_request( $auth_key, $bisu_token ) { + $external_business_id = get_option( 'wc_facebook_external_business_id' ); + + $expected_auth_key = 'sha1=' . (string) hash_hmac( 'sha1', $bisu_token, $external_business_id ); + + return hash_equals( $expected_auth_key, $auth_key ); + } + + + + /** + * Whatsapp Webhook Listener + * + * @since 2.3.0 + * @see Connection + * + * @param \WP_REST_Request $request The request. + * @return \WP_REST_Response + */ + public function whatsapp_webhook_callback( \WP_REST_Request $request ) { + try { + $request_params = $request->get_params(); + $waba_id = sanitize_text_field( $request_params['wabaId'] ); + $wacs_id = sanitize_text_field( $request_params['wacsId'] ); + $is_waba_payment_setup = sanitize_text_field( $request_params['isWabaPaymentSetup'] ); + $waba_profile_picture_url = sanitize_text_field( $request_params['wabaProfilePictureUrl'] ); + $bisu_token = sanitize_text_field( $request_params['clientBisuToken'] ); + $business_id = sanitize_text_field( $request_params['clientBusinessId'] ); + $wacs_phone_number = sanitize_text_field( $request_params['wacsPhoneNumber'] ); + $waba_display_name = sanitize_text_field( $request_params['wabaDisplayName'] ); + $auth_key = sanitize_text_field( $request_params['authKey'] ); + + // authentication is done via auth_key using sha_1 hash mac of BISU token and external business ID stored in woo DB + $authentication_result = self::authenticate_request( $auth_key, $bisu_token ); + + if ( false === $authentication_result ) { + wc_get_logger()->info( + sprintf( + __( 'Authentication Failure on received Whatsapp Webhook', 'facebook-for-woocommerce' ), + ) + ); + return new \WP_REST_Response( + [ + 'success' => false, + 'message' => 'Authentication Failure on received Whatsapp Webhook', + ], + 400 + ); + } + + if ( empty( $waba_id ) || empty( $bisu_token ) || empty( $business_id ) || empty( $wacs_phone_number ) || empty( $wacs_id ) ) { + wc_get_logger()->info( + sprintf( + __( 'All required onboarding info not received in Whatsapp Webhook', 'facebook-for-woocommerce' ), + ) + ); + return new \WP_REST_Response( + [ + 'success' => false, + 'message' => 'All required onboarding info not received in Whatsapp Webhook', + ], + 400 + ); + } + + wc_get_logger()->info( + sprintf( + /* translators: %s waba ID %s business ID */ + __( 'Whatsapp Account WebHook Event received. WABA ID: %1$s, Business ID: %2$s ', 'facebook-for-woocommerce' ), + $waba_id, + $business_id + ) + ); + + $options_setting_fields = array( + 'wc_facebook_wa_integration_waba_id' => $waba_id, + 'wc_facebook_wa_integration_bisu_access_token' => $bisu_token, + 'wc_facebook_wa_integration_business_id' => $business_id, + 'wc_facebook_wa_integration_wacs_phone_number' => $wacs_phone_number, + 'wc_facebook_wa_integration_is_payment_setup' => $is_waba_payment_setup, + 'wc_facebook_wa_integration_wacs_id' => $wacs_id, + 'wc_facebook_wa_integration_waba_profile_picture_url' => $waba_profile_picture_url, + 'wc_facebook_wa_integration_waba_display_name' => $waba_display_name, + + ); + + $result = self::update_settings( $options_setting_fields ); + + if ( false === $result ) { + wc_get_logger()->info( + sprintf( + /* translators: %d $waba_id, %d $business_id. */ + __( 'Whatsapp Integration Setting Fields Update Failure waba_id: %1$s, business_id: %2$s', 'facebook-for-woocommerce' ), + $waba_id, + $business_id, + ) + ); + + return new \WP_REST_Response( + [ + 'success' => false, + 'message' => 'Whatsapp Integration Setting Fields Update Failure', + ], + 400 + ); + + } + + wc_get_logger()->info( + sprintf( + /* translators: %d $waba_id, %d $business_id. */ + __( 'Whatsapp Integration Setting Fields stored successfully in wp_options. wc_facebook_wa_integration_waba_id: %1$s, wc_facebook_wa_integration_business_id: %2$s ', 'facebook-for-woocommerce' ), + $waba_id, + $business_id, + ) + ); + + return new \WP_REST_Response( [ 'success' => true ], 200 ); + } catch ( \Exception $e ) { + return $this->error_response( + [ + 'success' => false, + 'message' => $e->getMessage(), + ], + 500 + ); + } + } +} diff --git a/includes/RolloutSwitches.php b/includes/RolloutSwitches.php index e9beb84dc..1c925c269 100644 --- a/includes/RolloutSwitches.php +++ b/includes/RolloutSwitches.php @@ -23,10 +23,12 @@ class RolloutSwitches { /** @var \WC_Facebookcommerce commerce handler */ private \WC_Facebookcommerce $plugin; - public const SWITCH_ROLLOUT_FEATURES = 'rollout_enabled'; + public const SWITCH_ROLLOUT_FEATURES = 'rollout_enabled'; + public const WHATSAPP_UTILITY_MESSAGING = 'whatsapp_utility_messages_enabled'; private const ACTIVE_SWITCHES = array( self::SWITCH_ROLLOUT_FEATURES, + self::WHATSAPP_UTILITY_MESSAGING, ); /** * Stores the rollout switches and their enabled/disabled states. @@ -88,7 +90,7 @@ public function is_switch_enabled( string $switch_name ) { return false; } - return $this->rollout_switches[ $switch_name ] ?? true; + return isset( $this->rollout_switches[ $switch_name ] ) ? $this->rollout_switches[ $switch_name ] : true; } public function is_switch_active( string $switch_name ): bool { diff --git a/webpack.config.js b/webpack.config.js index ad207bcf0..ab10dc326 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,23 +13,31 @@ const jQueryUIAdminFileNames = [ 'settings-commerce', 'settings-sync', 'enhanced-settings-sync', + 'whatsapp-billing', + 'whatsapp-connection', + 'whatsapp-consent', + 'whatsapp-templates', + 'whatsapp-finish', + 'whatsapp-consent-remove', + 'whatsapp-disconnect', + 'whatsapp-events', ]; const jQueryUIAdminFileEntries = {}; jQueryUIAdminFileNames.forEach( ( name ) => { - jQueryUIAdminFileEntries[ `admin/${ name }` ] = `./assets/js/admin/${ name }.js`; + jQueryUIAdminFileEntries[ `admin/${ name }` ] = `./assets/js/admin/${ name }.js`; } ); module.exports = { - ...defaultConfig, - entry: { - // Use admin/index.js for any new React-powered UI - 'admin/index': './assets/js/admin/index.js', - ...jQueryUIAdminFileEntries, - }, - output: { - filename: '[name].js', - path: __dirname + '/assets/build', - }, + ...defaultConfig, + entry: { + // Use admin/index.js for any new React-powered UI + 'admin/index': './assets/js/admin/index.js', + ...jQueryUIAdminFileEntries, + }, + output: { + filename: '[name].js', + path: __dirname + '/assets/build', + }, };