├── .distignore ├── .wordpress-org ├── banner-1544x500.png ├── banner-772x250.png ├── icon-256x256.png ├── icon.svg ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png ├── screenshot-5.png ├── screenshot-6.png ├── screenshot-7.png ├── screenshot-8.png └── screenshot-9.png ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── DISCLAIMER.md ├── LICENSE.md ├── assets ├── admin │ ├── css │ │ └── multisafepay-admin.css │ └── js │ │ └── multisafepay-admin.js └── public │ ├── css │ └── multisafepay-public.css │ ├── img │ ├── alipay.png │ ├── alipayplus.png │ ├── amazonpay.png │ ├── amex.png │ ├── applepay.png │ ├── babycad.png │ ├── bancontact.png │ ├── banktrans-de.png │ ├── banktrans-en.png │ ├── banktrans-es.png │ ├── banktrans-fr.png │ ├── banktrans-nl.png │ ├── banktrans.png │ ├── beautywellness.png │ ├── belfius.png │ ├── betaalplan-en.png │ ├── betaalplan-nl.png │ ├── betaalplan.png │ ├── boekenbon.png │ ├── cbc.png │ ├── creditcard.png │ ├── dbrtp.png │ ├── dirdeb-en.png │ ├── dirdeb-nl.png │ ├── dirdeb.png │ ├── directbanktransfer.png │ ├── dotpay.png │ ├── einvoice.png │ ├── eps.png │ ├── fashioncheque.png │ ├── fashiongiftcard.png │ ├── fietsenbon.png │ ├── gezondheidsbon.png │ ├── giropay.png │ ├── givacard.png │ ├── good4fun.png │ ├── goodcard.png │ ├── googlepay.png │ ├── ideal-qr.png │ ├── ideal.png │ ├── in3.png │ ├── kbc.png │ ├── klarna.png │ ├── maestro.png │ ├── mastercard.png │ ├── mistercash.png │ ├── multisafepay.png │ ├── mybank.png │ ├── nationaletuinbon.png │ ├── parfumcadeaukaart.png │ ├── pay-after-delivery-installments.png │ ├── payafter-en.png │ ├── payafter-nl.png │ ├── payafter.png │ ├── paypal.png │ ├── paysafecard.png │ ├── podium.png │ ├── riverty.png │ ├── sofort.png │ ├── sportenfit.png │ ├── trustly.png │ ├── visa.png │ ├── vvv.png │ ├── wallet.png │ ├── webshopgiftcard.png │ ├── wellnessgiftcard.png │ ├── wijncadeau.png │ ├── winkelcheque.png │ ├── yourgift.png │ └── zinia.png │ └── js │ ├── multisafepay-apple-pay-wallet.js │ ├── multisafepay-apple-pay.js │ ├── multisafepay-blocks │ └── src │ │ └── index.js │ ├── multisafepay-common-wallets.js │ ├── multisafepay-google-pay-wallet.js │ ├── multisafepay-google-pay.js │ ├── multisafepay-jquery-wallets.js │ ├── multisafepay-payment-component.js │ └── multisafepay-validator-wallets.js ├── composer.json ├── index.php ├── languages ├── multisafepay-nl_BE.mo ├── multisafepay-nl_BE.po ├── multisafepay-nl_NL.mo └── multisafepay-nl_NL.po ├── multisafepay.php ├── package.json ├── readme.txt ├── src ├── Blocks │ └── BlocksController.php ├── Client │ └── MultiSafepayClient.php ├── Exceptions │ ├── MissingDependencyException.php │ └── index.php ├── Main.php ├── PaymentMethods │ ├── Base │ │ ├── BaseBrandedPaymentMethod.php │ │ ├── BaseGiftCardPaymentMethod.php │ │ ├── BasePaymentMethod.php │ │ ├── BasePaymentMethodBlocks.php │ │ ├── BaseRefunds.php │ │ └── index.php │ ├── PaymentMethodCallback.php │ ├── PaymentMethodsController.php │ └── index.php ├── Services │ ├── ApiTokenService.php │ ├── CustomerService.php │ ├── OrderService.php │ ├── PaymentComponentService.php │ ├── PaymentMethodService.php │ ├── Qr │ │ ├── QrCustomerService.php │ │ ├── QrOrderService.php │ │ ├── QrPaymentComponentService.php │ │ ├── QrPaymentWebhook.php │ │ └── QrShoppingCartService.php │ ├── SdkService.php │ ├── ShoppingCartService.php │ ├── ValidationService.php │ └── index.php ├── Settings │ ├── LogsController.php │ ├── SettingsController.php │ ├── SettingsFields.php │ ├── SettingsFieldsDisplay.php │ ├── StatusController.php │ ├── SystemReport.php │ ├── ThirdPartyCompatibility.php │ └── index.php ├── Utils │ ├── Activator.php │ ├── CustomLinks.php │ ├── DependencyChecker.php │ ├── EscapeUtil.php │ ├── Hpos.php │ ├── Internationalization.php │ ├── Loader.php │ ├── Logger.php │ ├── MoneyUtil.php │ ├── Order.php │ ├── QrCheckoutManager.php │ ├── QrOrder.php │ └── index.php └── index.php ├── templates ├── multisafepay-checkout-fields-display.php ├── multisafepay-settings-display.php └── partials │ ├── multisafepay-settings-logs-display.php │ ├── multisafepay-settings-status-display.php │ └── multisafepay-settings-support-display.php └── webpack.config.js /.distignore: -------------------------------------------------------------------------------- 1 | /.codecov.yml 2 | /.dist 3 | /.distignore 4 | /.env 5 | /.env.example 6 | /.git 7 | /.github 8 | /.gitignore 9 | /.idea 10 | /.phpcs.xml 11 | /.wordpress-org 12 | /CHANGELOG.md 13 | /CODE_OF_CONDUCT.md 14 | /DEVELOPMENT.md 15 | /Makefile 16 | /README.md 17 | /assets/public/js/multisafepay-blocks/src/* 18 | /bin 19 | /composer.json 20 | /composer.lock 21 | /coverage-report/* 22 | /coverage.xml 23 | /docker 24 | /docker-compose.yml 25 | /node_modules/* 26 | /package-lock.json 27 | /package.json 28 | /phpunit.xml 29 | /phpunit.xml.dist 30 | /tests 31 | /webpack.config.js 32 | -------------------------------------------------------------------------------- /.wordpress-org/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/banner-1544x500.png -------------------------------------------------------------------------------- /.wordpress-org/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/banner-772x250.png -------------------------------------------------------------------------------- /.wordpress-org/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/icon-256x256.png -------------------------------------------------------------------------------- /.wordpress-org/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Pioneers in ecommerce 37 | 30+ 38 | 41 | 43 | 44 | 46 | 47 | 71 | 72 | 74 | 78 | 79 | 80 | 82 | 85 | 87 | 88 | 90 | 92 | 96 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /.wordpress-org/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-1.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-2.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-3.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-4.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-5.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-6.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-7.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-8.png -------------------------------------------------------------------------------- /.wordpress-org/screenshot-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/.wordpress-org/screenshot-9.png -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at integration@multisafepay.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /DISCLAIMER.md: -------------------------------------------------------------------------------- 1 | DISCLAIMER 2 | ========== 3 | 4 | Do not edit or add to this plugin if you wish to upgrade the MultiSafepay plugin 5 | to newer versions in the future. If you wish to customize the plugin for your 6 | needs please document your changes and make backups before you update. 7 | ___ 8 | 9 | **author**: [Integration](integration@multisafepay.com)\ 10 | **copyright**: Copyright © 2021 MultiSafepay, Inc. (https://www.multisafepay.com) 11 | 12 | ___ 13 | 14 | **THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.** 20 | -------------------------------------------------------------------------------- /assets/admin/css/multisafepay-admin.css: -------------------------------------------------------------------------------- 1 | #multisafepay-settings table.form-table th label { 2 | position: relative; 3 | display: block; 4 | } 5 | #multisafepay-settings table.form-table input[type=date], 6 | #multisafepay-settings table.form-table input[type=datetime-local], 7 | #multisafepay-settings table.form-table input[type=datetime], 8 | #multisafepay-settings table.form-table input[type=email], 9 | #multisafepay-settings table.form-table input[type=number], 10 | #multisafepay-settings table.form-table input[type=password], 11 | #multisafepay-settings table.form-table input[type=tel], 12 | #multisafepay-settings table.form-table input[type=text], 13 | #multisafepay-settings table.form-table input[type=time], 14 | #multisafepay-settings table.form-table input[type=url], 15 | #multisafepay-settings table.form-table input[type=week], 16 | #multisafepay-settings table.form-table textarea, 17 | #multisafepay-settings table.form-table select { 18 | width: 400px; 19 | margin: 0; 20 | padding: 0 8px; 21 | box-sizing: border-box; 22 | vertical-align: top; 23 | } 24 | #multisafepay-support td.version-table-row { 25 | padding: 5px 10px 5px 0; 26 | } 27 | #multisafepay-support h2 { 28 | font-size: 19px; 29 | font-weight: bold; 30 | margin-top: 35px; 31 | } 32 | #multisafepay-support .docs-list { 33 | padding-left: 10px; 34 | margin-left: 10px; 35 | } 36 | #multisafepay-support .docs-list li { 37 | list-style-type: circle; 38 | } 39 | #multisafepay-support .support-list { 40 | padding-left: 10px; 41 | margin-left: 10px; 42 | } 43 | #multisafepay-support .support-list li { 44 | list-style-type: circle; 45 | } 46 | #multisafepay-support .account-list { 47 | padding-left: 10px; 48 | margin-left: 10px; 49 | } 50 | #multisafepay-support .account-list li { 51 | list-style-type: circle; 52 | } 53 | #multisafepay-support .multisafepay-sales-contact-list { 54 | padding-left: 10px; 55 | padding-top: 10px; 56 | } 57 | #multisafepay-support .multisafepay-sales-contact-list li strong { 58 | display: block; 59 | padding-bottom: 5px; 60 | } 61 | #multisafepay-support .multisafepay-sales-contact-list li:first-child { 62 | padding-top: 8px; 63 | } 64 | #multisafepay-support .multisafepay-sales-contact-list li { 65 | padding-top: 22px; 66 | list-style-type: none; 67 | } 68 | #multisafepay-support .multisafepay-sales-contact-list li ul { 69 | padding-left: 25px; 70 | 71 | } 72 | #multisafepay-support .multisafepay-sales-contact-list li ul li { 73 | padding-top: 0; 74 | list-style-type: square; 75 | } 76 | #multisafepay-support .multisafepay-sales-contact-list li ul li:first-child { 77 | padding-top: 0; 78 | } 79 | #multisafepay-system-status .multisafepay_status_table { 80 | margin-bottom: 1em; 81 | background: #fff; 82 | border: 1px solid #c3c4c7; 83 | box-shadow: 0 1px 1px rgb(0 0 0 / 4%); 84 | width: 100%; 85 | } 86 | #multisafepay-system-status .multisafepay_status_table th { 87 | padding: 9px; 88 | } 89 | #multisafepay-system-status .multisafepay_status_table th h2 { 90 | font-size: 15px; 91 | margin: 0; 92 | text-align: left; 93 | } 94 | #multisafepay-system-status .multisafepay_status_table tr:nth-child(2n) { 95 | background: #fcfcfc; 96 | } 97 | #multisafepay-system-status .multisafepay_status_table th, #multisafepay-system-status .multisafepay_status_table td { 98 | font-size: 1.1em; 99 | font-weight: 400; 100 | padding: 9px; 101 | } 102 | #multisafepay-system-status .multisafepay_status_table td:first-child { 103 | width: 33%; 104 | } 105 | #multisafepay-system-status .multisafepay_status_table td h4 { 106 | margin: 0; 107 | } 108 | 109 | #multisafepay-system-status .system-status-section { 110 | margin: 5px 0 15px; 111 | border-left-color: #008dcb !important; 112 | background: #FFF; 113 | border: 1px solid #c3c4c7; 114 | border-left-width: 4px; 115 | padding: 1px 12px; 116 | } 117 | 118 | #multisafepay-system-status #multisafepay-system-report { 119 | display: none; 120 | } 121 | 122 | #multisafepay-system-status #multisafepay-system-report textarea { 123 | font-family: monospace; 124 | width: 100%; 125 | margin: 0; 126 | height: 300px; 127 | padding: 20px; 128 | border-radius: 0; 129 | resize: none; 130 | font-size: 12px; 131 | line-height: 20px; 132 | outline: 0; 133 | background: #f0f0f1; 134 | } 135 | -------------------------------------------------------------------------------- /assets/admin/js/multisafepay-admin.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $( 3 | function ( 4 | ) { 5 | function togglePaymentSettingsFields( direct, merchantInfo ) { 6 | const formInput = $( merchantInfo ).closest( 'tr' ); 7 | 8 | if ( $( direct ).find( 'option:selected' ).val() === '1' ) { 9 | formInput.show(); 10 | } else { 11 | formInput.hide(); 12 | } 13 | 14 | $( direct ).on( 'change', function () { 15 | if ( $( this ).find( 'option:selected' ).val() === '1' ) { 16 | formInput.show(); 17 | } else { 18 | formInput.hide(); 19 | } 20 | }); 21 | } 22 | 23 | togglePaymentSettingsFields( 24 | '#woocommerce_multisafepay_googlepay_use_direct_button', 25 | '#woocommerce_multisafepay_googlepay_merchant_name, #woocommerce_multisafepay_googlepay_merchant_id' 26 | ); 27 | 28 | togglePaymentSettingsFields( 29 | '#woocommerce_multisafepay_applepay_use_direct_button', 30 | '#woocommerce_multisafepay_applepay_merchant_name' 31 | ); 32 | 33 | function addMultiSafepayTransactionLink() { 34 | const orderNumbers = $('.woocommerce-order-data__meta.order_number'); 35 | const theRegex = /\((\d+)\)/; 36 | 37 | orderNumbers.each( function() { 38 | const objectThis = $( this ); 39 | const currentHtml = objectThis.html(); 40 | if ( ( typeof multisafepayAdminData !== 'undefined' ) && multisafepayAdminData.transactionUrl ) { 41 | const newHtml = currentHtml.replace( theRegex, ( match, transactionId ) => { 42 | return '(' + transactionId + ')'; 43 | }); 44 | objectThis.html( newHtml ); 45 | } 46 | }); 47 | } 48 | 49 | addMultiSafepayTransactionLink(); 50 | }); 51 | })( jQuery ); 52 | -------------------------------------------------------------------------------- /assets/public/css/multisafepay-public.css: -------------------------------------------------------------------------------- 1 | #payment .payment_methods li p { 2 | margin: 0 0 1.41575em !important; 3 | } 4 | #payment .payment_methods li img { 5 | float: right; 6 | border: 0; 7 | padding: 0; 8 | max-height: 1.618em; 9 | } 10 | #payment .payment_methods .wc_payment_method .multisafepay-payment-component .loader-wrapper { 11 | min-width: 100%; 12 | min-height: 100%; 13 | height: 100%; 14 | top: 0; 15 | left: 0; 16 | background: rgba(248, 248, 248, 0.93); 17 | position: absolute; 18 | z-index: 300; 19 | } 20 | #payment .payment_methods .wc_payment_method .multisafepay-payment-component .loader-wrapper .loader { 21 | margin: 0; 22 | display: block; 23 | position: absolute; 24 | left: 45%; 25 | top: 40%; 26 | border: 25px solid rgba(100, 100, 100, 0.2); 27 | width: 1px; 28 | height: 1px; 29 | border-left-color: transparent; 30 | border-right-color: transparent; 31 | -webkit-border-radius: 50px; 32 | -moz-border-radius: 50px; 33 | border-radius: 50px; 34 | -webkit-animation: msp-spin 1.5s infinite; 35 | -moz-animation: msp-spin 1.5s infinite; 36 | animation: msp-spin 1.5s infinite; 37 | } 38 | .apple-pay-button { 39 | display: inline-block; 40 | -webkit-appearance: -apple-pay-button; 41 | -apple-pay-button-type: plain; 42 | width: 100%; 43 | min-width: 160px; 44 | height: 65px; 45 | min-height: 40px; 46 | border-radius: 0; 47 | } 48 | .apple-pay-button-black { 49 | -apple-pay-button-style: black; 50 | } 51 | .gpay-button.short, .gpay-button.plain { 52 | min-width: 160px !important; 53 | min-height: 40px !important; 54 | border-radius: 0 !important; 55 | background-size: 84px 33px !important; 56 | } 57 | @supports (not (-moz-appearance:none)) { 58 | .msp-ui-qr-code { 59 | max-width: 55% !important; 60 | max-height: 55% !important; 61 | } 62 | } 63 | @supports (-moz-appearance:none) { 64 | .msp-ui-qr-code { 65 | width: 206px !important; 66 | height: 206px !important; 67 | max-width: 206px !important; 68 | max-height: 206px !important; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /assets/public/img/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/alipay.png -------------------------------------------------------------------------------- /assets/public/img/alipayplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/alipayplus.png -------------------------------------------------------------------------------- /assets/public/img/amazonpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/amazonpay.png -------------------------------------------------------------------------------- /assets/public/img/amex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/amex.png -------------------------------------------------------------------------------- /assets/public/img/applepay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/applepay.png -------------------------------------------------------------------------------- /assets/public/img/babycad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/babycad.png -------------------------------------------------------------------------------- /assets/public/img/bancontact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/bancontact.png -------------------------------------------------------------------------------- /assets/public/img/banktrans-de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/banktrans-de.png -------------------------------------------------------------------------------- /assets/public/img/banktrans-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/banktrans-en.png -------------------------------------------------------------------------------- /assets/public/img/banktrans-es.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/banktrans-es.png -------------------------------------------------------------------------------- /assets/public/img/banktrans-fr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/banktrans-fr.png -------------------------------------------------------------------------------- /assets/public/img/banktrans-nl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/banktrans-nl.png -------------------------------------------------------------------------------- /assets/public/img/banktrans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/banktrans.png -------------------------------------------------------------------------------- /assets/public/img/beautywellness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/beautywellness.png -------------------------------------------------------------------------------- /assets/public/img/belfius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/belfius.png -------------------------------------------------------------------------------- /assets/public/img/betaalplan-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/betaalplan-en.png -------------------------------------------------------------------------------- /assets/public/img/betaalplan-nl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/betaalplan-nl.png -------------------------------------------------------------------------------- /assets/public/img/betaalplan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/betaalplan.png -------------------------------------------------------------------------------- /assets/public/img/boekenbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/boekenbon.png -------------------------------------------------------------------------------- /assets/public/img/cbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/cbc.png -------------------------------------------------------------------------------- /assets/public/img/creditcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/creditcard.png -------------------------------------------------------------------------------- /assets/public/img/dbrtp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/dbrtp.png -------------------------------------------------------------------------------- /assets/public/img/dirdeb-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/dirdeb-en.png -------------------------------------------------------------------------------- /assets/public/img/dirdeb-nl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/dirdeb-nl.png -------------------------------------------------------------------------------- /assets/public/img/dirdeb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/dirdeb.png -------------------------------------------------------------------------------- /assets/public/img/directbanktransfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/directbanktransfer.png -------------------------------------------------------------------------------- /assets/public/img/dotpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/dotpay.png -------------------------------------------------------------------------------- /assets/public/img/einvoice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/einvoice.png -------------------------------------------------------------------------------- /assets/public/img/eps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/eps.png -------------------------------------------------------------------------------- /assets/public/img/fashioncheque.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/fashioncheque.png -------------------------------------------------------------------------------- /assets/public/img/fashiongiftcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/fashiongiftcard.png -------------------------------------------------------------------------------- /assets/public/img/fietsenbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/fietsenbon.png -------------------------------------------------------------------------------- /assets/public/img/gezondheidsbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/gezondheidsbon.png -------------------------------------------------------------------------------- /assets/public/img/giropay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/giropay.png -------------------------------------------------------------------------------- /assets/public/img/givacard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/givacard.png -------------------------------------------------------------------------------- /assets/public/img/good4fun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/good4fun.png -------------------------------------------------------------------------------- /assets/public/img/goodcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/goodcard.png -------------------------------------------------------------------------------- /assets/public/img/googlepay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/googlepay.png -------------------------------------------------------------------------------- /assets/public/img/ideal-qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/ideal-qr.png -------------------------------------------------------------------------------- /assets/public/img/ideal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/ideal.png -------------------------------------------------------------------------------- /assets/public/img/in3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/in3.png -------------------------------------------------------------------------------- /assets/public/img/kbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/kbc.png -------------------------------------------------------------------------------- /assets/public/img/klarna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/klarna.png -------------------------------------------------------------------------------- /assets/public/img/maestro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/maestro.png -------------------------------------------------------------------------------- /assets/public/img/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/mastercard.png -------------------------------------------------------------------------------- /assets/public/img/mistercash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/mistercash.png -------------------------------------------------------------------------------- /assets/public/img/multisafepay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/multisafepay.png -------------------------------------------------------------------------------- /assets/public/img/mybank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/mybank.png -------------------------------------------------------------------------------- /assets/public/img/nationaletuinbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/nationaletuinbon.png -------------------------------------------------------------------------------- /assets/public/img/parfumcadeaukaart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/parfumcadeaukaart.png -------------------------------------------------------------------------------- /assets/public/img/pay-after-delivery-installments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/pay-after-delivery-installments.png -------------------------------------------------------------------------------- /assets/public/img/payafter-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/payafter-en.png -------------------------------------------------------------------------------- /assets/public/img/payafter-nl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/payafter-nl.png -------------------------------------------------------------------------------- /assets/public/img/payafter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/payafter.png -------------------------------------------------------------------------------- /assets/public/img/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/paypal.png -------------------------------------------------------------------------------- /assets/public/img/paysafecard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/paysafecard.png -------------------------------------------------------------------------------- /assets/public/img/podium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/podium.png -------------------------------------------------------------------------------- /assets/public/img/riverty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/riverty.png -------------------------------------------------------------------------------- /assets/public/img/sofort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/sofort.png -------------------------------------------------------------------------------- /assets/public/img/sportenfit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/sportenfit.png -------------------------------------------------------------------------------- /assets/public/img/trustly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/trustly.png -------------------------------------------------------------------------------- /assets/public/img/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/visa.png -------------------------------------------------------------------------------- /assets/public/img/vvv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/vvv.png -------------------------------------------------------------------------------- /assets/public/img/wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/wallet.png -------------------------------------------------------------------------------- /assets/public/img/webshopgiftcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/webshopgiftcard.png -------------------------------------------------------------------------------- /assets/public/img/wellnessgiftcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/wellnessgiftcard.png -------------------------------------------------------------------------------- /assets/public/img/wijncadeau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/wijncadeau.png -------------------------------------------------------------------------------- /assets/public/img/winkelcheque.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/winkelcheque.png -------------------------------------------------------------------------------- /assets/public/img/yourgift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/yourgift.png -------------------------------------------------------------------------------- /assets/public/img/zinia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiSafepay/woocommerce/d9e045c7397a9156ccffb81c1bf1e3586ed16195/assets/public/img/zinia.png -------------------------------------------------------------------------------- /assets/public/js/multisafepay-apple-pay.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * DISCLAIMER 4 | * 5 | * Do not edit or add to this file if you wish to upgrade the MultiSafepay plugin 6 | * to newer versions in the future. If you wish to customize the plugin for your 7 | * needs, please document your changes and make backups before you update. 8 | * 9 | * @author MultiSafepay 10 | * @copyright Copyright (c) MultiSafepay, Inc. (https://www.multisafepay.com) 11 | * @license http://www.gnu.org/licenses/gpl-3.0.html 12 | * @package MultiSafepay 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | (function($) { 23 | 'use strict'; 24 | $( 25 | function() { 26 | check_if_apple_pay_available(); 27 | $( document ).ajaxComplete( 28 | function( e, xhr, settings ) { 29 | if ( settings.url.indexOf( '?wc-ajax=update_order_review' ) !== -1 ) { 30 | check_if_apple_pay_available(); 31 | } 32 | } 33 | ); 34 | } 35 | ); 36 | 37 | function check_if_apple_pay_available() { 38 | if ( ! window.ApplePaySession || ! ApplePaySession.canMakePayments() ) { 39 | $( '.payment_method_multisafepay_applepay' ).remove(); 40 | } 41 | } 42 | })( jQuery ); 43 | -------------------------------------------------------------------------------- /assets/public/js/multisafepay-blocks/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * DISCLAIMER 4 | * 5 | * Do not edit or add to this file if you wish to upgrade the MultiSafepay plugin 6 | * to newer versions in the future. If you wish to customize the plugin for your 7 | * needs, please document your changes and make backups before you update. 8 | * 9 | * @author MultiSafepay 10 | * @copyright Copyright (c) MultiSafepay, Inc. (https://www.multisafepay.com) 11 | * @license http://www.gnu.org/licenses/gpl-3.0.html 12 | * @package MultiSafepay 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | function check_apple_pay_availability() { 23 | return window.ApplePaySession && ApplePaySession.canMakePayments(); 24 | } 25 | 26 | const registerMultiSafepayPaymentMethods = ( { wc, multisafepay_gateways } ) => { 27 | const { registerPaymentMethod } = wc.wcBlocksRegistry; 28 | 29 | multisafepay_gateways.forEach( 30 | ( gateway ) => 31 | { 32 | if ( gateway.is_admin || ( gateway.id !== 'multisafepay_applepay' ) || check_apple_pay_availability() ) { 33 | registerPaymentMethod( createOptions( gateway ) ); 34 | } 35 | } 36 | ); 37 | } 38 | 39 | const createOptions = ( gateway ) => { 40 | return { 41 | name: gateway.id, 42 | label: gateway.title, 43 | paymentMethodId: gateway.id, 44 | edit: React.createElement( 'div', null, '' ), 45 | canMakePayment: () => true, 46 | ariaLabel: gateway.title, 47 | content: React.createElement( 'div', null, gateway.description ), 48 | }; 49 | }; 50 | 51 | document.addEventListener( 52 | 'DOMContentLoaded', 53 | () => 54 | { 55 | registerMultiSafepayPaymentMethods( 56 | window 57 | ); 58 | } 59 | ); 60 | -------------------------------------------------------------------------------- /assets/public/js/multisafepay-google-pay-wallet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * DISCLAIMER 4 | * 5 | * Do not edit or add to this file if you wish to upgrade the MultiSafepay plugin 6 | * to newer versions in the future. If you wish to customize the plugin for your 7 | * needs, please document your changes and make backups before you update. 8 | * 9 | * @author MultiSafepay 10 | * @copyright Copyright (c) MultiSafepay, Inc. (https://www.multisafepay.com) 11 | * @license http://www.gnu.org/licenses/gpl-3.0.html 12 | * @package MultiSafepay 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | /** 23 | * Set of global variables following the Google Pay API 24 | * 25 | * - baseRequest: Object 26 | * - tokenizationSpecification: Object 27 | * - allowedCardNetworks: Array 28 | * - allowedCardAuthMethods: Array 29 | * - baseCardPaymentMethod: Object 30 | * - cardPaymentMethod: Object 31 | * - paymentsClient: Object 32 | * - isReadyToPayRequest: Object 33 | * 34 | * They need to be created from the global scope 35 | */ 36 | const baseRequest = { 37 | apiVersion: 2, 38 | apiVersionMinor: 0 39 | }; 40 | 41 | const tokenizationSpecification = { 42 | type: 'PAYMENT_GATEWAY', 43 | parameters: { 44 | 'gateway': 'multisafepay', 45 | 'gatewayMerchantId': configGooglePay.gatewayMerchantId.toString() 46 | } 47 | }; 48 | 49 | const allowedCardNetworks = ['MASTERCARD', 'VISA']; 50 | const allowedCardAuthMethods = ['CRYPTOGRAM_3DS', 'PAN_ONLY']; 51 | 52 | const baseCardPaymentMethod = { 53 | type: 'CARD', 54 | parameters: { 55 | allowedAuthMethods: allowedCardAuthMethods, 56 | allowedCardNetworks: allowedCardNetworks 57 | } 58 | }; 59 | 60 | const cardPaymentMethod = Object.assign( 61 | {tokenizationSpecification: tokenizationSpecification}, 62 | baseCardPaymentMethod 63 | ); 64 | 65 | let paymentsClient = false, isReadyToPayRequest = false; 66 | 67 | /** 68 | * Create a default value for configGooglePay if Google Pay is 69 | * enabled as redirect automatically 70 | * 71 | * @package MultiSafepay Shared Class for Direct Payments 72 | */ 73 | if (typeof configGooglePay === 'undefined') { 74 | configGooglePay = { 'debugMode' : null }; 75 | } 76 | 77 | (function ($) { 78 | $( 79 | function () { 80 | /** 81 | * Checking if the Google Pay API file has been loaded 82 | */ 83 | if ( ! window.google || ! window.google.payments || ! window.google.payments.api ) { 84 | console.error( 'Error initializing Google Pay: Script not loaded' ); 85 | } else { 86 | const googlePayConfigEnvironment = configGooglePay.environment === 'LIVE' ? 'PRODUCTION' : 'TEST'; 87 | paymentsClient = new google.payments.api.PaymentsClient( { environment: googlePayConfigEnvironment } ); 88 | isReadyToPayRequest = Object.assign( {}, baseRequest ); 89 | isReadyToPayRequest.allowedPaymentMethods = [baseCardPaymentMethod]; 90 | } 91 | } 92 | ); 93 | })( jQuery ); 94 | 95 | /** 96 | * Class for Google Pay Direct 97 | */ 98 | class GooglePayDirect { 99 | /** 100 | * @returns {void} 101 | */ 102 | constructor() { 103 | /** 104 | * Initialize the debug mode if is configured 105 | */ 106 | this.initializeDebug(); 107 | 108 | /** 109 | * Initialize the class 110 | * 111 | * @returns {Promise} 112 | */ 113 | this.init() 114 | .then( 115 | () => { 116 | debugDirect( 'Google Pay Direct class initialized', this.debug, 'log' ); 117 | } 118 | ) 119 | .catch( 120 | error => { 121 | console.error( 'Error initializing Google Pay Direct:', error ); 122 | } 123 | ); 124 | } 125 | 126 | /** 127 | * Initialize the debug mode if configGooglePay is defined 128 | * and the debugMode is enabled 129 | * 130 | * @returns {void} 131 | */ 132 | initializeDebug() { 133 | this.debug = ( typeof configGooglePay !== 'undefined' ) && 134 | ( typeof configGooglePay.debugMode !== 'undefined' ) && 135 | ( configGooglePay.debugMode === true ); 136 | } 137 | 138 | /** 139 | * Initialize the process calling to create the button 140 | * 141 | * @returns {Promise} 142 | */ 143 | async init() 144 | { 145 | try { 146 | await this.createGooglePayButton(); 147 | } catch ( error ) { 148 | console.error( 'Error creating Google Pay button:', error ); 149 | } 150 | } 151 | 152 | /** 153 | * Create the Google Pay button 154 | * 155 | * @returns {Promise} 156 | */ 157 | async createGooglePayButton() 158 | { 159 | // Check if previous buttons already exist and remove them 160 | cleanUpDirectButtons(); 161 | 162 | if ( ! paymentsClient || ! paymentsClient.createButton ) { 163 | debugDirect( 'Error creating Google Pay button: Script not loaded rightly', this.debug ); 164 | return; 165 | } 166 | 167 | const buttonContainer = document.getElementById( 'place_order' ).parentElement; 168 | if ( ! buttonContainer ) { 169 | debugDirect( 'Button container not found', this.debug ); 170 | return; 171 | } 172 | 173 | // Features of the button 174 | const buttonTag = paymentsClient.createButton( 175 | { 176 | buttonType: 'plain', 177 | buttonColor: 'black', 178 | buttonSizeMode: 'fill', 179 | onClick: this.onGooglePaymentButtonClicked.bind( this ) 180 | } 181 | ); 182 | 183 | const isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ); 184 | const height = isSafari ? '65px' : '64px'; 185 | buttonContainer.style.setProperty( 'height', height, 'important' ); 186 | 187 | // Append the button to the div 188 | buttonContainer.appendChild( buttonTag ); 189 | } 190 | 191 | /** 192 | * Create the Google Pay payment data request 193 | * 194 | * Some variables from the global scope are launched from 195 | * the internal code of Prestashop 196 | * 197 | * @returns {object} paymentDataRequest 198 | */ 199 | getGooglePaymentDataRequest() 200 | { 201 | const paymentDataRequest = Object.assign( {}, baseRequest ); 202 | paymentDataRequest.allowedPaymentMethods = [cardPaymentMethod]; 203 | paymentDataRequest.transactionInfo = { 204 | totalPriceStatus: 'FINAL', 205 | totalPrice: configGooglePay.totalPrice.toFixed( 2 ), 206 | currencyCode: configGooglePay.currencyCode, 207 | countryCode: configGooglePay.countryCode 208 | }; 209 | paymentDataRequest.merchantInfo = { 210 | merchantName: configGooglePay.merchantName, 211 | merchantId: configGooglePay.merchantId 212 | }; 213 | return paymentDataRequest; 214 | } 215 | 216 | /** 217 | * Event handler for the Google Pay button 218 | * 219 | * @returns {Promise} 220 | */ 221 | async onGooglePaymentButtonClicked() 222 | { 223 | const validatorInstance = new FieldsValidator(); 224 | const fieldsAreValid = await validatorInstance.checkFields(); 225 | if ( fieldsAreValid ) { 226 | if ( paymentsClient && paymentsClient.loadPaymentData ) { 227 | try { 228 | const dataRequest = this.getGooglePaymentDataRequest(); 229 | if (this.debug && ( ! dataRequest || (typeof dataRequest !== 'object' ) ) ) { 230 | debugDirect( 'Invalid data from paymentDataRequest object', this.debug ); 231 | } 232 | 233 | const paymentData = await paymentsClient.loadPaymentData( dataRequest ); 234 | const processedPayment = this.processGooglePayment( paymentData ); 235 | if ( this.debug && ! processedPayment ) { 236 | debugDirect( 'Failed to process Google Pay payment', this.debug ); 237 | } 238 | } catch ( message ) { 239 | console.error( 'Message from the Google Pay API:', message ); 240 | } 241 | } else { 242 | debugDirect( 'Terms of Service for Google Pay not checked', this.debug, 'warn' ); 243 | } 244 | } else { 245 | debugDirect( 'Not all mandatory fields were filled out', this.debug, 'warn' ); 246 | } 247 | } 248 | 249 | /** 250 | * Submit the Google Pay form 251 | * 252 | * @param {string} paymentToken 253 | * @returns {boolean} 254 | */ 255 | submitGooglePayForm( paymentToken ) 256 | { 257 | if ( ( typeof paymentToken !== 'string' ) || ( paymentToken.trim() === '' ) ) { 258 | debugDirect( 'Invalid payload provided', this.debug ); 259 | return false; 260 | } 261 | 262 | const googlepayForm = document.querySelector( 'form[name="checkout"]' ); 263 | 264 | if ( ! googlepayForm ) { 265 | debugDirect( 'Google Pay form not found', this.debug ); 266 | return false; 267 | } 268 | 269 | // Settings the features of the input field 270 | const inputField = document.createElement( 'input' ); 271 | inputField.type = 'hidden'; 272 | inputField.name = 'payment_token'; 273 | inputField.value = paymentToken; 274 | 275 | // Settings the features of the browser field 276 | const browserField = document.createElement( 'input' ); 277 | browserField.type = 'hidden'; 278 | browserField.name = 'browser'; 279 | browserField.value = getCustomerBrowserInfo(); 280 | 281 | // Add the hidden field to the form including the token value 282 | googlepayForm.appendChild( inputField ); 283 | // Add the hidden field to the form including the browser info 284 | googlepayForm.appendChild( browserField ); 285 | // Submit the form automatically 286 | googlepayForm.dispatchEvent( new Event( 'submit' ) ); 287 | return true; 288 | } 289 | 290 | /** 291 | * @param {object} paymentData 292 | * @returns {boolean} 293 | */ 294 | processGooglePayment(paymentData) 295 | { 296 | // Validate input 297 | if ( ! paymentData || 298 | ! paymentData.paymentMethodData || 299 | ! paymentData.paymentMethodData.tokenizationData || 300 | ! paymentData.paymentMethodData.tokenizationData.token 301 | ) { 302 | debugDirect( 'Invalid payment data received', this.debug ); 303 | return false; 304 | } 305 | 306 | // Extract the token from the payment data sent by Google Pay 307 | const payload = paymentData.paymentMethodData.tokenizationData.token; 308 | 309 | // Check if the payload is a string and not empty 310 | if ( ( typeof payload !== 'string' ) || ( payload.trim() === '' ) ) { 311 | debugDirect( 'Invalid token received', this.debug ); 312 | return false; 313 | } 314 | 315 | // Call the submit function only if the payload is valid 316 | return this.submitGooglePayForm( payload ); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /assets/public/js/multisafepay-google-pay.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * DISCLAIMER 4 | * 5 | * Do not edit or add to this file if you wish to upgrade the MultiSafepay plugin 6 | * to newer versions in the future. If you wish to customize the plugin for your 7 | * needs, please document your changes and make backups before you update. 8 | * 9 | * @author MultiSafepay 10 | * @copyright Copyright (c) MultiSafepay, Inc. (https://www.multisafepay.com) 11 | * @license http://www.gnu.org/licenses/gpl-3.0.html 12 | * @package MultiSafepay 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | (function($) { 23 | 'use strict'; 24 | $( 25 | function() { 26 | check_if_google_pay_is_available(); 27 | $( document ).ajaxComplete( 28 | function( e, xhr, settings ) { 29 | if ( settings.url.indexOf( '?wc-ajax=update_order_review' ) !== -1 ) { 30 | check_if_google_pay_is_available(); 31 | } 32 | } 33 | ); 34 | } 35 | ); 36 | 37 | /** 38 | * Check if Google Pay is available 39 | */ 40 | function check_if_google_pay_is_available() { 41 | if ( ! window.google || ! window.google.payments || ! window.google.payments.api ) { 42 | console.error( 'Error initializing Google Pay: Script not loaded' ); 43 | } else { 44 | let googlePayClient = new google.payments.api.PaymentsClient( { environment: 'PRODUCTION' } ); 45 | 46 | const baseCardPaymentMethod = { 47 | type: 'CARD', 48 | parameters: { 49 | allowedCardNetworks: ['VISA', 'MASTERCARD'], 50 | allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'] 51 | } 52 | }; 53 | 54 | const googlePayBaseConfiguration = { 55 | apiVersion: 2, 56 | apiVersionMinor: 0, 57 | allowedPaymentMethods: [baseCardPaymentMethod], 58 | existingPaymentMethodRequired: true 59 | } 60 | googlePayClient.isReadyToPay( googlePayBaseConfiguration ).then( 61 | ( response ) => { 62 | if ( ! response.result ) { 63 | $( '.payment_method_multisafepay_googlepay' ).remove(); 64 | } 65 | } 66 | ); 67 | } 68 | } 69 | })( jQuery ); 70 | -------------------------------------------------------------------------------- /assets/public/js/multisafepay-jquery-wallets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * DISCLAIMER 4 | * 5 | * Do not edit or add to this file if you wish to upgrade the MultiSafepay plugin 6 | * to newer versions in the future. If you wish to customize the plugin for your 7 | * needs, please document your changes and make backups before you update. 8 | * 9 | * @author MultiSafepay 10 | * @copyright Copyright (c) MultiSafepay, Inc. (https://www.multisafepay.com) 11 | * @license http://www.gnu.org/licenses/gpl-3.0.html 12 | * @package MultiSafepay 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | (function ($) { 23 | $( 24 | function ( 25 | ) { 26 | /** 27 | * Create an instance of the FieldsValidator class 28 | * 29 | * @type {FieldsValidator} 30 | */ 31 | const validatorInstance = new FieldsValidator(); 32 | 33 | /** 34 | * Listen to the Select2 events to remove 35 | * the inline styles and error messages 36 | * 37 | * @returns {void} 38 | */ 39 | function select2Validation() { 40 | $( 'select' ).each( 41 | function() { 42 | $( this ).on( 43 | 'select2:select', 44 | function() { 45 | debugDirect( 'Select2 action initialized for field: ' + this.name, debugStatus, 'log' ); 46 | 47 | const wrapper = $( this ).closest( '.validate-required' ); 48 | const select2Container = wrapper.find( '.select2-selection' ); 49 | if ( select2Container.length ) { 50 | // Remove the inline red border style 51 | select2Container.removeAttr( 'style' ); 52 | // Alternative approach if removeAttr doesn't work 53 | select2Container.css( 'border', '' ); 54 | } 55 | 56 | // Get the field ID container 57 | const fieldId = this.name + '_field'; 58 | const fieldContainer = $( '#' + fieldId ); 59 | if ( fieldContainer.length ) { 60 | // Remove WooCommerce invalid class and add validated class 61 | fieldContainer.removeClass( 'woocommerce-invalid' ); 62 | fieldContainer.addClass( 'woocommerce-validated' ); 63 | } 64 | validatorInstance.removeErrorMessage( this.name ); 65 | } 66 | ); 67 | } 68 | ); 69 | } 70 | 71 | /** 72 | * Get the total price from the server side 73 | * 74 | * @returns {Promise} 75 | */ 76 | async function getTotalPriceFromServer() { 77 | if ( 78 | ( typeof configAdminUrlAjax !== 'undefined' ) && 79 | ( typeof configAdminUrlAjax.location !== 'undefined' ) && 80 | ( typeof configAdminUrlAjax.nonce !== 'undefined') 81 | ) { 82 | try { 83 | const response = await $.ajax( 84 | { 85 | url: configAdminUrlAjax.location, 86 | type: 'POST', 87 | data: { 88 | 'nonce': configAdminUrlAjax.nonce, 89 | 'action': 'get_updated_total_price', 90 | } 91 | } 92 | ); 93 | 94 | // Check if totalPrice is a valid number 95 | if ( ( typeof response.totalPrice === 'number' ) && ! isNaN( response.totalPrice ) ) { 96 | // Round the number to avoid floating-point precision issues 97 | return Math.round( response.totalPrice ) / 100; 98 | } else { 99 | // Handle invalid totalPrice 100 | debugDirect( 'Invalid or non-numeric total price received: ' + JSON.stringify( response.totalPrice ), debugStatus ); 101 | return 0; 102 | } 103 | } catch ( error ) { 104 | console.error( 'Error trying to get the total price from the server side', error ); 105 | return 0; 106 | } 107 | } else { 108 | debugDirect( 'Values for configAdminUrlAjax not defined', debugStatus ); 109 | return 0; 110 | } 111 | } 112 | 113 | /** 114 | * Check if the total price has been changed in the checkout page 115 | * 116 | * @returns {void} 117 | */ 118 | async function checkActualTotalPrice() { 119 | try { 120 | const totalNumber = await getTotalPriceFromServer(); 121 | if ( totalNumber > 0 ) { 122 | // Check if configGooglePay and configApplePay are defined 123 | // before attempting to access their properties 124 | if ( 125 | ( typeof configGooglePay !== 'undefined' ) && 126 | ( typeof configGooglePay.totalPrice !== 'undefined' ) && 127 | ( totalNumber !== configGooglePay.totalPrice ) 128 | ) { 129 | configGooglePay.totalPrice = totalNumber; 130 | } 131 | 132 | if ( 133 | ( typeof configApplePay !== 'undefined' ) && 134 | ( typeof configApplePay.totalPrice !== 'undefined' ) && 135 | ( totalNumber !== configApplePay.totalPrice ) 136 | ) { 137 | configApplePay.totalPrice = totalNumber; 138 | } 139 | debugDirect( 'Total price is ' + totalNumber.toFixed( 2 ), debugStatus, 'log' ); 140 | } else { 141 | debugDirect( 'Total price was not provided by the server', debugStatus, 'warn' ); 142 | } 143 | } catch ( error ) { 144 | console.error( 'Error fetching the total price from getTotalPriceFromServer()', error ); 145 | } finally { 146 | $( '.gpay-button' ).prop( 'disabled', false ); 147 | $( '.apple-pay-button' ).prop( 'disabled', false ); 148 | } 149 | } 150 | 151 | /** 152 | * Initialize the class to launch Google Pay and Apple Pay 153 | * as direct payment methods 154 | */ 155 | new GoogleApplePayDirectHandler(); 156 | 157 | /** 158 | * Initialize the listening to the Select2 events 159 | */ 160 | select2Validation(); 161 | 162 | /** 163 | * Listen to the ajaxComplete event to check if the total price 164 | * has been changed in the checkout page 165 | * and enable the Google Pay and Apple Pay buttons 166 | */ 167 | $( document ).ajaxComplete( 168 | function( e, xhr, settings ) { 169 | if ( settings.url.indexOf( '?wc-ajax=update_order_review' ) !== -1 ) { 170 | $( document ).one( 171 | 'update_checkout', 172 | () => { 173 | $( '.gpay-button' ).prop( 'disabled', true ); 174 | $( '.apple-pay-button' ).prop( 'disabled', true ); 175 | } 176 | ); 177 | $( document ).one( 178 | 'updated_checkout', 179 | () => { 180 | checkActualTotalPrice(); 181 | // Remove the orphan error messages from the notice group 182 | validatorInstance.removeOrphanErrorMessages(); 183 | // Re-initialize select2 validation after checkout is updated 184 | select2Validation(); 185 | } 186 | ); 187 | $( document ).on( 188 | 'payment_method_selected', 189 | () => { 190 | $( '.gpay-button' ).prop( 'disabled', false ); 191 | $( '.apple-pay-button' ).prop( 'disabled', false ); 192 | } 193 | ); 194 | new GoogleApplePayDirectHandler(); 195 | } 196 | } 197 | ); 198 | } 199 | ); 200 | })( jQuery ); 201 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multisafepay/woocommerce", 3 | "description": "A new WooCommerce plugin", 4 | "type": "wordpress-plugin", 5 | "version": "6.9.0", 6 | "license": "GPL-3.0-or-later", 7 | "minimum-stability": "RC", 8 | "keywords" : [ "wordpress", "multisafepay" ], 9 | "require": { 10 | "multisafepay/php-sdk": "^5.17", 11 | "nyholm/psr7": "^1.4", 12 | "psr/http-client": "^1.0" 13 | }, 14 | "provide": { 15 | "psr/http-client-implementation": "1.0" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit" : "^8.5", 19 | "squizlabs/php_codesniffer": "3.*", 20 | "wp-coding-standards/wpcs": "^2.3", 21 | "woocommerce/woocommerce-sniffs": "^0.1", 22 | "object-calisthenics/phpcs-calisthenics-rules": "^3.7", 23 | "phpro/grumphp": "^1.0", 24 | "yoast/phpunit-polyfills": "^1.0", 25 | "phpstan/phpstan": "^1.5", 26 | "php-stubs/woocommerce-stubs": "^6.0", 27 | "slevomat/coding-standard": "^6.4" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "MultiSafepay\\WooCommerce\\": "src/" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "MultiSafepay\\WooCommerce\\Tests\\Fixtures\\": "tests/fixtures/" 37 | } 38 | }, 39 | "scripts": { 40 | "phpcs": "@php vendor/bin/phpcs --standard=phpcs.xml .", 41 | "phpcbf": "@php vendor/bin/phpcbf --standard=phpcs.xml .", 42 | "run-grumphp": "@php vendor/bin/grumphp run --tasks=phpcs,phpunit", 43 | "phpunit": "@php vendor/bin/phpunit", 44 | "phpstan": "@php vendor/bin/phpstan analyse --configuration=tests/phpstan/phpstan.neon --memory-limit 1G --error-format github" 45 | }, 46 | "config": { 47 | "allow-plugins": { 48 | "dealerdirect/phpcodesniffer-composer-installer": true, 49 | "phpro/grumphp": true, 50 | "php-http/discovery": false 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | activate( $network_wide ); 63 | } 64 | register_activation_hook( __FILE__, 'activate_multisafepay' ); 65 | 66 | /** 67 | * Init plugin 68 | * 69 | * @see https://developer.wordpress.org/plugins/hooks/ 70 | * @return void 71 | */ 72 | function init_multisafepay() { 73 | if ( ! function_exists( 'is_plugin_active' ) ) { 74 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 75 | } 76 | if ( is_plugin_active( 'woocommerce/woocommerce.php' ) ) { 77 | $plugin = new Main(); 78 | $plugin->init(); 79 | } 80 | } 81 | 82 | /** 83 | * Wait for WooCommerce to load 84 | * 85 | * @return void 86 | */ 87 | function action_woocommerce_loaded() { 88 | init_multisafepay(); 89 | } 90 | add_action( 'woocommerce_loaded', 'action_woocommerce_loaded', 10, 1 ); 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multisafepay-blocks", 3 | "version": "1.0.0", 4 | "description": "Use the MultiSafepay plugin to accept payments in WooCommerce blocks.", 5 | "main": "index.js", 6 | "keywords": [ 7 | "MultiSafepay" 8 | ], 9 | "author": { 10 | "name": "MultiSafepay", 11 | "homepage": "https://multisafepay.com/" 12 | }, 13 | "license": "GPL-3.0-or-later", 14 | "devDependencies": { 15 | "@wordpress/scripts": "^28.1.0", 16 | "@wordpress/dependency-extraction-webpack-plugin": "^6.1.0" 17 | }, 18 | "scripts": { 19 | "build": "wp-scripts build" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Blocks/BlocksController.php: -------------------------------------------------------------------------------- 1 | register( new BasePaymentMethodBlocks() ); 23 | } 24 | ); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Client/MultiSafepayClient.php: -------------------------------------------------------------------------------- 1 | logger = $logger ?? new Logger(); 27 | } 28 | 29 | /** 30 | * Sends a request using wp_remote_request for the given PSR-7 request (RequestInterface) 31 | * and returns a PSR-7 response (ResponseInterface). 32 | * 33 | * @param RequestInterface $request 34 | * @return ResponseInterface 35 | * @throws Exception 36 | */ 37 | public function sendRequest( RequestInterface $request ): ResponseInterface { 38 | $request->getBody()->rewind(); 39 | $args = $this->get_headers_from_request_interface( $request ); 40 | 41 | try { 42 | $response_data = wp_remote_request( $request->getUri()->__toString(), $args ); 43 | if ( is_wp_error( $response_data ) ) { 44 | throw new Exception( $response_data->get_error_message() ); 45 | } 46 | } catch ( Exception $exception ) { 47 | $this->logger->log_error( 'Error when process request via MultiSafepayClient: ' . $exception->getMessage() ); 48 | throw new Exception( $exception->getMessage() ); 49 | } 50 | 51 | $body = wp_remote_retrieve_body( $response_data ); 52 | $response = new Response( $response_data['response']['code'], $response_data['headers']->getAll(), $body, '1.1', null ); 53 | $response->getBody()->rewind(); 54 | return $response; 55 | } 56 | 57 | /** 58 | * Return an array of headers to be used in wp_remote_request 59 | * 60 | * @param RequestInterface $request 61 | * @return array 62 | */ 63 | private function get_headers_from_request_interface( RequestInterface $request ): array { 64 | $args = array( 65 | 'method' => $request->getMethod(), 66 | 'body' => $request->getBody()->getContents(), 67 | 'httpversion' => $request->getProtocolVersion(), 68 | 'timeout' => 30, 69 | ); 70 | foreach ( $request->getHeaders() as $name => $value ) { 71 | $args['headers'][ $name ] = $value[0]; 72 | } 73 | return $args; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Exceptions/MissingDependencyException.php: -------------------------------------------------------------------------------- 1 | missing_plugin_names = $missing_plugins; 27 | } 28 | 29 | /** 30 | * Get the list of all missing plugins 31 | * 32 | * @return array 33 | */ 34 | public function get_missing_plugin_names(): array { 35 | return $this->missing_plugin_names; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Exceptions/index.php: -------------------------------------------------------------------------------- 1 | brand = $brand; 26 | parent::__construct( $payment_method ); 27 | } 28 | 29 | /** 30 | * @return string 31 | */ 32 | public function get_payment_method_gateway_code(): string { 33 | return $this->payment_method->getId(); 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function get_payment_method_title(): string { 40 | return $this->brand['name']; 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function get_payment_method_icon(): string { 47 | return $this->brand['icon_urls']['large']; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function get_payment_method_id(): string { 54 | return PaymentMethodService::get_legacy_woocommerce_payment_gateway_ids( $this->brand['id'] ); 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/PaymentMethods/Base/BaseGiftCardPaymentMethod.php: -------------------------------------------------------------------------------- 1 | get_option( $this->get_payment_method_id() . '_gift_card_max_amount_updated', false ) ) { 25 | $this->update_option( 'max_amount', '' ); 26 | $this->update_option( $this->get_payment_method_id() . '_gift_card_max_amount_updated', '1' ); 27 | $this->max_amount = ''; 28 | } 29 | } 30 | 31 | /** 32 | * @param WC_Order $order 33 | * @return bool 34 | */ 35 | public function can_refund_order( $order ) { 36 | return false; 37 | } 38 | 39 | /** 40 | * Return if payment component is enabled. 41 | * 42 | * @return bool 43 | */ 44 | public function is_payment_component_enabled(): bool { 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/PaymentMethods/Base/BasePaymentMethodBlocks.php: -------------------------------------------------------------------------------- 1 | get_multisafepay_payment_methods_from_api(); 43 | foreach ( $multisafepay_payment_methods as $multisafepay_payment_method ) { 44 | $woocommerce_payment_gateways = array(); 45 | 46 | if ( isset( $multisafepay_payment_method['type'] ) && ( 'coupon' === $multisafepay_payment_method['type'] ) ) { 47 | $woocommerce_payment_gateways[] = new BaseGiftCardPaymentMethod( new PaymentMethod( $multisafepay_payment_method ) ); 48 | } 49 | 50 | if ( isset( $multisafepay_payment_method['type'] ) && ( 'payment-method' === $multisafepay_payment_method['type'] ) ) { 51 | $woocommerce_payment_gateways[] = new BasePaymentMethod( new PaymentMethod( $multisafepay_payment_method ) ); 52 | foreach ( $multisafepay_payment_method['brands'] as $brand ) { 53 | if ( ! empty( $brand['allowed_countries'] ) ) { 54 | $woocommerce_payment_gateways[] = new BaseBrandedPaymentMethod( new PaymentMethod( $multisafepay_payment_method ), $brand ); 55 | } 56 | } 57 | } 58 | 59 | foreach ( $woocommerce_payment_gateways as $woocommerce_payment_gateway ) { 60 | // Include direct payment methods without components just in the checkout page of the frontend context 61 | if ( 62 | $woocommerce_payment_gateway->check_direct_payment_methods_without_components() && 63 | ! $woocommerce_payment_gateway->admin_editing_checkout_page() 64 | ) { 65 | $this->gateways[] = $woocommerce_payment_gateway; 66 | } 67 | 68 | if ( ( 'redirect' === $woocommerce_payment_gateway->get_payment_method_type() ) && $woocommerce_payment_gateway->is_available() ) { 69 | $this->gateways[] = $woocommerce_payment_gateway; 70 | } 71 | } 72 | } 73 | $was_printed = true; 74 | } 75 | } 76 | 77 | /** 78 | * Returns an array of script handles to enqueue for 79 | * this payment method in the frontend context 80 | * 81 | * @return string[] 82 | */ 83 | public function get_payment_method_script_handles(): array { 84 | static $was_printed = false; 85 | 86 | if ( ! $was_printed ) { 87 | $asset_path = MULTISAFEPAY_PLUGIN_DIR_PATH . '/assets/public/js/multisafepay-blocks/build/index.asset.php'; 88 | $dependencies = array(); 89 | 90 | if ( is_file( $asset_path ) ) { 91 | $asset = require $asset_path; 92 | $dependencies = is_array( $asset ) && isset( $asset['dependencies'] ) ? $asset['dependencies'] : $dependencies; 93 | } 94 | 95 | wp_register_script( 96 | 'multisafepay-payment-methods-blocks', 97 | MULTISAFEPAY_PLUGIN_URL . '/assets/public/js/multisafepay-blocks/build/index.js', 98 | $dependencies, 99 | MULTISAFEPAY_PLUGIN_VERSION, 100 | true 101 | ); 102 | 103 | wp_localize_script( 'multisafepay-payment-methods-blocks', 'multisafepay_gateways', $this->get_payment_method_data() ); 104 | $was_printed = true; 105 | } 106 | 107 | return array( 'multisafepay-payment-methods-blocks' ); 108 | } 109 | 110 | /** 111 | * Returns an array of key=>value pairs of data 112 | * made available to the payment methods script. 113 | * 114 | * @return array 115 | */ 116 | public function get_payment_method_data(): array { 117 | $payment_methods_data = array(); 118 | foreach ( $this->gateways as $gateway ) { 119 | $payment_methods_data[] = array( 120 | 'id' => $gateway->get_payment_method_id(), 121 | 'title' => $gateway->get_title(), 122 | 'description' => $gateway->get_description(), 123 | 'is_admin' => is_admin(), 124 | ); 125 | } 126 | 127 | return $payment_methods_data; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/PaymentMethods/Base/BaseRefunds.php: -------------------------------------------------------------------------------- 1 | get_transaction_manager(); 42 | 43 | /** @var WC_Order $order */ 44 | $order = wc_get_order( $order_id ); 45 | 46 | // Get meta multisafepay_transaction_id 47 | $multisafepay_transaction_id = $order->get_meta( 'multisafepay_transaction_id', true ); 48 | 49 | /** @var TransactionResponse $multisafepay_transaction */ 50 | $multisafepay_transaction = $transaction_manager->get( 51 | ! empty( $multisafepay_transaction_id ) ? $multisafepay_transaction_id : $order->get_order_number() 52 | ); 53 | 54 | if ( $multisafepay_transaction->requiresShoppingCart() ) { 55 | /** @var RefundRequest $refund_request */ 56 | $refund_request = $transaction_manager->createRefundRequest( $multisafepay_transaction ); 57 | 58 | $refunds = $order->get_refunds(); 59 | $refund_merchant_item_id = reset( $refunds )->id; 60 | 61 | $cart_item = new CartItem(); 62 | $cart_item->addName( __( 'Refund', 'multisafepay' ) ) 63 | ->addQuantity( 1 ) 64 | ->addUnitPrice( MoneyUtil::create_money( (float) $amount, $order->get_currency() )->negative() ) 65 | ->addMerchantItemId( 'refund_id_' . $refund_merchant_item_id ) 66 | ->addTaxRate( 0 ); 67 | 68 | $refund_request->getCheckoutData()->addItem( $cart_item ); 69 | } 70 | 71 | if ( ! $multisafepay_transaction->requiresShoppingCart() ) { 72 | $refund_request = new RefundRequest(); 73 | $refund_request->addDescriptionText( $reason ); 74 | $refund_request->addMoney( MoneyUtil::create_money( (float) $amount, $order->get_currency() ) ); 75 | } 76 | 77 | try { 78 | $error = null; 79 | $transaction_manager->refund( $multisafepay_transaction, $refund_request ); 80 | } catch ( Exception | ClientExceptionInterface | ApiException $exception ) { 81 | $error = __( 'Error:', 'multisafepay' ) . htmlspecialchars( $exception->getMessage() ); 82 | $this->logger->log_error( 'Error during refund: ' . $error . ' Refund request : ' . wp_json_encode( $refund_request->getData() ) ); 83 | } 84 | 85 | if ( ! $error ) { 86 | /* translators: %1$: The currency code. %2$ The transaction amount */ 87 | $note = sprintf( __( 'Refund of %1$s%2$s has been processed successfully.', 'multisafepay' ), get_woocommerce_currency_symbol( $order->get_currency() ), $amount ); 88 | $this->logger->log_info( $note ); 89 | $order->add_order_note( $note ); 90 | return true; 91 | } 92 | 93 | if ( get_option( 'multisafepay_debugmode', false ) ) { 94 | /* translators: %1$: The order ID. %2$ The PSP transaction ID */ 95 | $message = sprintf( __( 'Refund for Order ID: %1$s with transactionId: %2$s gives message: %3$s.', 'multisafepay' ), $order_id, $multisafepay_transaction->getTransactionId(), $error ); 96 | $this->logger->log_warning( $message ); 97 | } 98 | 99 | return false; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/PaymentMethods/Base/index.php: -------------------------------------------------------------------------------- 1 | logger = $logger ?? new Logger(); 39 | $this->api_token_manager = ( new SdkService() )->get_api_token_manager(); 40 | } 41 | 42 | /** 43 | * Returns a MultiSafepay ApiToken 44 | * 45 | * @return string 46 | */ 47 | public function get_api_token(): string { 48 | if ( null === $this->api_token_manager ) { 49 | return ''; 50 | } 51 | 52 | $cached_api_token = get_transient( 'multisafepay_api_token' ); 53 | if ( false !== $cached_api_token ) { 54 | return $cached_api_token; 55 | } 56 | 57 | try { 58 | $api_token = $this->api_token_manager->get()->getApiToken(); 59 | } catch ( ApiException | ClientExceptionInterface $exception ) { 60 | $this->logger->log_error( $exception->getMessage() ); 61 | return ''; 62 | } 63 | 64 | set_transient( 'multisafepay_api_token', $api_token, self::EXPIRATION_TIME_FOR_API_TOKEN_REQUEST ); 65 | 66 | return $api_token; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Services/CustomerService.php: -------------------------------------------------------------------------------- 1 | logger = $logger ?? new Logger(); 34 | } 35 | 36 | /** 37 | * @param WC_Order $order 38 | * @return CustomerDetails 39 | */ 40 | public function create_customer_details( WC_Order $order ): CustomerDetails { 41 | $customer_address = $this->create_address( 42 | $order->get_billing_address_1(), 43 | $order->get_billing_address_2(), 44 | $order->get_billing_country(), 45 | $order->get_billing_state(), 46 | $order->get_billing_city(), 47 | $order->get_billing_postcode() 48 | ); 49 | 50 | return $this->create_customer( 51 | $customer_address, 52 | $order->get_billing_email(), 53 | $order->get_billing_phone(), 54 | $order->get_billing_first_name(), 55 | $order->get_billing_last_name(), 56 | $order->get_customer_ip_address() ? $order->get_customer_ip_address() : '', 57 | $order->get_customer_user_agent() ? $order->get_customer_user_agent() : '', 58 | $order->get_billing_company(), 59 | $this->should_send_customer_reference( $order->get_payment_method() ) ? (string) $order->get_customer_id() : null, 60 | $this->get_customer_browser_info() 61 | ); 62 | } 63 | 64 | /** 65 | * Return browser information 66 | * 67 | * @return array|null 68 | */ 69 | protected function get_customer_browser_info(): ?array { 70 | $browser = sanitize_text_field( wp_unslash( $_POST['browser'] ?? '' ) ); 71 | 72 | if ( ! empty( $browser ) ) { 73 | return json_decode( $browser, true ); 74 | } 75 | 76 | return null; 77 | } 78 | 79 | /** 80 | * @param WC_Order $order 81 | * @return CustomerDetails 82 | */ 83 | public function create_delivery_details( WC_Order $order ): CustomerDetails { 84 | $delivery_address = $this->create_address( 85 | $order->get_shipping_address_1(), 86 | $order->get_shipping_address_2(), 87 | $order->get_shipping_country(), 88 | $order->get_shipping_state(), 89 | $order->get_shipping_city(), 90 | $order->get_shipping_postcode() 91 | ); 92 | 93 | return $this->create_customer( 94 | $delivery_address, 95 | $order->get_billing_email(), 96 | $order->get_billing_phone(), 97 | $order->get_shipping_first_name(), 98 | $order->get_shipping_last_name(), 99 | '', 100 | '', 101 | $order->get_shipping_company() 102 | ); 103 | } 104 | 105 | /** 106 | * @param Address $address 107 | * @param string $email_address 108 | * @param string $phone_number 109 | * @param string $first_name 110 | * @param string $last_name 111 | * @param string $ip_address 112 | * @param string $user_agent 113 | * @param null|string $company_name 114 | * @param null|string $customer_id 115 | * @param null|array $browser 116 | * @return CustomerDetails 117 | */ 118 | protected function create_customer( 119 | Address $address, 120 | string $email_address, 121 | string $phone_number, 122 | string $first_name, 123 | string $last_name, 124 | string $ip_address, 125 | string $user_agent, 126 | string $company_name = null, 127 | string $customer_id = null, 128 | ?array $browser = null 129 | ): CustomerDetails { 130 | $customer_details = new CustomerDetails(); 131 | $customer_details 132 | ->addAddress( $address ) 133 | ->addEmailAddress( new EmailAddress( $email_address ) ) 134 | ->addFirstName( $first_name ) 135 | ->addLastName( $last_name ) 136 | ->addPhoneNumber( new PhoneNumber( $phone_number ) ) 137 | ->addLocale( $this->get_locale() ) 138 | ->addCompanyName( $company_name ?? '' ); 139 | 140 | if ( ! empty( $ip_address ) ) { 141 | try { 142 | $customer_details->addIpAddress( new IpAddress( $ip_address ) ); 143 | } catch ( InvalidArgumentException $invalid_argument_exception ) { 144 | $this->logger->log_warning( 'Invalid Customer IP address: ' . $invalid_argument_exception->getMessage() ); 145 | } 146 | } 147 | 148 | if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { 149 | try { 150 | $customer_details->addForwardedIp( new IpAddress( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) ); 151 | } catch ( InvalidArgumentException $invalid_argument_exception ) { 152 | $this->logger->log_warning( 'Invalid Forwarded IP address: ' . $invalid_argument_exception->getMessage() ); 153 | } 154 | } 155 | 156 | if ( ! empty( $user_agent ) ) { 157 | $customer_details->addUserAgent( $user_agent ); 158 | } 159 | 160 | if ( ! empty( $customer_id ) ) { 161 | $customer_details->addReference( $customer_id ); 162 | } 163 | 164 | if ( ! empty( $browser ) ) { 165 | $customer_details->addData( $browser ); 166 | } 167 | 168 | return $customer_details; 169 | } 170 | 171 | /** 172 | * @param string $address_line_1 173 | * @param string $address_line_2 174 | * @param string $country 175 | * @param string $state 176 | * @param string $city 177 | * @param string $zip_code 178 | * @return Address 179 | */ 180 | protected function create_address( 181 | string $address_line_1, 182 | string $address_line_2, 183 | string $country, 184 | string $state, 185 | string $city, 186 | string $zip_code 187 | ): Address { 188 | $address_parser = new AddressParser(); 189 | $address = $address_parser->parse( $address_line_1, $address_line_2 ); 190 | 191 | $street = $address[0]; 192 | $house_number = $address[1]; 193 | 194 | $customer_address = new Address(); 195 | return $customer_address 196 | ->addStreetName( $street ) 197 | ->addHouseNumber( $house_number ) 198 | ->addState( $state ) 199 | ->addCity( $city ) 200 | ->addCountry( new Country( $country ) ) 201 | ->addZipCode( $zip_code ); 202 | } 203 | 204 | /** 205 | * Return customer locale 206 | * 207 | * @return string 208 | */ 209 | public function get_locale(): string { 210 | $locale = get_locale() ?? self::DEFAULT_LOCALE; 211 | return apply_filters( 'multisafepay_customer_locale', $locale ); 212 | } 213 | 214 | /** 215 | * Customer reference only needs to be sent when a payment token is being used, or 216 | * when a payment tokens needs to be created. 217 | * 218 | * @param string $payment_method_id 219 | * @return bool 220 | */ 221 | protected function should_send_customer_reference( string $payment_method_id ): bool { 222 | if ( ! isset( $_POST[ $payment_method_id . '_payment_component_tokenize' ] ) ) { 223 | return false; 224 | } 225 | 226 | if ( ! (bool) $_POST[ $payment_method_id . '_payment_component_tokenize' ] ) { 227 | return false; 228 | } 229 | 230 | return true; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/Services/OrderService.php: -------------------------------------------------------------------------------- 1 | customer_service = new CustomerService(); 43 | $this->shopping_cart_service = new ShoppingCartService(); 44 | $this->payment_method_service = new PaymentMethodService(); 45 | } 46 | 47 | /** 48 | * @param WC_Order $order 49 | * @param string $gateway_code 50 | * @param string $type 51 | * @return OrderRequest 52 | */ 53 | public function create_order_request( WC_Order $order, string $gateway_code, string $type ): OrderRequest { 54 | $order_request = new OrderRequest(); 55 | $order_request 56 | ->addOrderId( $order->get_order_number() ) 57 | ->addMoney( MoneyUtil::create_money( (float) ( $order->get_total() ), $order->get_currency() ) ) 58 | ->addGatewayCode( $gateway_code ) 59 | ->addType( $type ) 60 | ->addPluginDetails( $this->create_plugin_details() ) 61 | ->addDescriptionText( $this->get_order_description_text( $order->get_order_number() ) ) 62 | ->addCustomer( $this->customer_service->create_customer_details( $order ) ) 63 | ->addPaymentOptions( $this->create_payment_options( $order ) ) 64 | ->addSecondsActive( $this->get_seconds_active() ) 65 | ->addSecondChance( ( new SecondChance() )->addSendEmail( (bool) get_option( 'multisafepay_second_chance', false ) ) ) 66 | ->addData( array( 'var2' => $order->get_id() ) ); 67 | 68 | if ( $order->needs_shipping_address() && $order->has_shipping_address() ) { 69 | $order_request->addDelivery( $this->customer_service->create_delivery_details( $order ) ); 70 | } 71 | 72 | if ( ! get_option( 'multisafepay_disable_shopping_cart', false ) || in_array( $gateway_code, GatewaysSdk::SHOPPING_CART_REQUIRED_GATEWAYS, true ) ) { 73 | $order_request->addShoppingCart( $this->shopping_cart_service->create_shopping_cart( $order, $order->get_currency(), $gateway_code ) ); 74 | } 75 | 76 | if ( ! empty( $_POST[ $order->get_payment_method() . '_payment_component_payload' ] ) ) { 77 | $payment_method_id = $order->get_payment_method(); 78 | $payment_component_payload_key = $payment_method_id . '_payment_component_payload'; 79 | $payment_component_payload = sanitize_text_field( wp_unslash( $_POST[ $payment_component_payload_key ] ?? '' ) ); 80 | if ( ! empty( $payment_component_payload ) ) { 81 | $order_request->addType( 'direct' ); 82 | $order_request->addData( 83 | array( 84 | 'payment_data' => array( 85 | 'payload' => $payment_component_payload, 86 | ), 87 | ) 88 | ); 89 | } 90 | } 91 | 92 | $payment_token = sanitize_text_field( wp_unslash( $_POST['payment_token'] ?? '' ) ); 93 | if ( ! empty( $payment_token ) && ( ( 'APPLEPAY' === $gateway_code ) || ( 'GOOGLEPAY' === $gateway_code ) ) ) { 94 | $order_request->addType( 'direct' ); 95 | $order_request->addGatewayInfo( ( new Wallet() )->addPaymentToken( $payment_token ) ); 96 | } 97 | 98 | $order_request = $this->add_none_tax_rate( $order_request ); 99 | 100 | return apply_filters( 'multisafepay_order_request', $order_request ); 101 | } 102 | 103 | /** 104 | * @return PluginDetails 105 | */ 106 | protected function create_plugin_details(): PluginDetails { 107 | $plugin_details = new PluginDetails(); 108 | global $wp_version; 109 | return $plugin_details 110 | ->addApplicationName( 'Wordpress-WooCommerce' ) 111 | ->addApplicationVersion( 'WordPress version: ' . $wp_version . '. WooCommerce version: ' . WC_VERSION ) 112 | ->addPluginVersion( MULTISAFEPAY_PLUGIN_VERSION ) 113 | ->addShopRootUrl( get_bloginfo( 'url' ) ); 114 | } 115 | 116 | /** 117 | * @param WC_Order $order 118 | * @return PaymentOptions 119 | */ 120 | private function create_payment_options( WC_Order $order ): PaymentOptions { 121 | $payment_options = new PaymentOptions(); 122 | $payment_options->addNotificationUrl( get_rest_url( get_current_blog_id(), 'multisafepay/v1/notification' ) ); 123 | 124 | $cancel_endpoint = ( get_option( 'multisafepay_redirect_after_cancel', 'cart' ) === 'cart' ? '' : wc_get_checkout_url() ); 125 | $cancel_url = wp_specialchars_decode( $order->get_cancel_order_url( $cancel_endpoint ) ); 126 | 127 | if ( is_wc_endpoint_url( 'order-pay' ) ) { 128 | $cancel_url = wp_specialchars_decode( $order->get_checkout_payment_url() ); 129 | } 130 | 131 | $payment_options->addCancelUrl( $cancel_url ); 132 | $payment_options->addRedirectUrl( $order->get_checkout_order_received_url() ); 133 | if ( ! apply_filters( 'multisafepay_post_notification', true ) ) { 134 | $payment_options->addNotificationUrl( add_query_arg( 'wc-api', 'multisafepay', home_url( '/' ) ) ); 135 | $payment_options->addNotificationMethod( 'GET' ); 136 | } 137 | return $payment_options; 138 | } 139 | 140 | /** 141 | * Return the order description. 142 | * 143 | * @param string $order_number 144 | * @return string $order_description 145 | */ 146 | protected function get_order_description_text( $order_number ): string { 147 | /* translators: %s: order id */ 148 | $order_description = sprintf( __( 'Payment for order: %s', 'multisafepay' ), $order_number ); 149 | if ( get_option( 'multisafepay_order_request_description', false ) ) { 150 | $order_description = str_replace( '{order_number}', $order_number, get_option( 'multisafepay_order_request_description', false ) ); 151 | } 152 | return $order_description; 153 | } 154 | 155 | /** 156 | * Return the time active in seconds defined in the plugin settings page 157 | * 158 | * @return int 159 | */ 160 | protected function get_seconds_active(): int { 161 | $time_active = get_option( 'multisafepay_time_active', '30' ); 162 | $time_active_unit = get_option( 'multisafepay_time_unit', 'days' ); 163 | if ( 'days' === $time_active_unit ) { 164 | $time_active = $time_active * 24 * 60 * 60; 165 | } 166 | if ( 'hours' === $time_active_unit ) { 167 | $time_active = $time_active * 60 * 60; 168 | } 169 | return $time_active; 170 | } 171 | 172 | /** 173 | * This method add a tax rate of 0, in case is not being created automatically by the shopping cart. 174 | * This is required to process refunds, based on shopping cart items 175 | * 176 | * @param OrderRequest $order_request 177 | * @return OrderRequest 178 | */ 179 | public function add_none_tax_rate( OrderRequest $order_request ): OrderRequest { 180 | if ( $order_request->getShoppingCart() === null ) { 181 | return $order_request; 182 | } 183 | if ( $order_request->getCheckoutOptions()->getTaxTable() === null ) { 184 | return $order_request; 185 | } 186 | $shopping_cart = $order_request->getShoppingCart()->getData(); 187 | if ( isset( $shopping_cart['items'] ) ) { 188 | foreach ( $shopping_cart['items'] as $item ) { 189 | if ( '0' === $item['tax_table_selector'] ) { 190 | return $order_request; 191 | } 192 | } 193 | } 194 | $tax_rate = ( new TaxRate() )->addRate( 0 ); 195 | $tax_rule = ( new TaxRule() )->addTaxRate( $tax_rate )->addName( '0' ); 196 | $order_request->getCheckoutOptions()->getTaxTable()->addTaxRule( $tax_rule ); 197 | return $order_request; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/Services/PaymentComponentService.php: -------------------------------------------------------------------------------- 1 | sdk_service = new SdkService(); 35 | $this->api_token_service = new ApiTokenService(); 36 | $this->payment_method_service = new PaymentMethodService(); 37 | } 38 | 39 | /** 40 | * Return the arguments required when payment component needs to be initialized 41 | * 42 | * @param BasePaymentMethod $woocommerce_payment_gateway 43 | * @param bool $validate_checkout 44 | * @return array 45 | */ 46 | public function get_payment_component_arguments( BasePaymentMethod $woocommerce_payment_gateway, bool $validate_checkout = false ): array { 47 | $payment_component_arguments = array( 48 | 'debug' => (bool) get_option( 'multisafepay_debugmode', false ), 49 | 'env' => $this->sdk_service->get_test_mode() ? 'test' : 'live', 50 | 'ajax_url' => admin_url( 'admin-ajax.php' ), 51 | 'nonce' => wp_create_nonce( 'payment_component_arguments_nonce' ), 52 | 'api_token' => $this->api_token_service->get_api_token(), 53 | 'orderData' => array( 54 | 'currency' => get_woocommerce_currency(), 55 | 'amount' => ( $this->get_total_amount() * 100 ), 56 | 'customer' => array( 57 | 'locale' => strtoupper( substr( ( new CustomerService() )->get_locale(), 0, 2 ) ), 58 | 'country' => ( WC()->customer )->get_billing_country(), 59 | ), 60 | 'payment_options' => array( 61 | 'template' => array( 62 | 'settings' => array( 63 | 'embed_mode' => 1, 64 | ), 65 | 'merge' => true, 66 | ), 67 | 'settings' => array( 68 | 'connect' => array( 69 | 'issuers_display_mode' => 'select', 70 | ), 71 | ), 72 | ), 73 | ), 74 | 'gateway' => $woocommerce_payment_gateway->get_payment_method_gateway_code(), 75 | 'qr_supported' => $woocommerce_payment_gateway->is_qr_enabled() || $woocommerce_payment_gateway->is_qr_only_enabled(), 76 | ); 77 | 78 | // Payment Component Template ID. 79 | $template_id = get_option( 'multisafepay_payment_component_template_id', false ); 80 | if ( ! empty( $template_id ) ) { 81 | $payment_component_arguments['orderData']['payment_options']['template_id'] = $template_id; 82 | } 83 | 84 | // Tokenization and recurring model 85 | if ( $woocommerce_payment_gateway->is_tokenization_enabled() && is_user_logged_in() ) { 86 | $payment_component_arguments['recurring'] = array( 87 | 'model' => 'cardOnFile', 88 | 'tokens' => $this->sdk_service->get_payment_tokens( 89 | (string) get_current_user_id(), 90 | sanitize_text_field( $woocommerce_payment_gateway->get_payment_method_gateway_code() ) 91 | ), 92 | ); 93 | } 94 | 95 | // Payment Component QR 96 | if ( $validate_checkout ) { 97 | $qr_checkout_manager = new QrCheckoutManager(); 98 | if ( $qr_checkout_manager->validate_checkout_fields() ) { 99 | if ( $woocommerce_payment_gateway->is_qr_enabled() ) { 100 | $payment_component_arguments['orderData']['payment_options']['settings']['connect']['qr'] = array( 'enabled' => 1 ); 101 | } 102 | if ( $woocommerce_payment_gateway->is_qr_only_enabled() ) { 103 | $payment_component_arguments['orderData']['payment_options']['settings']['connect']['qr'] = array( 104 | 'enabled' => 1, 105 | 'qr_only' => 1, 106 | ); 107 | } 108 | } 109 | } 110 | 111 | return $payment_component_arguments; 112 | } 113 | 114 | /** 115 | * Return the arguments required when payment component needs to be initialized via a WP AJAX request 116 | * 117 | * @return void 118 | */ 119 | public function refresh_payment_component_config() { 120 | $payment_component_arguments_nonce = sanitize_key( $_POST['nonce'] ?? '' ); 121 | if ( ! wp_verify_nonce( wp_unslash( $payment_component_arguments_nonce ), 'payment_component_arguments_nonce' ) ) { 122 | wp_send_json( array() ); 123 | } 124 | $gateway_id = sanitize_key( $_POST['gateway_id'] ?? '' ); 125 | $woocommerce_payment_gateway = $this->payment_method_service->get_woocommerce_payment_gateway_by_id( $gateway_id ); 126 | $validate_checkout_fields = ( $woocommerce_payment_gateway->is_payment_component_enabled() && $woocommerce_payment_gateway->is_qr_enabled() || $woocommerce_payment_gateway->is_qr_only_enabled() ); 127 | $payment_component_arguments = $this->get_payment_component_arguments( $woocommerce_payment_gateway, $validate_checkout_fields ); 128 | wp_send_json( $payment_component_arguments ); 129 | } 130 | 131 | /** 132 | * Return the total amount of the cart or order 133 | * 134 | * @return float 135 | */ 136 | private function get_total_amount(): float { 137 | $total_amount = ( WC()->cart ) ? (float) WC()->cart->get_total( '' ) : null; 138 | 139 | if ( is_wc_endpoint_url( 'order-pay' ) ) { 140 | $order_id = absint( get_query_var( 'order-pay' ) ); 141 | if ( 0 < $order_id ) { 142 | $order = wc_get_order( $order_id ); 143 | if ( $order ) { 144 | $total_amount = (float) $order->get_total(); 145 | } 146 | } 147 | } 148 | 149 | return $total_amount; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Services/Qr/QrCustomerService.php: -------------------------------------------------------------------------------- 1 | create_address( 31 | $customer[ $type ]['address_1'], 32 | $customer[ $type ]['address_2'], 33 | $customer[ $type ]['country'], 34 | $customer[ $type ]['state'] ?? '', 35 | $customer[ $type ]['city'] ?? '', 36 | $customer[ $type ]['postcode'] 37 | ); 38 | 39 | return $this->create_customer( 40 | $customer_address, 41 | $customer[ $type ]['email'] ?? $customer['billing']['email'], 42 | $customer[ $type ]['phone'] ?? $customer['billing']['phone'], 43 | $customer[ $type ]['first_name'] ?? $customer['billing']['first_name'], 44 | $customer[ $type ]['last_name'] ?? $customer['billing']['last_name'], 45 | ( new QrOrder() )->get_customer_ip_address() ?? '', 46 | ( new QrOrder() )->get_user_agent() ?? '', 47 | $customer[ $type ]['company'] ?? $customer['billing']['company'], 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Services/Qr/QrOrderService.php: -------------------------------------------------------------------------------- 1 | qr_customer_service = new QrCustomerService(); 51 | $this->qr_shopping_cart_service = new QrShoppingCartService(); 52 | $this->logger = new Logger(); 53 | } 54 | 55 | /** 56 | * @param string $order_id 57 | * @param string $gateway_code 58 | * @param string $payload 59 | * @param array $checkout_fields 60 | * @return OrderRequest 61 | * @throws InvalidArgumentException 62 | */ 63 | public function get_order_request( 64 | string $order_id, 65 | string $gateway_code, 66 | string $payload, 67 | array $checkout_fields 68 | ): OrderRequest { 69 | $cart = WC()->cart; 70 | $order_request = new OrderRequest(); 71 | $order_request 72 | ->addOrderId( $order_id ) 73 | ->addMoney( MoneyUtil::create_money( (float) $cart->get_total( 'edit' ), get_woocommerce_currency() ) ) 74 | ->addGatewayCode( $gateway_code ) 75 | ->addType( OrderRequest::DIRECT_TYPE ) 76 | ->addPluginDetails( $this->create_plugin_details() ) 77 | ->addDescriptionText( $this->get_order_description_text( $order_id ) ) 78 | ->addCustomer( $this->qr_customer_service->create_customer_details_from_cart( $checkout_fields['customer'] ) ) 79 | ->addPaymentOptions( $this->create_payment_options( $this->generate_token( $order_id ) ) ) 80 | ->addSecondsActive( $this->get_seconds_active() ) 81 | ->addSecondChance( ( new SecondChance() )->addSendEmail( (bool) get_option( 'multisafepay_second_chance', false ) ) ) 82 | ->addData( array( 'var2' => $order_id ) ); 83 | 84 | if ( $this->is_filled_shipping_address( $checkout_fields ) && WC()->cart->needs_shipping() ) { 85 | $order_request->addDelivery( $this->qr_customer_service->create_customer_details_from_cart( $checkout_fields['customer'], 'shipping' ) ); 86 | } 87 | 88 | if ( ! get_option( 'multisafepay_disable_shopping_cart', false ) || in_array( $gateway_code, GatewaysSdk::SHOPPING_CART_REQUIRED_GATEWAYS, true ) ) { 89 | $order_request->addShoppingCart( $this->qr_shopping_cart_service->create_shopping_cart( $cart, get_woocommerce_currency() ) ); 90 | } 91 | 92 | if ( ! empty( $payload ) ) { 93 | $order_request->addData( 94 | array( 95 | 'payment_data' => array( 96 | 'gateway' => $gateway_code, 97 | 'payload' => $payload, 98 | ), 99 | ) 100 | ); 101 | } 102 | 103 | $order_request = $this->add_none_tax_rate( $order_request ); 104 | 105 | return apply_filters( 'multisafepay_order_request', $order_request ); 106 | } 107 | 108 | /** 109 | * Check if the shipping address is filled 110 | * 111 | * @param array $checkout_fields 112 | * @return bool 113 | */ 114 | public function is_filled_shipping_address( array $checkout_fields ): bool { 115 | $filled_fields = $checkout_fields['customer']['shipping'] ?? array(); 116 | return ! empty( $filled_fields['address_1'] ) || ! empty( $filled_fields['address_2'] ); 117 | } 118 | 119 | /** 120 | * @param string $token 121 | * @return PaymentOptions 122 | */ 123 | public function create_payment_options( string $token ): PaymentOptions { 124 | $redirect_cancel_url = add_query_arg( 'token', $token, get_rest_url( get_current_blog_id(), 'multisafepay/v1/qr-balancer' ) ); 125 | $payment_options = new PaymentOptions(); 126 | $payment_options->addNotificationUrl( get_rest_url( get_current_blog_id(), 'multisafepay/v1/qr-notification' ) ); 127 | $payment_options->addCancelUrl( $redirect_cancel_url ); 128 | $payment_options->addRedirectUrl( $redirect_cancel_url ); 129 | $payment_options->addSettings( array( 'qr' => array( 'enabled' => true ) ) ); 130 | 131 | return $payment_options; 132 | } 133 | 134 | /** 135 | * Generate a token that will be used on validation of QR related endpoints 136 | * 137 | * @param string $order_id 138 | * @return string 139 | */ 140 | public function generate_token( string $order_id ) { 141 | $token = wp_generate_password( 32, false ); 142 | set_transient( 'multisafepay_token_' . $order_id, $token, 86400 ); 143 | return $token; 144 | } 145 | 146 | /** 147 | * Check if the QR data is valid 148 | * 149 | * @param array $qr_data 150 | * @param string $order_id 151 | * @return bool 152 | */ 153 | public function is_valid_qr_data( array $qr_data, string $order_id ): bool { 154 | $required_fields = array( 155 | 'image' => $qr_data['qr']['image'] ?? null, 156 | 'token' => $qr_data['qr']['params']['token'] ?? null, 157 | 'order_id' => $qr_data['order_id'] ?? null, 158 | ); 159 | 160 | foreach ( $required_fields as $field => $value ) { 161 | if ( empty( $value ) || ( ( 'order_id' === $field ) && ( (string) $value !== $order_id ) ) ) { 162 | return false; 163 | } 164 | } 165 | 166 | return true; 167 | } 168 | 169 | /** 170 | * Generate a unique order ID for cart-based transactions 171 | * 172 | * @return string 173 | */ 174 | public function generate_unique_order_id(): string { 175 | return 'QR-' . uniqid( '', true ); 176 | } 177 | 178 | /** 179 | * Activate the QR code for the order 180 | * 181 | * @param BasePaymentMethod $payment_gateway 182 | * @param string $payload 183 | * @param array $checkout_fields 184 | * @return array 185 | * @throws InvalidArgumentException 186 | */ 187 | public function place_order( BasePaymentMethod $payment_gateway, string $payload, array $checkout_fields ): array { 188 | $order_id = $this->generate_unique_order_id(); 189 | 190 | // Create a transient using order ID and the checkout fields 191 | set_transient( 'multisafepay_qr_order_' . $order_id, $checkout_fields, self::EXPIRATION_TIME_CHECKOUT_QR_DATA ); 192 | 193 | $order_request = $this->get_order_request( 194 | $order_id, 195 | $payment_gateway->get_payment_method_gateway_code(), 196 | $payload, 197 | $checkout_fields 198 | ); 199 | 200 | $sdk = new SdkService(); 201 | $transaction_manager = $sdk->get_transaction_manager(); 202 | 203 | try { 204 | $transaction = $transaction_manager->create( $order_request ); 205 | } catch ( ApiException | ClientExceptionInterface $exception ) { 206 | $this->logger->log_error( $exception->getMessage() ); 207 | wc_add_notice( __( 'There was a problem processing your payment using a QR code. Please try again later or contact with us.', 'multisafepay' ), 'error' ); 208 | return array(); 209 | } 210 | 211 | $payment_data = $transaction->getData(); 212 | 213 | $this->logger->log_info( 'Start MultiSafepay transaction for the order ID ' . $order_id . ' on ' . date( 'd/m/Y H:i:s' ) . ' with payment data ' . wp_json_encode( $payment_data ) ); 214 | 215 | if ( $this->is_valid_qr_data( $payment_data, $order_id ) ) { 216 | return $payment_data; 217 | } 218 | 219 | $this->logger->log_error( 'QR code was not correct' ); 220 | 221 | return array(); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Services/Qr/QrPaymentComponentService.php: -------------------------------------------------------------------------------- 1 | payment_method_service = new PaymentMethodService(); 34 | $this->logger = new Logger(); 35 | } 36 | 37 | /** 38 | * Place a MultiSafepay transaction with QR code 39 | * 40 | * @return void 41 | */ 42 | public function set_multisafepay_qr_code_transaction(): void { 43 | $payment_component_arguments_nonce = sanitize_key( $_POST['nonce'] ?? '' ); 44 | if ( ! wp_verify_nonce( wp_unslash( $payment_component_arguments_nonce ), 'payment_component_arguments_nonce' ) ) { 45 | wp_send_json( array() ); 46 | } 47 | $gateway_id = sanitize_key( $_POST['gateway_id'] ?? '' ); 48 | $payload = sanitize_text_field( wp_unslash( $_POST['payload'] ?? '' ) ); 49 | 50 | if ( empty( $gateway_id ) || empty( $payload ) ) { 51 | return; 52 | } 53 | $woocommerce_payment_gateway = $this->payment_method_service->get_woocommerce_payment_gateway_by_id( $gateway_id ); 54 | if ( ! is_null( $woocommerce_payment_gateway ) && 55 | ( $woocommerce_payment_gateway->is_qr_enabled() || $woocommerce_payment_gateway->is_qr_only_enabled() ) 56 | ) { 57 | try { 58 | $qr_checkout_manager = new QrCheckoutManager(); 59 | $checkout_fields = $qr_checkout_manager->get_checkout_data(); 60 | $multisafepay_qr_code_transaction_data = array(); 61 | 62 | if ( $qr_checkout_manager->validate_checkout_fields() ) { 63 | $shopping_cart_order_service = new QrOrderService(); 64 | $multisafepay_qr_code_transaction_data = $shopping_cart_order_service->place_order( $woocommerce_payment_gateway, $payload, $checkout_fields ); 65 | } 66 | 67 | wp_send_json( $multisafepay_qr_code_transaction_data ); 68 | 69 | } catch ( Exception | InvalidArgumentException $exception ) { 70 | $this->logger->log_error( 'Arguments for QR could not be collected: ' . $exception->getMessage() ); 71 | } 72 | } 73 | } 74 | 75 | 76 | /** 77 | * Return redirect URL where meta value key is 'multisafepay_transaction_id' and value is $order_id 78 | * 79 | * @return void 80 | */ 81 | public function get_qr_order_redirect_url(): void { 82 | $payment_component_arguments_nonce = sanitize_key( $_POST['nonce'] ?? '' ); 83 | if ( ! wp_verify_nonce( wp_unslash( $payment_component_arguments_nonce ), 'payment_component_arguments_nonce' ) ) { 84 | wp_send_json( array() ); 85 | } 86 | 87 | $order_id = sanitize_text_field( wp_unslash( $_POST['order_id'] ?? '' ) ); 88 | 89 | for ( $count = 0; $count < 5; $count++ ) { 90 | // Get WooCommerce Order ID where meta value key is 'multisafepay_transaction_id' and value is $order_id 91 | $woocommerce_order_id = Order::get_order_id_by_multisafepay_transaction_id_key( $order_id ); 92 | 93 | if ( ! $woocommerce_order_id ) { 94 | sleep( 2 ); 95 | continue; 96 | } 97 | 98 | $woocommerce_order = wc_get_order( $woocommerce_order_id ); 99 | 100 | wp_send_json( 101 | array( 102 | 'success' => true, 103 | 'redirect_url' => $woocommerce_order->get_checkout_order_received_url(), 104 | ) 105 | ); 106 | } 107 | 108 | wp_send_json( 109 | array( 110 | 'success' => true, 111 | 'redirect_url' => ( get_option( 'multisafepay_redirect_after_cancel', 'cart' ) === 'cart' ) ? 112 | wc_get_cart_url() : 113 | wc_get_checkout_url(), 114 | ) 115 | ); 116 | 117 | exit(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Services/Qr/QrShoppingCartService.php: -------------------------------------------------------------------------------- 1 | logger = $logger ?? new Logger(); 33 | } 34 | 35 | /** 36 | * Create a shopping cart from a WooCommerce cart 37 | * 38 | * @param WC_Cart $cart 39 | * @param string $currency 40 | * @return ShoppingCart 41 | * @throws InvalidArgumentException 42 | */ 43 | public function create_shopping_cart( WC_Cart $cart, string $currency ): ShoppingCart { 44 | // Recalculate totals is needed, to ensure all discounts, fees, etc are applied 45 | $cart->calculate_totals(); 46 | 47 | // Is vat exempt 48 | $is_vat_exempt = $this->is_order_vat_exempt( $cart ); 49 | 50 | $cart_items = array(); 51 | 52 | foreach ( $cart->get_cart() as $cart_item ) { 53 | $cart_items[] = $this->create_cart_item( $cart_item, $currency, $is_vat_exempt ); 54 | } 55 | 56 | // Add shipping as an item if applicable 57 | if ( $cart->needs_shipping() ) { 58 | $cart_items[] = $this->create_shipping_cart_item( $cart, $currency, $is_vat_exempt ); 59 | } 60 | 61 | // Add fees as items if applicable 62 | foreach ( $cart->get_fees() as $fee ) { 63 | $cart_items[] = $this->create_fee_cart_item( $fee, $currency, $is_vat_exempt ); 64 | } 65 | 66 | // Add coupons as items if applicable 67 | foreach ( $cart->get_coupons() as $coupon ) { 68 | if ( 69 | ( get_option( 'woocommerce_smart_coupon_apply_before_tax', 'no' ) !== 'yes' ) && 70 | in_array( $coupon->get_discount_type(), $this->get_types_of_coupons_not_applied_at_item_level(), true ) 71 | ) { 72 | $cart_items[] = $this->create_coupon_cart_item( $coupon, $currency ); 73 | } 74 | } 75 | 76 | $shopping_cart = new ShoppingCart( $cart_items ); 77 | 78 | $this->logger->log_info( wp_json_encode( $shopping_cart->getData() ) ); 79 | 80 | return $shopping_cart; 81 | } 82 | 83 | /** 84 | * Create a cart item from the WooCommerce shopping cart item 85 | * 86 | * @param array $cart_item 87 | * @param string $currency 88 | * @param bool $is_vat_exempt 89 | * @return CartItem 90 | * @throws InvalidArgumentException 91 | */ 92 | private function create_cart_item( array $cart_item, string $currency, bool $is_vat_exempt = false ): CartItem { 93 | $product = $cart_item['data']; 94 | $item = new CartItem(); 95 | $product_name = $product->get_name(); 96 | $product_price = (float) $cart_item['line_subtotal'] / $cart_item['quantity']; 97 | 98 | // If product price without discount get_subtotal() is not the same as product price with discount 99 | // Then a percentage coupon has been applied to this item 100 | if ( (float) $cart_item['line_subtotal'] !== (float) $cart_item['line_total'] ) { 101 | $discount = (float) $cart_item['line_subtotal'] - (float) $cart_item['line_total']; 102 | // translators: %1$s is the discount amount, %2$s is the currency 103 | $product_name .= sprintf( __( ' - Coupon applied: - %1$s %2$s', 'multisafepay' ), number_format( $discount, 2, '.', '' ), $currency ); 104 | $product_price = (float) $cart_item['line_total'] / (int) $cart_item['quantity']; 105 | } 106 | 107 | return $item->addName( $product_name ) 108 | ->addQuantity( (int) $cart_item['quantity'] ) 109 | ->addMerchantItemId( $product->get_sku() ? (string) $product->get_sku() : (string) $product->get_id() ) 110 | ->addUnitPrice( MoneyUtil::create_money( $product_price, $currency ) ) 111 | ->addTaxRate( $this->get_item_tax_rate_from_cart( $cart_item, $is_vat_exempt ) ); 112 | } 113 | 114 | /** 115 | * Get the tax rate for a cart item 116 | * 117 | * @param array $cart_item 118 | * @param bool $is_vat_exempt 119 | * @return float 120 | */ 121 | private function get_item_tax_rate_from_cart( array $cart_item, bool $is_vat_exempt = false ): float { 122 | if ( ! wc_tax_enabled() ) { 123 | return 0; 124 | } 125 | 126 | if ( $is_vat_exempt ) { 127 | return 0.00; 128 | } 129 | 130 | $product = $cart_item['data']; 131 | 132 | if ( 'taxable' !== $product->get_tax_status() ) { 133 | return 0; 134 | } 135 | 136 | $tax_class = $product->get_tax_class(); 137 | $tax_rates = WC_Tax::get_rates( $tax_class ); 138 | 139 | switch ( count( $tax_rates ) ) { 140 | case 0: 141 | $tax_rate = 0; 142 | break; 143 | case 1: 144 | $tax = reset( $tax_rates ); 145 | $tax_rate = $tax['rate']; 146 | break; 147 | default: 148 | $price_including_tax = wc_get_price_including_tax( $product ); 149 | $price_excluding_tax = wc_get_price_excluding_tax( $product ); 150 | $tax_rate = ( ( $price_including_tax / $price_excluding_tax ) - 1 ) * 100; 151 | break; 152 | } 153 | 154 | return $tax_rate; 155 | } 156 | 157 | /** 158 | * Create a shipping item from the WooCommerce shopping cart 159 | * 160 | * @param WC_Cart $cart 161 | * @param string $currency 162 | * @param bool $is_vat_exempt 163 | * @return ShippingItem 164 | */ 165 | public function create_shipping_cart_item( WC_Cart $cart, string $currency, bool $is_vat_exempt = false ): ShippingItem { 166 | $shipping_item = new ShippingItem(); 167 | return $shipping_item->addName( __( 'Shipping', 'multisafepay' ) ) 168 | ->addQuantity( 1 ) 169 | ->addUnitPrice( MoneyUtil::create_money( (float) $cart->get_shipping_total(), $currency ) ) 170 | ->addMerchantItemId( 'msp-shipping' ) 171 | ->addTaxRate( $this->get_shipping_tax_rate( $cart, $is_vat_exempt ) ); 172 | } 173 | 174 | /** 175 | * Get the tax rate for shipping 176 | * 177 | * @param WC_Cart $cart 178 | * @param bool $is_vat_exempt 179 | * @return float 180 | */ 181 | private function get_shipping_tax_rate( WC_Cart $cart, bool $is_vat_exempt = false ): float { 182 | if ( ! wc_tax_enabled() ) { 183 | return 0; 184 | } 185 | 186 | if ( $is_vat_exempt ) { 187 | return 0.00; 188 | } 189 | 190 | $shipping_total = $cart->get_shipping_total(); 191 | $shipping_taxes = $cart->get_shipping_taxes(); 192 | 193 | if ( empty( $shipping_taxes ) || ( 0.00 === $shipping_total ) ) { 194 | return 0; 195 | } 196 | 197 | $total_tax = array_sum( $shipping_taxes ); 198 | 199 | return ( (float) $total_tax * 100 ) / (float) $shipping_total; 200 | } 201 | 202 | /** 203 | * Create a fee item from the WooCommerce shopping cart 204 | * 205 | * @param stdClass $fee 206 | * @param string $currency 207 | * @param bool $is_vat_exempt 208 | * @return CartItem 209 | */ 210 | private function create_fee_cart_item( stdClass $fee, string $currency, bool $is_vat_exempt = false ): CartItem { 211 | $fee_item = new CartItem(); 212 | return $fee_item->addName( $fee->name ) 213 | ->addQuantity( 1 ) 214 | ->addMerchantItemId( (string) $fee->id ) 215 | ->addUnitPrice( MoneyUtil::create_money( (float) $fee->amount, $currency ) ) 216 | ->addTaxRate( $this->get_fee_tax_rate( $fee, $is_vat_exempt ) ); 217 | } 218 | 219 | /** 220 | * Get the tax rate for a fee item from the cart 221 | * 222 | * @param stdClass $fee 223 | * @param bool $is_vat_exempt 224 | * @return float 225 | */ 226 | private function get_fee_tax_rate( stdClass $fee, bool $is_vat_exempt = false ): float { 227 | if ( ! wc_tax_enabled() ) { 228 | return 0.00; 229 | } 230 | 231 | if ( $is_vat_exempt ) { 232 | return 0.00; 233 | } 234 | 235 | if ( 0.00 === (float) $fee->amount ) { 236 | return 0.00; 237 | } 238 | 239 | if ( false === $fee->taxable ) { 240 | return 0.00; 241 | } 242 | 243 | if ( empty( $fee->total ) ) { 244 | return 0.00; 245 | } 246 | 247 | $total_tax = array_sum( $fee->tax_data ); 248 | 249 | return ( (float) $total_tax * 100 ) / (float) $fee->total; 250 | } 251 | 252 | /** 253 | * Create a coupon item from the WooCommerce shopping cart 254 | * 255 | * @param WC_Coupon $coupon 256 | * @param string $currency 257 | * @return CartItem 258 | * @throws InvalidArgumentException 259 | */ 260 | private function create_coupon_cart_item( WC_Coupon $coupon, string $currency ): CartItem { 261 | $coupon_item = new CartItem(); 262 | return $coupon_item->addName( $coupon->get_code() ) 263 | ->addQuantity( 1 ) 264 | ->addMerchantItemId( (string) $coupon->get_id() ) 265 | ->addUnitPrice( MoneyUtil::create_money( (float) -$coupon->get_amount(), $currency ) ) 266 | ->addTaxRate( 0 ); 267 | } 268 | 269 | /** 270 | * Retrieve the types of coupons that are not applied at item level 271 | * 272 | * @return array 273 | */ 274 | public function get_types_of_coupons_not_applied_at_item_level(): array { 275 | return apply_filters( 'multisafepay_types_of_coupons_not_applied_at_item_level', array( 'smart_coupon' ) ); 276 | } 277 | 278 | /** 279 | * Returns if order is VAT exempt via WC_Cart->get_customer()->is_vat_exempt 280 | * 281 | * @param WC_Cart $cart 282 | * @return bool 283 | */ 284 | public function is_order_vat_exempt( WC_Cart $cart ): bool { 285 | $customer = $cart->get_customer(); 286 | 287 | if ( null === $customer ) { 288 | return false; 289 | } 290 | 291 | return $customer->is_vat_exempt(); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/Services/SdkService.php: -------------------------------------------------------------------------------- 1 | api_key = $api_key ?? $this->get_api_key(); 59 | $this->test_mode = $test_mode ?? $this->get_test_mode(); 60 | $this->logger = $logger ?? new Logger(); 61 | $psr_factory = new Psr17Factory(); 62 | $client = new MultiSafepayClient(); 63 | try { 64 | $this->sdk = new Sdk( $this->api_key, ( $this->test_mode ) ? false : true, $client, $psr_factory, $psr_factory ); 65 | } catch ( InvalidApiKeyException $invalid_api_key_exception ) { 66 | set_transient( 'multisafepay_payment_methods', array() ); 67 | $this->logger->log_error( $invalid_api_key_exception->getMessage() ); 68 | } 69 | } 70 | 71 | /** 72 | * Returns if test mode is enable 73 | * 74 | * @return boolean 75 | */ 76 | public function get_test_mode(): bool { 77 | return (bool) get_option( 'multisafepay_testmode', false ); 78 | } 79 | 80 | /** 81 | * Returns api key set in settings page according with 82 | * the environment selected 83 | * 84 | * @return string 85 | */ 86 | public function get_api_key(): string { 87 | if ( $this->get_test_mode() ) { 88 | return get_option( 'multisafepay_test_api_key', '' ); 89 | } 90 | return get_option( 'multisafepay_api_key', '' ); 91 | } 92 | 93 | 94 | /** 95 | * Returns gateway manager 96 | * 97 | * @return GatewayManager|WP_Error 98 | */ 99 | public function get_gateway_manager() { 100 | try { 101 | return $this->sdk->getGatewayManager(); 102 | } catch ( ApiException $api_exception ) { 103 | $this->logger->log_error( $api_exception->getMessage() ); 104 | return new WP_Error( 'multisafepay-warning', $api_exception->getMessage() ); 105 | } 106 | } 107 | 108 | 109 | /** 110 | * Returns an array of the gateways available on the merchant account 111 | * 112 | * @return Gateway[]|WP_Error 113 | */ 114 | public function get_gateways() { 115 | try { 116 | return $this->get_gateway_manager()->getGateways( true ); 117 | } catch ( ApiException $api_exception ) { 118 | $this->logger->log_error( $api_exception->getMessage() ); 119 | return new WP_Error( 'multisafepay-warning', $api_exception->getMessage() ); 120 | } 121 | } 122 | 123 | /** 124 | * Returns transaction manager 125 | * 126 | * @return TransactionManager 127 | */ 128 | public function get_transaction_manager(): TransactionManager { 129 | return $this->sdk->getTransactionManager(); 130 | } 131 | 132 | /** 133 | * Returns issuer manager 134 | * 135 | * @return IssuerManager 136 | */ 137 | public function get_issuer_manager(): IssuerManager { 138 | return $this->sdk->getIssuerManager(); 139 | } 140 | 141 | 142 | /** 143 | * @return Sdk 144 | */ 145 | public function get_sdk(): Sdk { 146 | return $this->sdk; 147 | } 148 | 149 | /** 150 | * Returns api token manager 151 | * 152 | * @return ApiTokenManager 153 | */ 154 | public function get_api_token_manager(): ?ApiTokenManager { 155 | if ( null === $this->sdk ) { 156 | $this->logger->log_error( 'SDK is not initialized' ); 157 | return null; 158 | } 159 | return $this->sdk->getApiTokenManager(); 160 | } 161 | 162 | /** 163 | * Returns a PaymentMethodManager instance 164 | * 165 | * @return PaymentMethodManager|null 166 | */ 167 | public function get_payment_method_manager(): ?PaymentMethodManager { 168 | if ( null === $this->sdk ) { 169 | $this->logger->log_error( 'SDK is not initialized' ); 170 | return null; 171 | } 172 | try { 173 | return $this->sdk->getPaymentMethodManager(); 174 | } catch ( ApiException $api_exception ) { 175 | $this->logger->log_error( $api_exception->getMessage() ); 176 | return null; 177 | } 178 | } 179 | 180 | /** 181 | * Returns an array of tokens for the given customer reference and gateway code 182 | * 183 | * @param string $customer_reference 184 | * @param string $gateway_code 185 | * @return array 186 | */ 187 | public function get_payment_tokens( string $customer_reference, string $gateway_code ): array { 188 | try { 189 | $tokens = $this->sdk->getTokenManager()->getListByGatewayCodeAsArray( $customer_reference, $gateway_code ); 190 | } catch ( ApiException $api_exception ) { 191 | $this->logger->log_error( $api_exception->getMessage() ); 192 | return array(); 193 | } catch ( ClientExceptionInterface $client_exception ) { 194 | $this->logger->log_error( $client_exception->getMessage() ); 195 | return array(); 196 | } catch ( Exception $exception ) { 197 | $this->logger->log_error( $exception->getMessage() ); 198 | return array(); 199 | } 200 | 201 | return $tokens; 202 | } 203 | 204 | /** 205 | * Return the MultiSafepay Merchant Account ID 206 | * 207 | * @return int 208 | */ 209 | public function get_multisafepay_account_id(): int { 210 | try { 211 | $account_manager = $this->sdk->getAccountManager(); 212 | $gateway_merchant_id = $account_manager->get()->getAccountId(); 213 | } catch ( ApiException | ClientExceptionInterface | Exception $exception ) { 214 | $this->logger->log_error( 'Error when try to set the merchant credentials: ' . $exception->getMessage() ); 215 | } 216 | 217 | return $gateway_merchant_id ?? 0; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/Services/ValidationService.php: -------------------------------------------------------------------------------- 1 | true, 30 | 'valid' => true, 31 | 'error' => 'WC_Validation class not available', 32 | ) 33 | ); 34 | return; 35 | } 36 | 37 | // Use the WooCommerce validation class to validate the zip code 38 | $is_valid = WC_Validation::is_postcode( $postcode, $country ); 39 | 40 | wp_send_json( 41 | array( 42 | 'success' => $is_valid, 43 | 'valid' => $is_valid, 44 | 'postcode' => $postcode, 45 | 'country' => $country, 46 | ) 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Services/index.php: -------------------------------------------------------------------------------- 1 | get_multisafepay_logs(); 22 | $view_multisafepay_log_nonce = sanitize_key( $_POST['view-multisafepay-log'] ?? '' ); 23 | 24 | if ( 25 | ( ! empty( $view_multisafepay_log_nonce ) && wp_verify_nonce( wp_unslash( $view_multisafepay_log_nonce ), 'view-multisafepay-log' ) ) && 26 | ! empty( $_POST['log_file'] ) && isset( $logs[ sanitize_title( wp_unslash( $_POST['log_file'] ) ) ] ) ) { 27 | $current_log = $logs[ sanitize_title( wp_unslash( $_POST['log_file'] ) ) ]; 28 | // phpcs:ignore ObjectCalisthenics.ControlStructures.NoElse.ObjectCalisthenics\Sniffs\ControlStructures\NoElseSniff 29 | } elseif ( ! empty( $logs ) ) { 30 | $current_log = end( $logs ); 31 | } 32 | 33 | require_once MULTISAFEPAY_PLUGIN_DIR_PATH . 'templates/partials/multisafepay-settings-logs-display.php'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Settings/SettingsController.php: -------------------------------------------------------------------------------- 1 | base === 'woocommerce_page_multisafepay-settings' ) { 52 | wp_enqueue_style( 'multisafepay-admin-css', MULTISAFEPAY_PLUGIN_URL . '/assets/admin/css/multisafepay-admin.css', array(), MULTISAFEPAY_PLUGIN_VERSION, 'all' ); 53 | } 54 | $sections = array( 'multisafepay_applepay', 'multisafepay_googlepay' ); 55 | if ( isset( $_GET['section'] ) && in_array( $_GET['section'], $sections, true ) ) { 56 | wp_enqueue_script( 'multisafepay-admin-js', MULTISAFEPAY_PLUGIN_URL . '/assets/admin/js/multisafepay-admin.js', array(), MULTISAFEPAY_PLUGIN_VERSION, true ); 57 | } 58 | } 59 | 60 | /** 61 | * Register the common settings page in WooCommerce menu section. 62 | * 63 | * @see https://developer.wordpress.org/reference/functions/add_submenu_page/ 64 | * 65 | * @return void 66 | */ 67 | public function register_common_settings_page(): void { 68 | $title = sprintf( __( 'MultiSafepay Settings v. %s', 'multisafepay' ), MULTISAFEPAY_PLUGIN_VERSION ); // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment 69 | add_submenu_page( 70 | 'woocommerce', 71 | esc_html( $title ), 72 | __( 'MultiSafepay Settings', 'multisafepay' ), 73 | 'manage_woocommerce', 74 | 'multisafepay-settings', 75 | array( $this, 'display_multisafepay_settings' ) 76 | ); 77 | } 78 | 79 | /** 80 | * Display the common settings page view. 81 | * 82 | * @return void 83 | */ 84 | public function display_multisafepay_settings(): void { 85 | $tab_active = $this->get_tab_active(); 86 | require_once MULTISAFEPAY_PLUGIN_DIR_PATH . 'templates/multisafepay-settings-display.php'; 87 | } 88 | 89 | /** 90 | * Display the support settings page view. 91 | * 92 | * @return void 93 | */ 94 | public function display_multisafepay_support_section(): void { 95 | require_once MULTISAFEPAY_PLUGIN_DIR_PATH . 'templates/partials/multisafepay-settings-support-display.php'; 96 | } 97 | 98 | /** 99 | * Display the status tab. 100 | * 101 | * @return void 102 | */ 103 | public function display_multisafepay_status_section(): void { 104 | $status_controller = new StatusController(); 105 | $status_controller->display(); 106 | } 107 | 108 | /** 109 | * Display the log tab. 110 | * 111 | * @return void 112 | */ 113 | public function display_multisafepay_logs_section() { 114 | $logs_controller = new LogsController(); 115 | $logs_controller->display(); 116 | } 117 | 118 | /** 119 | * Returns active tab defined in get variable 120 | * 121 | * @return string 122 | */ 123 | private function get_tab_active(): string { 124 | if ( isset( $_GET['tab'] ) && '' !== $_GET['tab'] ) { 125 | return wp_unslash( sanitize_key( $_GET['tab'] ) ); 126 | } 127 | return 'general'; 128 | } 129 | 130 | /** 131 | * Register general settings in common settings page 132 | * 133 | * @return void 134 | */ 135 | public function register_common_settings(): void { 136 | $settings_fields = new SettingsFields(); 137 | $settings = $settings_fields->get_settings(); 138 | foreach ( $settings as $tab_key => $section ) { 139 | $this->add_settings_section( $tab_key, $section['title'] ); 140 | foreach ( $section['fields'] as $field ) { 141 | $this->register_setting( $field, $tab_key ); 142 | $this->add_settings_field( $field, $tab_key ); 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * Add settings field 149 | * 150 | * @see https://developer.wordpress.org/reference/functions/add_settings_field/ 151 | * 152 | * @param array $field The field 153 | * @param string $tab_key The key of the tab 154 | * @return void 155 | */ 156 | private function add_settings_field( array $field, string $tab_key ): void { 157 | add_settings_field( 158 | $field['id'], 159 | $this->generate_label_for_settings_field( $field ), 160 | array( $this, 'display_field' ), 161 | 'multisafepay-settings-' . $tab_key, 162 | $tab_key, 163 | array( 164 | 'field' => $field, 165 | ) 166 | ); 167 | } 168 | 169 | /** 170 | * Return the label tag to be used in add_settings_field 171 | * 172 | * @param array $field The settings field array 173 | * @return string 174 | */ 175 | private function generate_label_for_settings_field( array $field ): string { 176 | if ( '' === $field['tooltip'] ) { 177 | return sprintf( '', $field['id'], $field['label'] ); 178 | } 179 | return sprintf( '', $field['id'], $field['label'], wc_help_tip( $field['tooltip'] ) ); 180 | } 181 | 182 | /** 183 | * Filter which set the settings page and adds a screen options of WooCommerce 184 | * 185 | * @see http://hookr.io/filters/woocommerce_screen_ids/ 186 | * 187 | * @param array $screen 188 | * @return array 189 | */ 190 | public function set_wc_screen_options_in_common_settings_page( array $screen ): array { 191 | $screen[] = 'woocommerce_page_multisafepay-settings'; 192 | return $screen; 193 | } 194 | 195 | /** 196 | * Register setting 197 | * 198 | * @see https://developer.wordpress.org/reference/functions/register_setting/ 199 | * 200 | * @param array $field 201 | * @param string $tab_key 202 | * @return void 203 | */ 204 | private function register_setting( array $field, string $tab_key ): void { 205 | register_setting( 206 | 'multisafepay-settings-' . $tab_key, 207 | $field['id'], 208 | array( 209 | 'type' => $field['setting_type'], 210 | 'show_in_rest' => false, 211 | 'sanitize_callback' => $field['callback'], 212 | ) 213 | ); 214 | } 215 | 216 | /** 217 | * Add settings section 218 | * 219 | * @see https://developer.wordpress.org/reference/functions/add_settings_section/ 220 | * 221 | * @param string $section_key 222 | * @param string $section_title 223 | * @return void 224 | */ 225 | private function add_settings_section( string $section_key, string $section_title ): void { 226 | add_settings_section( 227 | $section_key, 228 | $section_title, 229 | array( $this, 'display_intro_section' ), 230 | 'multisafepay-settings-' . $section_key 231 | ); 232 | } 233 | 234 | /** 235 | * Callback to display the title on each settings sections 236 | * 237 | * @see https://developer.wordpress.org/reference/functions/add_settings_section/ 238 | * 239 | * @param array $args 240 | * @return void 241 | */ 242 | public function display_intro_section( array $args ): void { 243 | $settings_fields = new SettingsFields(); 244 | $settings = $settings_fields->get_settings(); 245 | if ( ! empty( $settings[ $args['id'] ]['intro'] ) ) { 246 | esc_html( (string) printf( '

%s

', esc_html( $settings[ $args['id'] ]['intro'] ) ) ); 247 | } 248 | } 249 | 250 | /** 251 | * Return the HTML view by field. 252 | * Is the callback function in add_settings_field 253 | * 254 | * @param array $args 255 | * @return void 256 | */ 257 | public function display_field( $args ): void { 258 | $field = $args['field']; 259 | $settings_field_display = new SettingsFieldsDisplay( $field ); 260 | $settings_field_display->display(); 261 | } 262 | 263 | /** 264 | * Filter the settings field and return sort by sort_order key. 265 | * 266 | * @param array $settings 267 | * @return array 268 | */ 269 | public function filter_multisafepay_common_settings_fields( array $settings ): array { 270 | foreach ( $settings as $key => $section ) { 271 | $sort_order = array(); 272 | foreach ( $section['fields'] as $field_key => $field ) { 273 | $sort_order[ $field_key ] = $field['sort_order']; 274 | } 275 | array_multisort( $sort_order, SORT_ASC, $settings[ $key ]['fields'] ); 276 | } 277 | return $settings; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/Settings/SettingsFieldsDisplay.php: -------------------------------------------------------------------------------- 1 | field = $field; 28 | } 29 | 30 | /** 31 | * Get the value by setting field 32 | * 33 | * @see https://developer.wordpress.org/reference/functions/get_option/ 34 | * @param array $field 35 | * @return mixed 36 | */ 37 | private function get_option_by_field( array $field ) { 38 | $value = get_option( $field['id'], false ); 39 | if ( ! $value ) { 40 | return $field['default']; 41 | } 42 | return esc_html( $value ); 43 | } 44 | 45 | /** 46 | * Render the html for a text type input 47 | * 48 | * @param array $field 49 | * @return string 50 | */ 51 | private function render_text_field( array $field ): string { 52 | $value = $this->get_option_by_field( $field ); 53 | $field_id = esc_attr( $field['id'] ); 54 | $placeholder = esc_attr( $field['placeholder'] ); 55 | $html = ''; 56 | if ( ! empty( $field['description'] ) ) { 57 | $html .= '

' . $field['description'] . '

'; 58 | } 59 | return $html; 60 | } 61 | 62 | /** 63 | * Render the html for a select type input 64 | * 65 | * @param array $field 66 | * @return string 67 | */ 68 | private function render_select_field( array $field ): string { 69 | $value = $this->get_option_by_field( $field ); 70 | $field_id = esc_attr( $field['id'] ); 71 | $html = ' '; 81 | if ( ! empty( $field['description'] ) ) { 82 | $html .= '

' . $field['description'] . '

'; 83 | } 84 | return $html; 85 | } 86 | 87 | /** 88 | * Render the html for a checkbox type input 89 | * 90 | * @param array $field 91 | * @return string 92 | */ 93 | private function render_checkbox_field( array $field ): string { 94 | $checked = ( $this->get_option_by_field( $field ) ) ? 'checked="checked"' : ''; 95 | $field_id = esc_attr( $field['id'] ); 96 | $placeholder = esc_attr( $field['placeholder'] ); 97 | $html = ''; 98 | if ( ! empty( $field['description'] ) ) { 99 | $html .= '

' . $field['description'] . '

'; 100 | } 101 | return $html; 102 | } 103 | 104 | /** 105 | * Render the html for a password type input 106 | * 107 | * @param array $field 108 | * @return string 109 | */ 110 | private function render_password_field( array $field ): string { 111 | $value = $this->get_option_by_field( $field ); 112 | $field_id = esc_attr( $field['id'] ); 113 | $placeholder = esc_attr( $field['placeholder'] ); 114 | $html = ''; 115 | if ( ! empty( $field['description'] ) ) { 116 | $html .= '

' . $field['description'] . '

'; 117 | } 118 | return $html; 119 | } 120 | 121 | /** 122 | * Render the html for each type of the registered setting field 123 | * 124 | * @return void 125 | */ 126 | public function display(): void { 127 | $html = ''; 128 | switch ( $this->field['type'] ) { 129 | case 'text': 130 | $html .= $this->render_text_field( $this->field ); 131 | break; 132 | case 'select': 133 | $html .= $this->render_select_field( $this->field ); 134 | break; 135 | case 'checkbox': 136 | $html .= $this->render_checkbox_field( $this->field ); 137 | break; 138 | case 'password': 139 | $html .= $this->render_password_field( $this->field ); 140 | break; 141 | } 142 | 143 | echo wp_kses( $html, EscapeUtil::get_allowed_html_tags() ); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/Settings/StatusController.php: -------------------------------------------------------------------------------- 1 | get_multisafepay_system_status_report(); 20 | $plain_text_status_report = $system_report->get_plain_text_system_status_report(); 21 | require_once MULTISAFEPAY_PLUGIN_DIR_PATH . 'templates/partials/multisafepay-settings-status-display.php'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Settings/ThirdPartyCompatibility.php: -------------------------------------------------------------------------------- 1 | declare_hpos_compatibility(); 20 | $this->declare_blocks_compatibility(); 21 | } 22 | 23 | /** 24 | * Declare compatibility with high performance order storage features 25 | * 26 | * @return void 27 | */ 28 | public function declare_hpos_compatibility(): void { 29 | if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { 30 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 31 | 'custom_order_tables', 32 | MULTISAFEPAY_PLUGIN_DIR_PATH . 'multisafepay.php' 33 | ); 34 | } 35 | } 36 | 37 | /** 38 | * Declare compatibility with block-based checkout features 39 | * 40 | * @return void 41 | */ 42 | public function declare_blocks_compatibility(): void { 43 | if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { 44 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 45 | 'cart_checkout_blocks', 46 | MULTISAFEPAY_PLUGIN_DIR_PATH . 'multisafepay.php' 47 | ); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Settings/index.php: -------------------------------------------------------------------------------- 1 | activate_plugin_single_site(); 26 | } 27 | if ( $network_wide ) { 28 | $this->activate_plugin_all_sites(); 29 | } 30 | } 31 | 32 | /** 33 | * Check if dependencies are not active and return fatal error 34 | * for a single site. 35 | * 36 | * @return void 37 | */ 38 | private function activate_plugin_single_site(): void { 39 | try { 40 | $dependency_checker = new DependencyChecker(); 41 | $dependency_checker->check(); 42 | } catch ( MissingDependencyException $missing_dependency_exception ) { 43 | $dependencies = implode( ', ', $missing_dependency_exception->get_missing_plugin_names() ); 44 | $message = sprintf( __( 'Missing dependencies: %s. Please install these extensions to use the MultiSafepay WooCommerce plugin', 'multisafepay' ), $dependencies ); // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment 45 | die( esc_html( $message ) ); 46 | } 47 | } 48 | 49 | /** 50 | * Check if dependencies are not active and return fatal error 51 | * for a network. 52 | * 53 | * @return void 54 | */ 55 | private function activate_plugin_all_sites(): void { 56 | $blog_ids = $this->get_blogs_ids(); 57 | foreach ( $blog_ids as $blog_id ) { 58 | switch_to_blog( $blog_id ); 59 | $this->activate_plugin_single_site(); 60 | restore_current_blog(); 61 | } 62 | } 63 | 64 | /** 65 | * Return all sites ids 66 | * 67 | * @return array 68 | */ 69 | private function get_blogs_ids(): array { 70 | $args = array( 71 | 'fields' => 'ids', 72 | ); 73 | $blogs_ids = get_sites( $args ); 74 | return $blogs_ids; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Utils/CustomLinks.php: -------------------------------------------------------------------------------- 1 | ' . __( 'Settings', 'multisafepay' ) . '', 21 | '' . __( 'Docs & Support', 'multisafepay' ) . '', 22 | ); 23 | return array_merge( $custom_links, $links ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Utils/DependencyChecker.php: -------------------------------------------------------------------------------- 1 | 'woocommerce/woocommerce.php', 15 | ); 16 | 17 | /** 18 | * Check if there is a dependency missing to make the plugin work 19 | * 20 | * @throws MissingDependencyException 21 | * @return void 22 | */ 23 | public function check(): void { 24 | $missing_plugins = $this->get_missing_plugins_list(); 25 | if ( ! empty( $missing_plugins ) ) { 26 | throw new MissingDependencyException( $missing_plugins ); 27 | } 28 | } 29 | 30 | /** 31 | * Return the keys of all missing plugins 32 | * 33 | * @return array 34 | */ 35 | private function get_missing_plugins_list(): array { 36 | return array_keys( array_filter( self::REQUIRED_PLUGINS, array( $this, 'is_plugin_inactive' ) ) ); 37 | } 38 | 39 | /** 40 | * Check if a certain plugin is inactive 41 | * 42 | * @param string $plugin_path 43 | * @return boolean 44 | */ 45 | private function is_plugin_inactive( string $plugin_path ): bool { 46 | if ( ! is_plugin_active( $plugin_path ) ) { 47 | return true; 48 | } 49 | 50 | return false; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Utils/EscapeUtil.php: -------------------------------------------------------------------------------- 1 | array( 20 | 'name' => array(), 21 | 'id' => array(), 22 | 'type' => array(), 23 | 'placeholder' => array(), 24 | 'value' => array(), 25 | 'checked' => true, 26 | ), 27 | 'p' => array( 28 | 'class' => array(), 29 | ), 30 | 'select' => array( 31 | 'name' => array(), 32 | 'id' => array(), 33 | ), 34 | 'option' => array( 35 | 'value' => array(), 36 | 'selected' => true, 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Utils/Hpos.php: -------------------------------------------------------------------------------- 1 | version ) ) { 29 | return WC()->version; 30 | } 31 | return null; 32 | } 33 | 34 | /** 35 | * Check if the WooCommerce version is compatible with HPOS 36 | * 37 | * @return bool 38 | */ 39 | public static function is_active(): bool { 40 | return ( 'yes' === get_option( 'woocommerce_custom_orders_table_enabled', 'no' ) ) && 41 | version_compare( self::get_woocommerce_version(), self::HPOS_RELEASE_VERSION, '>=' ); 42 | } 43 | 44 | /** 45 | * @param WC_Order $order 46 | * @param string $key 47 | * @param string $value 48 | * 49 | * @return bool|int 50 | */ 51 | public static function update_meta( WC_Order $order, string $key, string $value ) { 52 | if ( self::is_active() ) { 53 | $order->update_meta_data( $key, $value ); 54 | return $order->save(); 55 | } 56 | 57 | return update_post_meta( $order->get_id(), $key, $value ); 58 | } 59 | 60 | /** 61 | * Get the order meta-data 62 | * 63 | * @param WC_Order $order 64 | * @param string $key 65 | * @param bool $single 66 | * 67 | * @return mixed 68 | */ 69 | public static function get_meta( WC_Order $order, string $key, bool $single = true ) { 70 | if ( self::is_active() ) { 71 | return $order->get_meta( $key ); 72 | } 73 | 74 | return get_post_meta( $order->get_id(), $key, $single ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Utils/Internationalization.php: -------------------------------------------------------------------------------- 1 | actions = array(); 29 | $this->filters = array(); 30 | } 31 | 32 | /** 33 | * Add a new action to the collection to be registered with WordPress. 34 | * 35 | * @param string $hook The name of the WordPress action that is being registered. 36 | * @param object $component A reference to the instance of the object on which the action is defined. 37 | * @param string $callback The name of the function defined on the $component. 38 | * @param int $priority The priority at which the function should be fired. 39 | * @param int $accepted_args The number of arguments that should be passed to the $callback. 40 | * @return void 41 | */ 42 | public function add_action( string $hook, $component, string $callback, int $priority = 10, int $accepted_args = 1 ): void { 43 | $this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args ); 44 | } 45 | 46 | /** 47 | * Add a new filter to the collection to be registered with WordPress. 48 | * 49 | * @param string $hook The name of the WordPress filter that is being registered. 50 | * @param object $component A reference to the instance of the object on which the filter is defined. 51 | * @param string $callback The name of the function defined on the $component. 52 | * @param int $priority The priority at which the function should be fired. 53 | * @param int $accepted_args The number of arguments that should be passed to the $callback. 54 | * @return void 55 | */ 56 | public function add_filter( string $hook, $component, string $callback, int $priority = 10, int $accepted_args = 1 ) { 57 | $this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args ); 58 | } 59 | 60 | /** 61 | * A utility function that is used to register the actions and hooks into a single 62 | * collection. 63 | * 64 | * @param array $hooks The collection of hooks that is being registered (that is, actions or filters). 65 | * @param string $hook The name of the WordPress filter that is being registered. 66 | * @param object $component A reference to the instance of the object on which the filter is defined. 67 | * @param string $callback The name of the function definition on the $component. 68 | * @param int $priority The priority at which the function should be fired. 69 | * @param int $accepted_args The number of arguments that should be passed to the $callback. 70 | * @return array The collection of actions and filters registered with WordPress. 71 | */ 72 | private function add( array $hooks, string $hook, $component, string $callback, int $priority, int $accepted_args ) { 73 | $hooks[] = array( 74 | 'hook' => $hook, 75 | 'component' => $component, 76 | 'callback' => $callback, 77 | 'priority' => $priority, 78 | 'accepted_args' => $accepted_args, 79 | ); 80 | return $hooks; 81 | } 82 | 83 | /** 84 | * Register the filters and actions with WordPress. 85 | * 86 | * @return void 87 | */ 88 | public function init() { 89 | foreach ( $this->filters as $hook ) { 90 | add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] ); 91 | } 92 | foreach ( $this->actions as $hook ) { 93 | add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] ); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Utils/Logger.php: -------------------------------------------------------------------------------- 1 | logger = $logger ?? wc_get_logger(); 23 | } 24 | 25 | /** 26 | * Log method for emergency level 27 | * System is unusable. 28 | * Example: 29 | * 30 | * @param string $message 31 | */ 32 | public function log_emergency( string $message ) { 33 | $this->logger->log( 'emergency', $message, array( 'source' => 'multisafepay' ) ); 34 | } 35 | 36 | /** 37 | * Log method for alert level 38 | * Action must be taken immediately. 39 | * Example: Entire website down, database unavailable, etc. 40 | * 41 | * @param string $message 42 | */ 43 | public function log_alert( string $message ) { 44 | $this->logger->log( 'alert', $message, array( 'source' => 'multisafepay' ) ); 45 | } 46 | 47 | /** 48 | * Log method for critical level 49 | * Critical conditions. 50 | * Example: Unexpected exceptions. 51 | * 52 | * @param string $message 53 | */ 54 | public function log_critical( string $message ) { 55 | $this->logger->log( 'critical', $message, array( 'source' => 'multisafepay' ) ); 56 | } 57 | 58 | /** 59 | * Log method for error level 60 | * Error conditions 61 | * Example: Set to shipped or invoiced an order, which is on initialized status 62 | * 63 | * @param string $message 64 | */ 65 | public function log_error( string $message ) { 66 | $this->logger->log( 'error', $message, array( 'source' => 'multisafepay' ) ); 67 | } 68 | 69 | /** 70 | * Log method for warning level 71 | * Exceptional occurrences that are not errors because do not lead to a complete failure of the application. 72 | * Example: Entire website down, database unavailable, etc. 73 | * 74 | * @param string $message 75 | */ 76 | public function log_warning( string $message ) { 77 | $this->logger->log( 'warning', $message, array( 'source' => 'multisafepay' ) ); 78 | } 79 | 80 | /** 81 | * Log method for notice level 82 | * Normal but significant events. 83 | * Example: A notification has been processed using a payment method different than the one registered when the order was created. 84 | * 85 | * @param string $message 86 | */ 87 | public function log_notice( string $message ) { 88 | $this->logger->log( 'notice', $message, array( 'source' => 'multisafepay' ) ); 89 | } 90 | 91 | /** 92 | * Log method for info level 93 | * Interesting events. 94 | * Example: The payment link of a transaction 95 | * 96 | * @param string $message 97 | */ 98 | public function log_info( string $message ) { 99 | if ( get_option( 'multisafepay_debugmode', false ) ) { 100 | $this->logger->log( 'info', $message, array( 'source' => 'multisafepay' ) ); 101 | } 102 | } 103 | 104 | /** 105 | * Log method for debug level 106 | * Detailed debug information: Denotes specific and detailed information of every action. 107 | * Example: The trace of every action registered in the system. 108 | * 109 | * @param string $message 110 | */ 111 | public function log_debug( string $message ) { 112 | if ( get_option( 'multisafepay_debugmode', false ) ) { 113 | $this->logger->log( 'debug', $message, array( 'source' => 'multisafepay' ) ); 114 | } 115 | } 116 | 117 | /** 118 | * Return an array of logs filenames 119 | * 120 | * @return array 121 | */ 122 | private function get_logs(): array { 123 | return WC_Log_Handler_File::get_log_files(); 124 | } 125 | 126 | /** 127 | * Return an array of logs that belongs to MultiSafepay 128 | * 129 | * @return array 130 | */ 131 | public function get_multisafepay_logs(): array { 132 | $logs = $this->get_logs(); 133 | foreach ( $logs as $key => $log ) { 134 | if ( strpos( $log, 'multisafepay' ) === false ) { 135 | unset( $logs[ $key ] ); 136 | } 137 | } 138 | return $logs; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Utils/MoneyUtil.php: -------------------------------------------------------------------------------- 1 | get_payment_method(), 'multisafepay_' ) !== false ) { 20 | return true; 21 | } 22 | 23 | return false; 24 | } 25 | 26 | /** 27 | * Add a note to the order 28 | * 29 | * @param WC_Order $order 30 | * @param string $message 31 | * @param bool $on_debug 32 | * @return void 33 | */ 34 | public static function add_order_note( WC_Order $order, string $message, bool $on_debug = false ): void { 35 | if ( $on_debug && ! get_option( 'multisafepay_debugmode', false ) ) { 36 | return; 37 | } 38 | 39 | $order->add_order_note( $message ); 40 | } 41 | 42 | /** 43 | * Return WooCommerce Order ID where meta value key is 'multisafepay_transaction_id' and value is $order_id 44 | * 45 | * @param string $order_id 46 | * @return false|mixed|\WC_Order 47 | * 48 | * @phpcs:disable WordPress.DB.SlowDBQuery 49 | */ 50 | public static function get_order_id_by_multisafepay_transaction_id_key( string $order_id ) { 51 | $orders = wc_get_orders( 52 | array( 53 | 'limit' => 1, 54 | 'meta_key' => 'multisafepay_transaction_id', 55 | 'meta_value' => $order_id, 56 | 'return' => 'ids', 57 | ) 58 | ); 59 | return ! empty( $orders ) ? $orders[0] : false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Utils/QrOrder.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | description ) { ?> 4 |

description ); ?>

5 | 6 | 7 | payment_component ) { ?> 8 |
9 | 15 | 16 | -------------------------------------------------------------------------------- /templates/multisafepay-settings-display.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | 5 | 16 | '; 20 | echo '

' . esc_html__( 'Order Status', 'multisafepay' ) . '

'; 21 | echo '
'; 22 | settings_fields( 'multisafepay-settings-order_status' ); 23 | do_settings_sections( 'multisafepay-settings-order_status' ); 24 | submit_button(); 25 | echo '
'; 26 | echo '
'; 27 | break; 28 | case 'options': 29 | echo '
'; 30 | echo '

' . esc_html__( 'Options', 'multisafepay' ) . '

'; 31 | echo '
'; 32 | settings_fields( 'multisafepay-settings-options' ); 33 | do_settings_sections( 'multisafepay-settings-options' ); 34 | submit_button(); 35 | echo '
'; 36 | echo '
'; 37 | break; 38 | case 'logs': 39 | $this->display_multisafepay_logs_section(); 40 | break; 41 | case 'support': 42 | $this->display_multisafepay_support_section(); 43 | break; 44 | case 'status': 45 | $this->display_multisafepay_status_section(); 46 | break; 47 | case 'general': 48 | default: 49 | echo ''; 57 | break; 58 | } 59 | ?> 60 | 61 | -------------------------------------------------------------------------------- /templates/partials/multisafepay-settings-logs-display.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

6 |

7 |
8 | 9 | 10 | 11 |
12 |

13 |

14 |
15 | 16 | 17 | 18 |
19 |

20 |
21 | 22 | 27 | 28 |
29 | 30 |

31 |
32 |
33 |
34 | 35 |
36 | 37 | -------------------------------------------------------------------------------- /templates/partials/multisafepay-settings-status-display.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 |
5 |

6 |

7 | 8 |

9 |
10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | $value ) { ?> 24 | 25 | 28 | 31 | 32 | 33 | 34 |
18 |

19 |
26 | : 27 | 29 | 30 |
35 | 36 |
37 | -------------------------------------------------------------------------------- /templates/partials/multisafepay-settings-support-display.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 |

5 | 16 |

17 |
    18 |
  • 19 | 20 |
  • 21 |
  • 22 | 23 |
  • 24 |
25 |

26 |

27 | 28 |

29 | 76 |

77 |

78 | 79 |

80 | 88 |
89 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); 3 | 4 | module.exports = { 5 | ...defaultConfig, 6 | entry: { 7 | 'index': path.resolve( __dirname, 'assets/public/js/multisafepay-blocks/src/index.js' ), 8 | }, 9 | output: { 10 | path: path.resolve( __dirname, 'assets/public/js/multisafepay-blocks/build' ), 11 | filename: '[name].js', 12 | }, 13 | }; 14 | --------------------------------------------------------------------------------