From 72441fb44fbd38546152b2ea142d983cce710012 Mon Sep 17 00:00:00 2001 From: apetrovici <47985108+apetrovici@users.noreply.github.com> Date: Mon, 1 Aug 2022 15:08:20 +0300 Subject: [PATCH] v1.3.0 (#29) * Bug/ts 24 Order notes for transactions id are missing after a new order for admin page is added (#20) * add order note after order created * debug status * TS-24: add order note when has T_id * TR-24: [Integrations]: allow gateway instantiation without wp custom functionality Co-authored-by: apetrovici * Bug/ts 67 bug with issuing refund programmatically (#19) * TS-67 [Woo-commerce] - refund bug when method is called with testendpoint * TS-67 [Woocommerce] - update readme file. * TS-67: [Wocommerce] For testing - all params can be sent ?wc-api=globalpayments_test_refund&order-id=136&reason=something&amount=1 * update version to 1.2.3 * Remove validations at endpoint. code review requested changes. * TR-67: [WooCommerce]: refine test endpoint * add validation for amount * Code standar fix Co-authored-by: apetrovici * TS-65 Add apple pay button style in admin (#21) * TS-65 Add apple pay button style in admin * TS-68 bug for order pay in user account Fix for Apple pay and Google pay * Remove test endpoint * Update sdk and fix digital wallets payment * Fix apple pay payment action text * tested up to tested on wordpress 6.0. and woocommerce 6.6 * Change merchant id to Client id Google pay change. * minimum php requirement change 5.5.9 to 7.1 php version * TR-25: [WooCommerce]: admin pay for order * TR-25: [WooCommerce]: version udate * add styling to button pay for order * modal margin reduce overlap * TR-25: [WooCommerce]: admin pay for order * TR-25: [WooCommerce]: update changelog * TR-25: [WooCommerce] update php sdk * TR-25: bug fix * TR-25: update readme * TR-25: [WooCommerce]: coding standards update * TR-79: [WooCommerce]: bug fix option * TR-80: [WooCommerce]: bug fix capture * TR-25: [WooCommerce]: update comments * 78-bug-design-button-for-pay-from-pay-for-order 78-bug-design-button-for-pay-from-pay-for-order * TR-25: [WooCommerce]: bug fix handle empty data * TR-25: [WooCommerce]: bug fix handle empty data * TR-77: [WooCommerce]: bug fix retrieve user token * Revert "78-bug-design-button-for-pay-from-pay-for-order" This reverts commit e7323c2b7388e60bdd38ae55607dd72464e0ce3a. * 78-fix-button-style * fix for other fields in modal * TR-78: [WooCommerce]: bug fix button style Co-authored-by: javiercrac --- assets/admin/css/globalpayments-admin.css | 19 ++ assets/admin/js/globalpayments-admin.js | 53 +++++ assets/admin/js/globalpayments-modal.js | 70 ++++++ assets/frontend/js/globalpayments-applepay.js | 9 +- .../frontend/js/globalpayments-googlepay.js | 6 + assets/frontend/js/globalpayments-helper.js | 5 +- .../globalpayments-secure-payment-fields.js | 15 +- composer.json | 2 +- composer.lock | 153 +++---------- ...ments-gateway-provider-for-woocommerce.php | 8 +- includes/admin/views/html-pay-order.php | 75 ++++++ readme.txt | 11 +- src/Data/PaymentTokenData.php | 9 +- src/Gateways/AbstractGateway.php | 118 +++++++--- src/Gateways/ApplePayGateway.php | 48 ++-- src/Gateways/Clients/SdkClient.php | 14 +- src/Gateways/GooglePayGateway.php | 6 +- src/Gateways/GpApiGateway.php | 18 +- src/Gateways/Requests/AbstractRequest.php | 13 ++ .../Requests/AuthorizationRequest.php | 3 + src/Gateways/Requests/RequestArg.php | 1 + src/Gateways/Traits/PayOrderTrait.php | 215 ++++++++++++++++++ src/Plugin.php | 4 +- 23 files changed, 693 insertions(+), 182 deletions(-) create mode 100644 assets/admin/js/globalpayments-modal.js create mode 100644 includes/admin/views/html-pay-order.php create mode 100644 src/Gateways/Traits/PayOrderTrait.php diff --git a/assets/admin/css/globalpayments-admin.css b/assets/admin/css/globalpayments-admin.css index 634664f..ff09773 100644 --- a/assets/admin/css/globalpayments-admin.css +++ b/assets/admin/css/globalpayments-admin.css @@ -2,3 +2,22 @@ margin:0; } +.woocommerce-globalpayments-checkout-error { + background-color:#e2401c; + margin-left:0; + margin-bottom:10px; + border-radius:4px; + color:#fff; + clear:both; + padding:6px 12px; + position:relative; + list-style:none outside; + display:block; + width:100%; + font-size:14px; +} + +.globalpayments.card-submit iframe { + min-height:32px; + width:auto; +} diff --git a/assets/admin/js/globalpayments-admin.js b/assets/admin/js/globalpayments-admin.js index f339e01..6749e61 100644 --- a/assets/admin/js/globalpayments-admin.js +++ b/assets/admin/js/globalpayments-admin.js @@ -17,6 +17,59 @@ */ attachEventHandlers: function () { $( document ).on( 'change', this.getLiveModeSelector(), this.toggleCredentialsSettings.bind( this ) ); + + // Admin Pay for Order + $( '#customer_user' ).on( 'change', this.updatePaymentMethods ); + $( '.wc-globalpayments-pay-order' ).on( 'click', this.payForOrder ); + $( document.body ).on('wc_backbone_modal_loaded', this.modalLoaded.bind( this ) ); + }, + + updatePaymentMethods: function ( e ) { + // fetch user payment tokens + var customer_id = $( e.target ).val(); + globalpayments_admin_params.payment_methods = []; + if ( customer_id > 0 && typeof globalpayments_admin_params !== "undefined" ) { + var data = { + _wpnonce: globalpayments_admin_params._wpnonce, + customer_id: customer_id + }; + $( '.wc-globalpayments-pay-order' ).prop( 'disabled', true ); + $.get( globalpayments_admin_params.payment_methods_url, data, function ( response ) { + globalpayments_admin_params.payment_methods = response; + $( '.wc-globalpayments-pay-order' ).prop( 'disabled', false ); + }, 'json' ); + } + }, + + /** + * Enable modal template. + * + * @param e + */ + payForOrder: function( e ) { + e.preventDefault(); + $( this ).WCGlobalPaymentsPayOrderBackboneModal({ + template: 'wc-globalpayments-pay-order-modal', + variable: { + customer_id: $( '#customer_user' ).val(), + payment_methods: globalpayments_admin_params.payment_methods, + } + }); + }, + + /** + * Render modal content. + * + * @param e + * @param target + */ + modalLoaded: function ( e, target ) { + switch ( target ) { + case 'wc-globalpayments-pay-order-modal': + $( document.body ).trigger( 'globalpayments_pay_order_modal_loaded' ); + $( document.body ).trigger( 'wc-credit-card-form-init' ); + break; + } }, /** diff --git a/assets/admin/js/globalpayments-modal.js b/assets/admin/js/globalpayments-modal.js new file mode 100644 index 0000000..a79045d --- /dev/null +++ b/assets/admin/js/globalpayments-modal.js @@ -0,0 +1,70 @@ +/*global jQuery */ +( function( $ ) { + 'use strict'; + + /** + * WooCommerce Backbone Global Payments Modal plugin + * + * @param {object} options + */ + $.fn.WCGlobalPaymentsPayOrderBackboneModal = function( options ) { + return this.each( function() { + ( new $.WCGlobalPaymentsPayOrderBackboneModal( $( this ), options ) ); + } ); + }; + + /** + * Initialize the Backbone Modal + * + * @param {object} element [description] + * @param {object} options [description] + */ + $.WCGlobalPaymentsPayOrderBackboneModal = function( element, options ) { + var settings = $.extend( {}, $.WCBackboneModal.defaultOptions, options ); + + if ( settings.template ) { + new $.WCGlobalPaymentsPayOrderBackboneModal.View( { + target: settings.template, + string: settings.variable + } ); + } + }; + + $.WCGlobalPaymentsPayOrderBackboneModal.View = $.WCBackboneModal.View.extend( { + events: _.extend( $.WCBackboneModal.View.prototype.events, { + 'click #place_order': 'payOrder' + } ), + payOrder: function( e ) { + e.preventDefault(); + this.block(); + $.ajax( { + url: globalpayments_admin_params.payorder_url, + method: 'POST', + data: this.getFormData(), + } ).done( function ( response ) { + if ( response.error ) { + this.unblock(); + $( document.body ).trigger( 'globalpayments_pay_order_modal_error', [ response.message ] ); + } else { + window.location.href = window.location.href; + } + }.bind( this ) ).fail( function ( xhr, textStatus, errorThrown ) { + this.unblock(); + $( document.body ).trigger( 'globalpayments_pay_order_modal_error', [ errorThrown ] ); + }.bind( this ) ) + $( document.body ).trigger( 'globalpayments_pay_order_modal_response', [ this ] ); + }, + block: function() { + this.$el.find( '.wc-backbone-modal-content' ).block( { + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + } ); + }, + unblock: function() { + this.$el.find( '.wc-backbone-modal-content' ).unblock(); + }, + } ); +} )( jQuery ); diff --git a/assets/frontend/js/globalpayments-applepay.js b/assets/frontend/js/globalpayments-applepay.js index 5fb0f5d..7d71011 100644 --- a/assets/frontend/js/globalpayments-applepay.js +++ b/assets/frontend/js/globalpayments-applepay.js @@ -42,6 +42,12 @@ if ( 1 == wc_checkout_params.is_checkout ) { $( document.body ).on( 'updated_checkout', this.initialize.bind( this ) ); } + + // Order Pay + if ( $( document.body ).hasClass( 'woocommerce-order-pay' ) ) { + $( document ).ready( this.initialize.bind( this ) ); + return; + } }, initialize: function () { @@ -61,7 +67,8 @@ var self = this var paymentButton = document.createElement( 'div' ); - paymentButton.className = 'apple-pay-button apple-pay-button-white-with-line'; + + paymentButton.className = 'apple-pay-button apple-pay-button-' + this.gatewayOptions.button_color; paymentButton.title = 'Pay with Apple Pay'; paymentButton.alt = 'Pay with Apple Pay'; paymentButton.id = self.id; diff --git a/assets/frontend/js/globalpayments-googlepay.js b/assets/frontend/js/globalpayments-googlepay.js index 4437263..c5a8ece 100644 --- a/assets/frontend/js/globalpayments-googlepay.js +++ b/assets/frontend/js/globalpayments-googlepay.js @@ -48,6 +48,12 @@ if ( 1 == wc_checkout_params.is_checkout ) { $( document.body ).on( 'updated_checkout', this.initialize.bind( this ) ); } + + // Order Pay + if ( $( document.body ).hasClass( 'woocommerce-order-pay' ) ) { + $( document ).ready( this.initialize.bind( this ) ); + return; + } }, initialize: function () { diff --git a/assets/frontend/js/globalpayments-helper.js b/assets/frontend/js/globalpayments-helper.js index bcb58d5..a564866 100644 --- a/assets/frontend/js/globalpayments-helper.js +++ b/assets/frontend/js/globalpayments-helper.js @@ -108,7 +108,6 @@ var savedCardsAvailable = $( this.getStoredPaymentMethodsRadioSelector( selectedPaymentGatewayId ) + '[value!="new"]' ).length > 0; // user selects (new) card as payment method var newSavedCardSelected = 'new' === $( this.getStoredPaymentMethodsRadioSelector( selectedPaymentGatewayId ) + ':checked' ).val(); - // selected payment method is card or digital wallet if ( ! savedCardsAvailable || savedCardsAvailable && newSavedCardSelected ) { $( this.getSubmitButtonTargetSelector( selectedPaymentGatewayId ) ).show(); @@ -133,7 +132,9 @@ // Checkout 'form[name="checkout"]', // Add payment method - 'form#add_payment_method' + 'form#add_payment_method', + // Admin Order Pay + 'form#wc-globalpayments-pay-order-form', ]; var forms = document.querySelectorAll( checkoutForms.join( ',' ) ); diff --git a/assets/frontend/js/globalpayments-secure-payment-fields.js b/assets/frontend/js/globalpayments-secure-payment-fields.js index 05499c8..9be88fa 100644 --- a/assets/frontend/js/globalpayments-secure-payment-fields.js +++ b/assets/frontend/js/globalpayments-secure-payment-fields.js @@ -112,6 +112,12 @@ $( document ).ready( this.renderPaymentFields.bind( this ) ); return; } + + // Admin Pay for Order + $( document.body ).on( 'globalpayments_pay_order_modal_loaded', this.renderPaymentFields.bind( this ) ); + $( document.body ).on( 'globalpayments_pay_order_modal_error', function( event, message ) { + self.showPaymentError( message ); + } ); }, /** @@ -453,7 +459,9 @@ helper.unblockOnError(); - $( document.body ).trigger( 'checkout_error' ); + if ( 1 == wc_checkout_params.is_checkout ) { + $( document.body ).trigger( 'checkout_error' ); + } }, /** @@ -589,8 +597,9 @@ * @returns {string} */ getSubmitButtonText: function () { - return $( '#place_order' ).data( 'value' ) || $( '#place_order' ).attr( 'value' ); - } + var selector = helper.getPlaceOrderButtonSelector(); + return $( selector ).data( 'value' ) || $( selector ).attr( 'value' ); + }, }; new GlobalPaymentsWooCommerce( globalpayments_secure_payment_fields_params, globalpayments_secure_payment_threedsecure_params ); diff --git a/composer.json b/composer.json index f25584e..2b68175 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ } }, "require": { - "globalpayments/php-sdk": "3.0.5", + "globalpayments/php-sdk": "4.0.4", "psr/log": "^1.1" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 327284e..d8adbe1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,29 +4,31 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7ae5f65598b227e0bea3335cb103fc36", + "content-hash": "9896be46886101a5b6e38ddea3d70ed4", "packages": [ { "name": "globalpayments/php-sdk", - "version": "3.0.5", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/globalpayments/php-sdk.git", - "reference": "d7e63ea28acbceb5863fe0aa485f4af3420b6f4e" + "reference": "b92712fb47ad9dda33afeba4661d3d572421a6c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/globalpayments/php-sdk/zipball/d7e63ea28acbceb5863fe0aa485f4af3420b6f4e", - "reference": "d7e63ea28acbceb5863fe0aa485f4af3420b6f4e", + "url": "https://api.github.com/repos/globalpayments/php-sdk/zipball/b92712fb47ad9dda33afeba4661d3d572421a6c0", + "reference": "b92712fb47ad9dda33afeba4661d3d572421a6c0", "shasum": "" }, "require": { "ext-curl": "*", "ext-dom": "*", + "ext-intl": "*", "ext-json": "*", + "ext-mbstring": "*", "ext-openssl": "*", "ext-zlib": "*", - "php": ">= 5.5.9" + "php": "^7.1" }, "require-dev": { "brianium/paratest": "0.15.0", @@ -53,9 +55,9 @@ "homepage": "https://developer.heartlandpaymentsystems.com/documentation", "support": { "issues": "https://github.com/globalpayments/php-sdk/issues", - "source": "https://github.com/globalpayments/php-sdk/tree/3.0.5" + "source": "https://github.com/globalpayments/php-sdk/tree/4.0.4" }, - "time": "2022-04-21T12:12:24+00:00" + "time": "2022-07-14T14:31:44+00:00" }, { "name": "psr/log", @@ -240,16 +242,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.13.2", + "version": "v4.14.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077" + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", "shasum": "" }, "require": { @@ -290,9 +292,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" }, - "time": "2021-11-30T19:35:32+00:00" + "time": "2022-05-31T20:59:12+00:00" }, { "name": "phar-io/manifest", @@ -1016,16 +1018,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.20", + "version": "9.5.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", - "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", "shasum": "" }, "require": { @@ -1059,7 +1061,6 @@ "sebastian/version": "^3.0.2" }, "require-dev": { - "ext-pdo": "*", "phpspec/prophecy-phpunit": "^2.0.1" }, "suggest": { @@ -1103,7 +1104,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21" }, "funding": [ { @@ -1115,7 +1116,7 @@ "type": "github" } ], - "time": "2022-04-01T12:37:26+00:00" + "time": "2022-06-19T12:14:25+00:00" }, { "name": "sebastian/cli-parser", @@ -2083,16 +2084,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.6.2", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", "shasum": "" }, "require": { @@ -2135,89 +2136,7 @@ "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2022-06-18T07:21:10+00:00" }, { "name": "theseer/tokenizer", @@ -2271,21 +2190,21 @@ }, { "name": "webmozart/assert", - "version": "1.10.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" + "ext-ctype": "*", + "php": "^7.2 || ^8.0" }, "conflict": { "phpstan/phpstan": "<0.12.20", @@ -2323,9 +2242,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" + "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, - "time": "2021-03-09T10:59:23+00:00" + "time": "2022-06-03T18:03:27+00:00" } ], "aliases": [], @@ -2335,5 +2254,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.2.0" } diff --git a/globalpayments-gateway-provider-for-woocommerce.php b/globalpayments-gateway-provider-for-woocommerce.php index 5ef85dc..dd35907 100644 --- a/globalpayments-gateway-provider-for-woocommerce.php +++ b/globalpayments-gateway-provider-for-woocommerce.php @@ -3,15 +3,15 @@ * Plugin Name: GlobalPayments WooCommerce * Plugin URI: https://github.com/globalpayments/globalpayments-woocommerce * Description: This extension allows WooCommerce to use the available Global Payments payment gateways. All card data is tokenized using the respective gateway's tokenization service. - * Version: 1.2.2 - * Requires PHP: 5.5.9 - * WC tested up to: 6.3.1 + * Version: 1.3.0 + * Requires PHP: 7.1 + * WC tested up to: 6.6.0 * Author: Global Payments */ defined( 'ABSPATH' ) || exit; -if ( version_compare( PHP_VERSION, '5.5.9', '<' ) ) { +if ( version_compare( PHP_VERSION, '7.1', '<' ) ) { return; } diff --git a/includes/admin/views/html-pay-order.php b/includes/admin/views/html-pay-order.php new file mode 100644 index 0000000..867b161 --- /dev/null +++ b/includes/admin/views/html-pay-order.php @@ -0,0 +1,75 @@ +

+ + +

+ diff --git a/readme.txt b/readme.txt index 5a01aef..d9227fe 100644 --- a/readme.txt +++ b/readme.txt @@ -2,8 +2,8 @@ Contributors: globalpayments Tags: woocommerce, woo, unified, commerce, platform, global, payments, heartland, payment, systems, tsys, genius, 3DS, gateway, token, tokenize, save cards Requires at least: 5.4 -Tested up to: 5.9.3 -Stable tag: 1.2.2 +Tested up to: 6.0 +Stable tag: 1.2.3 License: MIT License URI: https://github.com/globalpayments/globalpayments-woocommerce/blob/main/LICENSE @@ -24,6 +24,7 @@ This extension allows WooCommerce to use the available Global Payments payment g - 3D Secure 1 - Digital Wallets - Google Pay - Digital Wallets - Apple Pay +- Payments over the phone = Support = For more information or questions, please email developers@globalpay.com . @@ -46,6 +47,12 @@ Access to our Unified Payments requires sandbox credentials which you can retrie 4. Click ‘Create a New App’. An app is a set of credentials used to access the API and generate access tokens. == Changelog == += 1.3.0 = +* Unified Payments - Added Admin Pay for Order (process payments over the phone) +* Added Admin option for Apple Pay button color +* Bug fix - Refund issue when create_refund is called programmatically +* Bug fix - Digital Wallets pay buttons on `Pay for Order` +* Update PHP-SDK to v4.0.4 = 1.2.2 = * Bug fix - 3DS/Digital Wallets amount not updated when a customer added/removed a coupon diff --git a/src/Data/PaymentTokenData.php b/src/Data/PaymentTokenData.php index 3fa3ddc..db6af7e 100644 --- a/src/Data/PaymentTokenData.php +++ b/src/Data/PaymentTokenData.php @@ -51,6 +51,10 @@ public function get_token() { $token = $this->get_single_use_token(); } + if ( empty( $token ) ) { + throw new \Exception( __( 'Unable to retrieve payment token.' ) ); + } + return $token; } @@ -139,8 +143,11 @@ public function get_multi_use_token() { } $token = WC_Payment_Tokens::get( $token_id ); + if ( ! isset( $token ) ) { + return null; + } - if ( null === $token || $token->get_user_id() !== get_current_user_id() ) { + if ( $token->get_user_id() !== get_current_user_id() && ! wc_current_user_has_role( 'administrator' ) ) { return null; } diff --git a/src/Gateways/AbstractGateway.php b/src/Gateways/AbstractGateway.php index 4586eae..7c08e07 100644 --- a/src/Gateways/AbstractGateway.php +++ b/src/Gateways/AbstractGateway.php @@ -5,16 +5,14 @@ defined( 'ABSPATH' ) || exit; use Exception; -use GlobalPayments\Api\Entities\Enums\GatewayProvider; use GlobalPayments\Api\Entities\Exceptions\ApiException; use GlobalPayments\Api\Entities\Reporting\TransactionSummary; use GlobalPayments\WooCommercePaymentGatewayProvider\Gateways\Requests\RequestArg; +use GlobalPayments\WooCommercePaymentGatewayProvider\Plugin; use WC_Payment_Gateway_CC; use WC_Order; use GlobalPayments\Api\Entities\Transaction; -use GlobalPayments\WooCommercePaymentGatewayProvider\Plugin; - /** * Shared gateway method implementations */ @@ -140,8 +138,10 @@ abstract class AbstractGateway extends WC_Payment_Gateway_Cc { */ public $cvn_reject_conditions; - public function __construct() { + public function __construct( $is_provider = false ) { + $this->client = new Clients\SdkClient(); + $this->has_fields = true; $this->supports = array( 'products', @@ -164,9 +164,12 @@ public function __construct() { $this->init_form_fields(); $this->init_settings(); $this->configure_merchant_settings(); - $this->add_hooks(); + if ( $is_provider ) { + return; + } + $this->add_hooks(); } /** @@ -228,6 +231,7 @@ protected function environment_indicator() { * Get the current gateway provider * * @return string + * @throws Exception */ public function get_gateway_provider() { if ( ! $this->gateway_provider ) { @@ -315,7 +319,7 @@ public function tokenization_script() { WC()->version, true ); - + wp_localize_script( 'globalpayments-helper', 'globalpayments_helper_params', @@ -340,7 +344,7 @@ public function tokenization_script() { wp_enqueue_script( 'globalpayments-secure-payment-fields', Plugin::get_url( '/assets/frontend/js/globalpayments-secure-payment-fields.js' ), - array( 'globalpayments-secure-payment-fields-lib', 'wc-checkout' ), + is_admin() ? array( 'globalpayments-secure-payment-fields-lib' ) : array( 'globalpayments-secure-payment-fields-lib', 'wc-checkout' ), WC()->version, true ); @@ -356,7 +360,7 @@ public function tokenization_script() { ); // Global Payments scripts for handling 3DS - if ( GatewayProvider::GP_API !== $this->gateway_provider ) { + if ( GpApiGateway::GATEWAY_ID !== $this->id || ! is_checkout() ) { return; } @@ -450,7 +454,7 @@ public function init_form_fields() { 'label' => __( 'Check AVS/CVN result codes and reverse transaction.', 'globalpayments-gateway-provider-for-woocommerce' ), 'type' => 'checkbox', 'description' => sprintf( - __( 'This will check AVS/CVN result codes and reverse transaction.' ) + __( 'This will check AVS/CVN result codes and reverse transaction.', 'globalpayments-gateway-provider-for-woocommerce' ) ), 'default' => 'yes' ), @@ -459,7 +463,7 @@ public function init_form_fields() { 'type' => 'multiselect', 'class' => 'wc-enhanced-select', 'css' => 'width: 450px', - 'description' => __( 'Choose for which AVS result codes, the transaction must be auto reversed.' ), + 'description' => __( 'Choose for which AVS result codes, the transaction must be auto reversed.', 'globalpayments-gateway-provider-for-woocommerce' ), 'options' => $this->avs_rejection_conditions(), 'default' => array( "N", "S", "U", "P", "R", "G", "C", "I" ), ), @@ -468,7 +472,7 @@ public function init_form_fields() { 'type' => 'multiselect', 'class' => 'wc-enhanced-select', 'css' => 'width: 450px', - 'description' => __( 'Choose for which CVN result codes, the transaction must be auto reversed.' ), + 'description' => __( 'Choose for which CVN result codes, the transaction must be auto reversed.', 'globalpayments-gateway-provider-for-woocommerce' ), 'options' => $this->cvn_rejection_conditions(), 'default' => array( "P", "?", "N" ), ), @@ -563,7 +567,6 @@ protected function secure_payment_fields_styles() { 'font-size' => '14px', 'height' => '35px', 'padding' => '6px 12px', - 'width' => '100%', ), '#secure-payment-field:focus' => array( 'border' => '1px solid lightblue', @@ -745,6 +748,7 @@ protected function add_hooks() { 'admin_enforce_single_gateway' ) ); add_filter( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + add_action( 'woocommerce_new_order', array( $this, 'admin_add_order_note_after_order_created' ) ); } if ( 'no' === $this->enabled ) { @@ -771,12 +775,40 @@ protected function add_hooks() { } } + /** + * Add order note when creating an order from admin with transaction ID + * + * @param int $order_id + * + * @return bool + */ + public function admin_add_order_note_after_order_created( $order_id ) { + if ( ! $order_id ) { + return; + } + + $order = wc_get_order( $order_id ); + if ( empty( $order) ) { + return; + } + if ( $this->id != $order->get_payment_method() ) { + return; + } + if ( empty( $order->get_transaction_id() )) { + return; + } + + $order->add_order_note( __( 'Order created with Transaction ID: ' ) . $order->get_transaction_id() ); + + } + /** * Handle payment functions * * @param int $order_id * * @return array + * @throws ApiException */ public function process_payment( $order_id ) { $order = new WC_Order( $order_id ); @@ -784,6 +816,20 @@ public function process_payment( $order_id ) { $response = $this->submit_request( $request ); $is_successful = $this->handle_response( $request, $response ); + if ( $is_successful ) { + $note_text = sprintf( + '%1$s%2$s %3$s. Transaction ID: %4$s.', + get_woocommerce_currency_symbol( $order->get_currency() ), + $order->get_total(), + $this->payment_action == self::TXN_TYPE_AUTHORIZE ? + __( 'authorized', 'globalpayments-gateway-provider-for-woocommerce' ) : + __( 'charged', 'globalpayments-gateway-provider-for-woocommerce' ), + $order->get_transaction_id() + ); + + $order->add_order_note( $note_text ); + } + return array( 'result' => $is_successful ? 'success' : 'failure', 'redirect' => $is_successful ? $this->get_return_url( $order ) : false, @@ -794,6 +840,7 @@ public function process_payment( $order_id ) { * Handle adding new cards via 'My Account' * * @return array + * @throws Exception */ public function add_payment_method() { $request = $this->prepare_request( self::TXN_TYPE_VERIFY ); @@ -819,10 +866,11 @@ public function add_payment_method() { * Handle online refund requests via WP Admin > WooCommerce > Edit Order * * @param int $order_id - * @param null|number $amount + * @param null $amount * @param string $reason * - * @return array + * @return bool + * @throws ApiException */ public function process_refund( $order_id, $amount = null, $reason = '' ) { $details = $this->get_transaction_details( $order_id ); @@ -831,6 +879,18 @@ public function process_refund( $order_id, $amount = null, $reason = '' ) { $order = new WC_Order( $order_id ); $request = $this->prepare_request( $txn_type, $order ); + + if ( null != $amount ) { + $amount = str_replace( ',', '.', $amount ); + $amount = number_format( (float)round( $amount, 2, PHP_ROUND_HALF_UP ), 2, '.', '' ); + if ( ! is_numeric( $amount ) ) { + throw new Exception( __( 'Refund amount must be a valid number', 'globalpayments-gateway-provider-for-woocommerce' ) ); + } + } + $request->set_request_data( array( + 'refund_amount' => $amount, + 'refund_reason' => $reason, + )); $request_args = $request->get_args(); if ( 0 >= (float)$request_args[ RequestArg::AMOUNT ] ) { throw new Exception( __( 'Refund amount must be greater than zero.', 'globalpayments-gateway-provider-for-woocommerce' ) ); @@ -853,13 +913,12 @@ public function process_refund( $order_id, $amount = null, $reason = '' ) { /** * Handle capture auth requests via WP Admin > WooCommerce > Edit Order * - * @param int $order_id + * @param $order * - * @return array + * @return Transaction + * @throws Exception */ - public static function capture_credit_card_authorization( $order_id ) { - $order = new WC_Order( $order_id ); - + public static function capture_credit_card_authorization( $order ) { switch ( $order->get_payment_method() ) { case "globalpayments_heartland": $gateway = new HeartlandGateway(); @@ -884,6 +943,7 @@ public static function capture_credit_card_authorization( $order_id ) { if ( "00" === $response->responseCode && "Success" === $response->responseMessage || 'SUCCESS' === $response->responseCode && "CAPTURED" === $response->responseMessage ) { + delete_post_meta( $order->get_id(), '_globalpayments_payment_action' ); $order->add_order_note( "Transaction captured. Transaction ID for the capture: " . $response->transactionReference->transactionId ); @@ -904,9 +964,10 @@ public static function capture_credit_card_authorization( $order_id ) { /** * Handle online refund requests via WP Admin > WooCommerce > Edit Order > Order actions * - * @param int $order_id + * @param $order_id * - * @return TransactionSummary + * @return Transaction + * @throws Exception */ public function get_transaction_details( $order_id ) { $order = new WC_Order( $order_id ); @@ -919,10 +980,11 @@ public function get_transaction_details( $order_id ) { /** * Creates the necessary request based on the transaction type * - * @param WC_Order $order - * @param string $txn_type + * @param $txn_type + * @param WC_Order|null $order * * @return Requests\RequestInterface + * @throws Exception */ protected function prepare_request( $txn_type, WC_Order $order = null ) { $map = array( @@ -971,6 +1033,7 @@ protected function submit_request( Requests\RequestInterface $request ) { * @param Transaction $response * * @return bool + * @throws ApiException */ protected function handle_response( Requests\RequestInterface $request, Transaction $response ) { if ( $response->responseCode !== '00' && 'SUCCESS' !== $response->responseCode || $response->responseMessage === 'Partially Approved' ) { @@ -1056,7 +1119,7 @@ public function get_decline_message( string $response_code ) { * * @return array */ - public static function addCaptureOrderAction( $actions ) { + public static function add_capture_order_action( $actions ) { global $theorder; if ( AbstractGateway::TXN_TYPE_AUTHORIZE !== $theorder->get_meta( '_globalpayments_payment_action' ) ) { @@ -1083,6 +1146,9 @@ public function get_order_info() { } public function get_session_amount() { + if ( is_admin() ) { + return null; + } $cart_totals = WC()->session->get( 'cart_totals' ); return round( $cart_totals['total'], 2 ); @@ -1197,7 +1263,7 @@ public function admin_enqueue_scripts( $hook_suffix ) { if ( $this->id != $section ) { return; } - if ( !$this->is_digital_wallet) { + if ( ! $this->is_digital_wallet ) { wp_enqueue_script( 'globalpayments-admin', Plugin::get_url( '/assets/admin/js/globalpayments-admin.js' ), @@ -1222,7 +1288,5 @@ public function admin_enqueue_scripts( $hook_suffix ) { WC()->version ); } - } - } diff --git a/src/Gateways/ApplePayGateway.php b/src/Gateways/ApplePayGateway.php index bb3302c..6e1bd13 100644 --- a/src/Gateways/ApplePayGateway.php +++ b/src/Gateways/ApplePayGateway.php @@ -85,10 +85,17 @@ class ApplePayGateway extends AbstractGateway { */ public $payment_action; + /** + * Button color + * + * @var string + */ + public $button_color; + public function __construct() { parent::__construct(); - $this->gateway = new GpApiGateway(); + $this->gateway = new GpApiGateway( true ); } public function configure_method_settings() { @@ -107,7 +114,8 @@ public function get_frontend_gateway_options() { 'cc_types' => $this->cc_types, 'country_code' => wc_get_base_location()['country'], 'validate_merchant_url' => WC()->api_request_url( 'globalpayments_validate_merchant' ), - 'googlepay_gateway_id' => GooglePayGateway::GATEWAY_ID + 'googlepay_gateway_id' => GooglePayGateway::GATEWAY_ID, + 'button_color' => $this->button_color ); } @@ -163,8 +171,20 @@ public function get_gateway_form_fields() { 'AMEX' => 'AMEX', 'DISCOVER' => 'Discover', ), - 'default' => array( 'VISA' , 'MASTERCARD' , 'AMEX' , 'DISCOVER' ) - ) + 'default' => array( 'VISA', 'MASTERCARD', 'AMEX', 'DISCOVER' ) + ), + 'button_color' => array( + 'title' => __( 'Apple button color', 'globalpayments-gateway-provider-for-woocommerce' ), + 'type' => 'select', + 'description' => __( 'Button styling at checkout', 'globalpayments-gateway-provider-for-woocommerce' ), + 'default' => 'apple-pay-button-white', + 'desc_tip' => true, + 'options' => array( + 'black' => __( 'Black', 'globalpayments-gateway-provider-for-woocommerce' ), + 'white' => __( 'White', 'globalpayments-gateway-provider-for-woocommerce' ), + 'white-with-line' => __( 'White with Outline', 'globalpayments-gateway-provider-for-woocommerce' ), + ), + ), ); } @@ -195,7 +215,7 @@ public function init_form_fields() { 'default' => self::TXN_TYPE_SALE, 'desc_tip' => true, 'options' => array( - self::TXN_TYPE_SALE => __( 'Charge', 'globalpayments-gateway-provider-for-woocommerce' ), + self::TXN_TYPE_SALE => __( 'Authorize + Capture', 'globalpayments-gateway-provider-for-woocommerce' ), self::TXN_TYPE_AUTHORIZE => __( 'Authorize only', 'globalpayments-gateway-provider-for-woocommerce' ), ), ), @@ -211,11 +231,11 @@ public function payment_fields() { } public function validate_merchant() { - $responseValidationUrl = json_decode( file_get_contents( 'php://input' ) ); - if ( empty($responseValidationUrl) ) { - return null; - } - $validationUrl = $responseValidationUrl->validationUrl; + $responseValidationUrl = json_decode( file_get_contents( 'php://input' ) ); + if ( empty( $responseValidationUrl ) ) { + return null; + } + $validationUrl = $responseValidationUrl->validationUrl; if ( ! $this->apple_merchant_id || @@ -226,8 +246,8 @@ public function validate_merchant() { ) { return null; } - $pemCrtPath = ABSPATH . $this->apple_merchant_cert_path; - $pemKeyPath = ABSPATH . $this->apple_merchant_key_path; + $pemCrtPath = ABSPATH . $this->apple_merchant_cert_path; + $pemKeyPath = ABSPATH . $this->apple_merchant_key_path; $validationPayload = array(); $validationPayload['merchantIdentifier'] = $this->apple_merchant_id; @@ -298,8 +318,8 @@ public function tokenization_script() { array( 'orderInfoUrl' => WC()->api_request_url( 'globalpayments_order_info' ), 'order' => array( - 'amount' => $this->get_session_amount(), - 'currency' => get_woocommerce_currency(), + 'amount' => $this->get_session_amount(), + 'currency' => get_woocommerce_currency(), ) ) ); diff --git a/src/Gateways/Clients/SdkClient.php b/src/Gateways/Clients/SdkClient.php index 1d8aea8..15ce786 100644 --- a/src/Gateways/Clients/SdkClient.php +++ b/src/Gateways/Clients/SdkClient.php @@ -13,6 +13,8 @@ use GlobalPayments\Api\Entities\StoredCredential; use GlobalPayments\Api\Gateways\IPaymentGateway; use GlobalPayments\Api\PaymentMethods\CreditCardData; +use GlobalPayments\Api\PaymentMethods\Interfaces\IAuthable; +use GlobalPayments\Api\PaymentMethods\Interfaces\IChargable; use GlobalPayments\Api\ServiceConfigs\AcceptorConfig; use GlobalPayments\Api\ServiceConfigs\Gateways\GeniusConfig; use GlobalPayments\Api\ServiceConfigs\Gateways\GpApiConfig; @@ -96,6 +98,9 @@ public function set_request( RequestInterface $request ) { public function execute() { $this->configure_sdk(); $builder = $this->get_transaction_builder(); + if ( ! isset( $builder ) ) { + throw new \Exception( __( 'Unable to perform request.' ) ); + } if ( 'transactionDetail' === $this->args['TXN_TYPE'] ) { return $builder->execute(); } @@ -165,8 +170,9 @@ protected function get_transaction_builder() { $subject = in_array( $this->get_arg( RequestArg::TXN_TYPE ), $this->auth_transactions, true ) ? $this->card_data : $this->previous_transaction; - - return $subject->{$this->get_arg( RequestArg::TXN_TYPE )}(); + if ( $subject instanceof IChargable || $subject instanceof IAuthable ) { + return $subject->{$this->get_arg( RequestArg::TXN_TYPE )}(); + } } protected function prepare_request_args( RequestInterface $request ) { @@ -294,6 +300,10 @@ protected function prepare_card_data( WC_Payment_Token_CC $token = null ) { if ( isset( PaymentTokenData::$tsepCvv ) ) { $this->card_data->cvn = PaymentTokenData::$tsepCvv; } + + if ( $this->has_arg( RequestArg::ENTRY_MODE ) ) { + $this->card_data->entryMethod = $this->get_arg( RequestArg::ENTRY_MODE ); + } } protected function threedsecure_is_enabled() { diff --git a/src/Gateways/GooglePayGateway.php b/src/Gateways/GooglePayGateway.php index dbca869..03b0935 100644 --- a/src/Gateways/GooglePayGateway.php +++ b/src/Gateways/GooglePayGateway.php @@ -71,7 +71,7 @@ class GooglePayGateway extends AbstractGateway { public function __construct() { parent::__construct(); - $this->gateway = new GpApiGateway(); + $this->gateway = new GpApiGateway( true ); } public function get_first_line_support_email() { @@ -102,9 +102,9 @@ public function get_backend_gateway_options() { public function get_gateway_form_fields() { return array( 'global_payments_merchant_id' => array( - 'title' => __( 'Global Payments Merchant Id*', 'globalpayments-gateway-provider-for-woocommerce' ), + 'title' => __( 'Global Payments Client ID*', 'globalpayments-gateway-provider-for-woocommerce' ), 'type' => 'text', - 'description' => __( 'Your MerchantID provided by Global Payments.', 'globalpayments-gateway-provider-for-woocommerce' ), + 'description' => __( 'Your Client ID provided by Global Payments.', 'globalpayments-gateway-provider-for-woocommerce' ), 'custom_attributes' => array( 'required' => 'required' ), ), 'google_merchant_id' => array( diff --git a/src/Gateways/GpApiGateway.php b/src/Gateways/GpApiGateway.php index 8d86778..2315498 100644 --- a/src/Gateways/GpApiGateway.php +++ b/src/Gateways/GpApiGateway.php @@ -7,11 +7,13 @@ use GlobalPayments\Api\Entities\Enums\Channel; use GlobalPayments\Api\Gateways\GpApiConnector; use GlobalPayments\WooCommercePaymentGatewayProvider\Gateways\Requests\ThreeDSecure\CheckEnrollmentRequest; +use GlobalPayments\WooCommercePaymentGatewayProvider\Gateways\Traits\PayOrderTrait; use GlobalPayments\WooCommercePaymentGatewayProvider\Plugin; defined( 'ABSPATH' ) || exit; class GpApiGateway extends AbstractGateway { + use PayOrderTrait; /** * Gateway ID */ @@ -210,6 +212,13 @@ protected function get_access_token() { protected function add_hooks() { parent::add_hooks(); + if ( is_admin() ) { + // Admin Pay for Order hooks + add_action( 'woocommerce_admin_order_data_after_order_details', array( $this, 'pay_order_modal' ), 99 ); + add_filter( 'globalpayments_secure_payment_fields_styles', array( $this, 'pay_order_modal_secure_payment_fields_styles' ) ); + } + add_action( 'woocommerce_api_globalpayments_pay_order', array( $this, 'pay_order_modal_process_payment' ), 99 ); + add_action( 'woocommerce_api_globalpayments_get_payment_methods', array( $this, 'pay_order_modal_get_payment_methods' ) ); add_filter( 'pre_update_option_woocommerce_globalpayments_gpapi_settings', array( $this, 'woocommerce_globalpayments_gpapi_settings' @@ -244,14 +253,16 @@ public function woocommerce_globalpayments_gpapi_settings( $settings ) { } if ( empty( $settings['merchant_contact_url'] ) || 50 < strlen( $settings['merchant_contact_url'] ) ) { add_action( 'admin_notices', function () { - echo '

' . __( 'Please provide a Contact Url (maxLength: 50). Gateway not enabled.' ) . '

'; + echo '

' . + __( 'Please provide a Contact Url (maxLength: 50). Gateway not enabled.', 'globalpayments-gateway-provider-for-woocommerce' ) . '

'; } ); $settings['enabled'] = 'no'; } if ( wc_string_to_bool( $settings['is_production'] ) ) { if ( empty( $settings['app_id'] ) || empty( $settings['app_key'] ) ) { add_action( 'admin_notices', function () { - echo '

' . __( 'Please provide Live Credentials. Gateway not enabled.' ) . '

'; + echo '

' . + __( 'Please provide Live Credentials. Gateway not enabled.', 'globalpayments-gateway-provider-for-woocommerce' ) . '

'; } ); $settings['enabled'] = 'no'; } @@ -260,7 +271,8 @@ public function woocommerce_globalpayments_gpapi_settings( $settings ) { } if ( empty( $settings['sandbox_app_id'] ) || empty( $settings['sandbox_app_key'] ) ) { add_action( 'admin_notices', function () { - echo '

' . __( 'Please provide Sandbox Credentials. Gateway not enabled.' ) . '

'; + echo '

' . + __( 'Please provide Sandbox Credentials. Gateway not enabled.', 'globalpayments-gateway-provider-for-woocommerce' ) . '

'; } ); $settings['enabled'] = 'no'; } diff --git a/src/Gateways/Requests/AbstractRequest.php b/src/Gateways/Requests/AbstractRequest.php index 1b9e3fe..c3d627a 100644 --- a/src/Gateways/Requests/AbstractRequest.php +++ b/src/Gateways/Requests/AbstractRequest.php @@ -84,4 +84,17 @@ public function get_request_data( $key = null ) { return wc_clean( $this->data[ $key ] ); } + + /** + * Set Request Data + * + * @param array + */ + public function set_request_data ( array $data ) { + if ( ! empty( $this->data ) ) { + $this->data = array_merge( $this->data, $data ); + } else { + $this->data = $data; + } + } } diff --git a/src/Gateways/Requests/AuthorizationRequest.php b/src/Gateways/Requests/AuthorizationRequest.php index 47cdb96..3023e43 100644 --- a/src/Gateways/Requests/AuthorizationRequest.php +++ b/src/Gateways/Requests/AuthorizationRequest.php @@ -25,6 +25,9 @@ public function get_args() { RequestArg::SERVER_TRANS_ID => $this->data[ $this->gateway_id ]['serverTransId'] ?? null, RequestArg::PARES => ! empty( $this->data[ $this->gateway_id ]['PaRes'] ) ? $this->data[ $this->gateway_id ]['PaRes'] : null, ); + if ( isset ( $this->data['entry_mode'] ) ) { + $response[ RequestArg::ENTRY_MODE ] = $this->data['entry_mode']; + } if ( isset ( $this->data[ $this->gateway_id ]['digital_wallet_token_response'] ) ) { $response[ RequestArg::DIGITAL_WALLET_TOKEN ] = $this->remove_slashes_from_token( $this->data[ $this->gateway_id ]['digital_wallet_token_response'] ); diff --git a/src/Gateways/Requests/RequestArg.php b/src/Gateways/Requests/RequestArg.php index f49a573..6b2641b 100644 --- a/src/Gateways/Requests/RequestArg.php +++ b/src/Gateways/Requests/RequestArg.php @@ -8,6 +8,7 @@ abstract class RequestArg { const CARD_DATA = 'CARD_DATA'; const CARD_HOLDER_NAME = 'CARD_HOLDER_NAME'; const CURRENCY = 'CURRENCY'; + const ENTRY_MODE = 'ENTRY_MODE'; const PARES = 'PARES'; const PERMISSIONS = 'PERMISSIONS'; const SERVER_TRANS_ID = 'SERVER_TRANS_ID'; diff --git a/src/Gateways/Traits/PayOrderTrait.php b/src/Gateways/Traits/PayOrderTrait.php new file mode 100644 index 0000000..3476092 --- /dev/null +++ b/src/Gateways/Traits/PayOrderTrait.php @@ -0,0 +1,215 @@ +get_type() !== 'shop_order' || ! $order->has_status( array( 'pending', 'failed' ) ) ) { + return; + } + + // The HTML needed for the `Pay for Order` modal + include_once( Plugin::get_path() . '/includes/admin/views/html-pay-order.php' ); + + $this->tokenization_script(); + wp_enqueue_script( + 'globalpayments-modal', + Plugin::get_url( '/assets/admin/js/globalpayments-modal.js' ), + array( + 'jquery', + 'wc-backbone-modal', + 'jquery-blockui' + ), + WC()->version, + true + ); + wp_enqueue_script( + 'globalpayments-admin', + Plugin::get_url( '/assets/admin/js/globalpayments-admin.js' ), + array( + 'jquery', + 'jquery-blockui', + 'globalpayments-modal' + ), + WC()->version, + true + ); + wp_localize_script( + 'globalpayments-admin', + 'globalpayments_admin_params', + array( + '_wpnonce' => wp_create_nonce( 'woocommerce-globalpayments-pay' ), + 'gateway_id' => $this->id, + 'payorder_url' => WC()->api_request_url( 'globalpayments_pay_order' ), + 'payment_methods' => $this->get_payment_methods( $order->get_customer_id() ), + 'payment_methods_url' => WC()->api_request_url( 'globalpayments_get_payment_methods' ), + ) + ); + wp_enqueue_style( + 'globalpayments-admin', + Plugin::get_url( '/assets/admin/css/globalpayments-admin.css' ), + array(), + WC()->version + ); + } + + /** + * Endpoint for retrieving Customer payment methods. + */ + public function pay_order_modal_get_payment_methods() { + $payment_methods = array(); + $nonce_value = wc_get_var( $_REQUEST['_wpnonce'], '' ); // @codingStandardsIgnoreLine. + if ( ! wp_verify_nonce( $nonce_value, 'woocommerce-globalpayments-pay' ) ) { + wp_send_json( $payment_methods ); + } + $customer_id = absint( $_GET['customer_id'] ); + wp_send_json( $this->get_payment_methods( $customer_id ) ); + } + + /** + * Retrieve Customer payment methods. + * + * @param int $customer_id + * + * @return array + */ + private function get_payment_methods( int $customer_id ) { + $payment_methods = array(); + if ( empty( $customer_id ) ) { + return $payment_methods; + } + $tokens = \WC_Payment_Tokens::get_customer_tokens( $customer_id, $this->id ); + foreach ( $tokens as $token ) { + $payment_methods[] = array( + 'id' => $token->get_id(), + 'display_name' => $token->get_display_name(), + 'is_default' => $token->is_default(), + ); + } + + return $payment_methods; + } + + /** + * Endpoint for processing the payment in Admin modal. + */ + public function pay_order_modal_process_payment() { + try { + // Validate modal request + if ( ! isset( $_POST['woocommerce_globalpayments_pay'] ) ) { + throw new \Exception( __( 'Invalid payment request.', 'globalpayments-gateway-provider-for-woocommerce' ) ); + } + + wc_nocache_headers(); + + $nonce_value = wc_get_var( $_REQUEST['woocommerce-globalpayments-pay-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) ); // @codingStandardsIgnoreLine. + if ( ! wp_verify_nonce( $nonce_value, 'woocommerce-globalpayments-pay' ) ) { + throw new \Exception( __( 'Invalid payment request.', 'globalpayments-gateway-provider-for-woocommerce' ) ); + } + + $order_key = wc_get_post_data_by_key( 'order_key' ); + $order_id = (int) wc_get_post_data_by_key( 'order_id' ); + $order = wc_get_order( $order_id ); + if ( $order_id !== $order->get_id() || ! hash_equals( $order->get_order_key(), $order_key ) ) { + throw new \Exception( __( 'Invalid payment request.', 'globalpayments-gateway-provider-for-woocommerce' ) ); + } + + // Validate + if ( empty( abs( $order->get_total() ) ) ) { + throw new \Exception( __( 'Invalid amount. Order total must be greater than zero.', 'globalpayments-gateway-provider-for-woocommerce' ) ); + } + if ( ! empty( $_POST['transaction_id'] ) && empty( $_POST['allow_order'] ) ) { + throw new \Exception( __( 'This order has a transaction ID associated with it already. Please click the checkbox to proceed with payment.', 'globalpayments-gateway-provider-for-woocommerce' ) ); + } + + // Update payment method. + $order->set_payment_method( $this->id ); + $order->save(); + + // Process Payment + $result = $this->process_payment( $order_id ); + if ( isset( $result['result'] ) && 'success' === $result['result'] ) { + wp_send_json( [ + 'success' => true, + ] ); + } else { + throw new Exception( __( 'Something went wrong while processing the payment.', 'globalpayments-gateway-provider-for-woocommerce' ) ); + } + } catch ( \Exception $e ) { + wp_send_json( [ + 'error' => true, + 'message' => $e->getMessage(), + ] ); + } + } + + /** + * Secure payment fields styles for Admin modal. + * + * @param string $secure_payment_fields_styles CSS styles. + * + * @return false|string + */ + public function pay_order_modal_secure_payment_fields_styles( $secure_payment_fields_styles ) { + $secure_payment_fields_styles = json_decode( $secure_payment_fields_styles, true ); + + $secure_payment_fields_styles['#secure-payment-field-wrapper'] = array( + 'justify-content' => 'flex-end', + ); + $secure_payment_fields_styles['#secure-payment-field'] = array( + 'background-color' => '#fff', + 'border' => '1px solid #ccc', + 'border-radius' => '4px', + 'display' => 'block', + 'font-size' => '13px', + 'font-weight' => '400', + 'height' => '35px', + 'padding' => '6px 12px', + 'font-family' => '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !important', + ); + + $secure_payment_fields_styles['#secure-payment-field[type=button]:focus'] = array( + 'color' => '#fff', + 'background' => '#135e96', + 'border-color' => '#135e96', + ); + $secure_payment_fields_styles['#secure-payment-field[type=button]:hover'] = array( + 'color' => '#fff', + 'background' => '#135e96', + 'border-color' => '#135e96', + ); + $secure_payment_fields_styles['button#secure-payment-field.submit'] = array( + 'background' => '#2271b1', + 'border-color' => '#2271b1', + 'border-radius' => '3px', + 'color' => '#fff', + 'cursor' => 'pointer', + 'display' => 'inline-block', + 'flex' => '0', + 'line-height' => '23px', + 'margin-bottom' => '0', + 'min-height' => '32px', + 'padding' => '0px 12px 0px 12px', + 'text-align' => 'center', + 'text-decoration' => 'none', + 'text-shadow' => 'none', + 'white-space' => 'nowrap', + ); + + return json_encode( $secure_payment_fields_styles ); + } +} diff --git a/src/Plugin.php b/src/Plugin.php index dd5aeb1..31abb2e 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -18,7 +18,7 @@ class Plugin { * * @var string */ - const VERSION = '1.2.2'; + const VERSION = '1.3.0'; /** * Init the package. @@ -44,7 +44,7 @@ public static function init() { } add_filter( 'woocommerce_payment_gateways', array( self::class, 'add_gateways' ) ); - add_action( 'woocommerce_order_actions', array( Gateways\AbstractGateway::class, 'addCaptureOrderAction' ) ); + add_action( 'woocommerce_order_actions', array( Gateways\AbstractGateway::class, 'add_capture_order_action' ) ); add_action( 'woocommerce_order_action_capture_credit_card_authorization', array( Gateways\AbstractGateway::class, 'capture_credit_card_authorization'