diff --git a/inc/utils.php b/inc/utils.php index bdd68c75..c4f0e504 100644 --- a/inc/utils.php +++ b/inc/utils.php @@ -236,3 +236,13 @@ function transformPhoneToNLFormat($phone) } return $phone; } + +function isMollieBirthValid($billing_birthdate) +{ + $today = new DateTime(); + $birthdate = DateTime::createFromFormat('Y-m-d', $billing_birthdate); + if ($birthdate >= $today) { + return false; + } + return true; +} diff --git a/inc/woocommerce.php b/inc/woocommerce.php index 3c5a00fc..27fe5b02 100644 --- a/inc/woocommerce.php +++ b/inc/woocommerce.php @@ -36,6 +36,9 @@ function is_order_received_page() */ function untrailingslashit($string) { + if ($string === null) { + return ''; + } return rtrim($string, '/'); } } diff --git a/mollie-payments-for-woocommerce.php b/mollie-payments-for-woocommerce.php index 7e9d56bd..d129381e 100644 --- a/mollie-payments-for-woocommerce.php +++ b/mollie-payments-for-woocommerce.php @@ -3,16 +3,16 @@ * Plugin Name: Mollie Payments for WooCommerce * Plugin URI: https://www.mollie.com * Description: Accept payments in WooCommerce with the official Mollie plugin - * Version: 7.6.0 + * Version: 7.7.0 * Author: Mollie * Author URI: https://www.mollie.com * Requires at least: 5.0 - * Tested up to: 6.5 + * Tested up to: 6.6 * Text Domain: mollie-payments-for-woocommerce * Domain Path: /languages * License: GPLv2 or later * WC requires at least: 3.9 - * WC tested up to: 9.0 + * WC tested up to: 9.1 * Requires PHP: 7.2 * Requires Plugins: woocommerce */ diff --git a/public/images/payconiq.svg b/public/images/payconiq.svg new file mode 100644 index 00000000..ce31ca99 --- /dev/null +++ b/public/images/payconiq.svg @@ -0,0 +1 @@ + diff --git a/public/images/riverty.svg b/public/images/riverty.svg new file mode 100644 index 00000000..b9e2a97e --- /dev/null +++ b/public/images/riverty.svg @@ -0,0 +1,4 @@ + + + + diff --git a/readme.txt b/readme.txt index e7247028..65478312 100644 --- a/readme.txt +++ b/readme.txt @@ -2,8 +2,8 @@ Contributors: daanvm, danielhuesken, davdebcom, dinamiko, syde, l.vangunst, ndijkstra, robin-mollie, wido, carmen222, inpsyde-maticluznar Tags: mollie, payments, payment gateway, woocommerce, credit card, apple pay, ideal, bancontact, klarna, sofort, giropay, woocommerce subscriptions Requires at least: 3.8 -Tested up to: 6.5 -Stable tag: 7.5.5 +Tested up to: 6.6 +Stable tag: 7.7.0 Requires PHP: 7.2 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -221,6 +221,20 @@ Automatic updates should work like a charm; as always though, ensure you backup == Changelog == += 7.7.0 - 12-08-2024 = + +* Added - Payconiq payment method +* Added - Riverty payment method +* Fix - Declaring compatibility in WP Editor +* Security - Enhanced object reference security + += 7.6.0 - 10-07-2024 = + +* Added - Trustly payment method +* Deprecated - Giropay payment method (iropay Depreciation FAQ](https://help.mollie.com/hc/en-gb/articles/19745480480786-Giropay-Depreciation-FAQ)) +* Fixed - Mollie hooks into unrelated orders +* Fixed - Notices and type errors after 7.5.5 update +* Fixed - Rounding issues with products including tax = 7.5.5 - 18-06-2024 = * Feature Flag - Enable Bancomat Pay & Alma feature flag by default (official launch 2024-07-01) diff --git a/resources/js/blocks/molliePaymentMethod.js b/resources/js/blocks/molliePaymentMethod.js index 50990ecc..f890ce92 100644 --- a/resources/js/blocks/molliePaymentMethod.js +++ b/resources/js/blocks/molliePaymentMethod.js @@ -197,17 +197,17 @@ const MollieComponent = (props) => { }, [activePaymentMethod, onCheckoutValidation, billing.billingData, shippingData.shippingAddress, item, phoneString, inputBirthdate, inputPhone]); onSubmitLocal = onSubmit - const updateIssuer = ( changeEvent ) => { - selectIssuer( changeEvent.target.value ) - }; - const updateCompany = ( changeEvent ) => { - selectCompany( changeEvent.target.value ) - }; - const updatePhone = ( changeEvent ) => { - selectPhone( changeEvent.target.value ) - } - const updateBirthdate = ( changeEvent ) => { - selectBirthdate( changeEvent.target.value ) + const updateIssuer = (e) => selectIssuer(e.target.value); + const updateCompany = (e) => selectCompany(e.target.value); + const updatePhone = (e) => selectPhone(e.target.value); + const updateBirthdate = (e) => selectBirthdate( e.target.value ); + + function fieldMarkup(id, fieldType, label, action, value, placeholder = null) { + const className = "wc-block-components-text-input wc-block-components-address-form__" + id; + return
+ + +
} if (item.issuers && item.name !== "mollie_wc_gateway_creditcard"){ @@ -218,38 +218,59 @@ const MollieComponent = (props) => { return
; } - function fieldMarkup(id, fieldType, label, action, value) { - const className = "wc-block-components-text-input wc-block-components-address-form__" + id; - return
- - -
- } - if (item.name === "mollie_wc_gateway_billie") { if (isCompanyFieldVisible) { return; } const companyField = item.companyPlaceholder ? item.companyPlaceholder : "Company name"; - return fieldMarkup("billing-company","text", companyField, updateCompany, inputCompany); + return ( + <> +

{item.content}

+ {fieldMarkup("billing-company","text", companyField, updateCompany, inputCompany)} + + ); } - if (item.name === "mollie_wc_gateway_in3"){ - let fields = []; - const birthdateField = item.birthdatePlaceholder ? item.birthdatePlaceholder : "Birthdate"; - fields.push(fieldMarkup("billing-birthdate", "date", birthdateField, updateBirthdate, inputBirthdate)); - if (isPhoneFieldVisible === false) { - const phoneField = item.phonePlaceholder ? item.phonePlaceholder : "Phone"; - fields.push(fieldMarkup("billing-phone-in3", "tel", phoneField, updatePhone, inputPhone)); - } + useEffect(() => { + const countryCodes = { + BE: '+32xxxxxxxxx', + NL: '+316xxxxxxxx', + DE: '+49xxxxxxxxx', + AT: '+43xxxxxxxxx', + }; + const country = billing.billingData.country; + item.phonePlaceholder = countryCodes[country] || countryCodes['NL']; + }, [billing.billingData.country]); + + if (item.name === "mollie_wc_gateway_in3") { + const birthdateField = item.birthdatePlaceholder || "Birthdate"; + const phoneField = item.phonePlaceholder || "+316xxxxxxxx"; + const phoneLabel = item.phoneLabel || "Phone"; + return ( + <> +

{item.content}

+ {fieldMarkup("billing-birthdate", "date", birthdateField, updateBirthdate, inputBirthdate)} + {!isPhoneFieldVisible && fieldMarkup("billing-phone-in3", "tel", phoneLabel, updatePhone, inputPhone, phoneField)} + + ); + } - return <>{fields}; + if (item.name === "mollie_wc_gateway_riverty") { + const birthdateField = item.birthdatePlaceholder || "Birthdate"; + const phoneField = item.phonePlaceholder || "+316xxxxxxxx"; + const phoneLabel = item.phoneLabel || "Phone"; + return ( + <> +

{item.content}

+ {fieldMarkup("billing-birthdate", "date", birthdateField, updateBirthdate, inputBirthdate)} + {!isPhoneFieldVisible && fieldMarkup("billing-phone-riverty", "tel", phoneLabel, updatePhone, inputPhone, phoneField)} + + ); } return

{item.content}

} - const molliePaymentMethod = (useEffect, ajaxUrl, filters, gatewayData, availableGateways, item, jQuery, requiredFields, isCompanyFieldVisible, isPhoneFieldVisible) =>{ let billingCountry = filters.billingCountry let cartTotal = filters.cartTotal diff --git a/resources/js/mollieBlockIndex.js b/resources/js/mollieBlockIndex.js index b919acb3..04c6d367 100644 --- a/resources/js/mollieBlockIndex.js +++ b/resources/js/mollieBlockIndex.js @@ -8,7 +8,14 @@ import molliePaymentMethod from './blocks/molliePaymentMethod' window.onload = (event) => { const { registerPaymentMethod } = wc.wcBlocksRegistry; const { checkoutData, defaultFields } = wc.wcSettings.allSettings; - const { billing_address, shipping_address } = checkoutData; + let billing_address, shipping_address; + + if (checkoutData) { + ({ billing_address, shipping_address } = checkoutData); + } else { + billing_address = {}; + shipping_address = {}; + } const { ajaxUrl, filters, gatewayData, availableGateways } = mollieBlockData.gatewayData; const {useEffect} = wp.element; const isAppleSession = typeof window.ApplePaySession === "function" diff --git a/resources/js/rivertyCountryPlaceholder.js b/resources/js/rivertyCountryPlaceholder.js new file mode 100644 index 00000000..cb5e6289 --- /dev/null +++ b/resources/js/rivertyCountryPlaceholder.js @@ -0,0 +1,13 @@ +( + function ({jQuery}) { + jQuery(function ($) { + $('body').on('change', '#billing_country', function () { + if ($('input[name="payment_method"]:checked').val() === 'mollie_wc_gateway_riverty') { + $('body').trigger('update_checkout'); + } + }); + }); + })(window) + + + diff --git a/src/Assets/AssetsModule.php b/src/Assets/AssetsModule.php index 66699511..8b9f3a96 100644 --- a/src/Assets/AssetsModule.php +++ b/src/Assets/AssetsModule.php @@ -277,6 +277,13 @@ protected function registerFrontendScripts(string $pluginUrl, string $pluginPath (string) filemtime($this->getPluginPath($pluginPath, '/public/js/gatewaySurcharge.min.js')), true ); + wp_register_script( + 'mollie-riverty-classic-handles', + $this->getPluginUrl($pluginUrl, '/public/js/rivertyCountryPlaceholder.min.js'), + ['jquery'], + (string) filemtime($this->getPluginPath($pluginPath, '/public/js/rivertyCountryPlaceholder.min.js')), + true + ); } public function registerBlockScripts(string $pluginUrl, string $pluginPath): void @@ -310,11 +317,11 @@ public function enqueueFrontendScripts($container) return; } wp_enqueue_style('mollie-gateway-icons'); - $isBillieEnabled = mollieWooCommerceIsGatewayEnabled('mollie_wc_gateway_billie_settings', 'enabled'); $allMethodsEnabledAtMollie = $container->get('gateway.paymentMethodsEnabledAtMollie'); - $isBillieEnabledAtMollie = in_array('billie', $allMethodsEnabledAtMollie, true); - if ($isBillieEnabled && $isBillieEnabledAtMollie) { - wp_enqueue_script('mollie-billie-classic-handles'); + $isRivertyEnabled = mollieWooCommerceIsGatewayEnabled('mollie_wc_gateway_riverty_settings', 'enabled'); + $isRivertyEnabledAtMollie = in_array('riverty', $allMethodsEnabledAtMollie, true); + if ($isRivertyEnabled && $isRivertyEnabledAtMollie) { + wp_enqueue_script('mollie-riverty-classic-handles'); } $applePayGatewayEnabled = mollieWooCommerceIsGatewayEnabled('mollie_wc_gateway_applepay_settings', 'enabled'); diff --git a/src/Assets/MollieCheckoutBlocksSupport.php b/src/Assets/MollieCheckoutBlocksSupport.php index fe69e72d..450f6b76 100644 --- a/src/Assets/MollieCheckoutBlocksSupport.php +++ b/src/Assets/MollieCheckoutBlocksSupport.php @@ -132,6 +132,14 @@ public static function gatewayDataForWCBlocks(Data $dataService, array $gatewayI $title = $gateway->paymentMethod()->title(); $labelMarkup = "{$title}{$gateway->icon}"; $hasSurcharge = $gateway->paymentMethod()->hasSurcharge(); + $countryCodes = [ + 'BE' => '+32xxxxxxxxx', + 'NL' => '+316xxxxxxxx', + 'DE' => '+49xxxxxxxxx', + 'AT' => '+43xxxxxxxxx', + ]; + $country = WC()->customer ? WC()->customer->get_billing_country() : ''; + $phonePlaceholder = in_array($country, array_keys($countryCodes)) ? $countryCodes[$country] : $countryCodes['NL']; $gatewayData[] = [ 'name' => $gatewayKey, 'label' => $labelMarkup, @@ -152,7 +160,8 @@ public static function gatewayDataForWCBlocks(Data $dataService, array $gatewayI 'supports' => self::gatewaySupportsFeatures($gateway->paymentMethod(), $isSepaEnabled), 'errorMessage' => $gateway->paymentMethod()->getProperty('errorMessage'), 'companyPlaceholder' => $gateway->paymentMethod()->getProperty('companyPlaceholder'), - 'phonePlaceholder' => $gateway->paymentMethod()->getProperty('phonePlaceholder'), + 'phoneLabel' => $gateway->paymentMethod()->getProperty('phoneLabel'), + 'phonePlaceholder' => $phonePlaceholder, 'birthdatePlaceholder' => $gateway->paymentMethod()->getProperty('birthdatePlaceholder'), ]; } diff --git a/src/Gateway/GatewayModule.php b/src/Gateway/GatewayModule.php index b97e5867..6aaae88b 100644 --- a/src/Gateway/GatewayModule.php +++ b/src/Gateway/GatewayModule.php @@ -794,14 +794,9 @@ private function isPhoneValid($billing_phone) return preg_match('/^\+[1-9]\d{10,13}$|^[1-9]\d{9,13}$|^06\d{9,13}$/', $billing_phone); } - private function isBirthValid($billing_birthdate) + private function isBirthValid($billing_birthdate): bool { - $today = new DateTime(); - $birthdate = DateTime::createFromFormat('Y-m-d', $billing_birthdate); - if ($birthdate >= $today) { - return false; - } - return true; + return isMollieBirthValid($billing_birthdate); } public function addPhoneWhenRest($arrayContext) diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index c9a72920..1ff2cc0c 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -179,7 +179,7 @@ public function __construct( 'yes'; $this->enabled = $isEnabledAtWoo; - if ($this->paymentMethod->getProperty('filtersOnBuild')) { + if ($this->enabled && $this->paymentMethod->getProperty('filtersOnBuild')) { $this->paymentMethod->filtersOnBuild(); } } diff --git a/src/Payment/MollieOrder.php b/src/Payment/MollieOrder.php index 804e91b6..36691581 100644 --- a/src/Payment/MollieOrder.php +++ b/src/Payment/MollieOrder.php @@ -1002,7 +1002,11 @@ protected function getCustomerBirthdate($order) if (!$gateway || !isset($gateway->id)) { return null; } - $methodId = $gateway->id === 'mollie_wc_gateway_in3'; + if (strpos($gateway->id, 'mollie_wc_gateway_') === false) { + return null; + } + $additionalFields = $gateway->paymentMethod()->getProperty('additionalFields'); + $methodId = $additionalFields && in_array('birthdate', $additionalFields, true); if ($methodId) { //phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $fieldPosted = wc_clean(wp_unslash($_POST["billing_birthdate"] ?? '')); diff --git a/src/Payment/PaymentService.php b/src/Payment/PaymentService.php index f866de5f..bd7bf82d 100644 --- a/src/Payment/PaymentService.php +++ b/src/Payment/PaymentService.php @@ -398,6 +398,7 @@ protected function processAsMollieOrder( 'mollie_wc_gateway_klarna', 'mollie_wc_gateway_billie', 'mollie_wc_gateway_in3', + 'mollie_wc_gateway_riverty', ]; if (in_array($order_payment_method, $orderMandatoryPaymentMethods, true)) { diff --git a/src/PaymentMethods/In3.php b/src/PaymentMethods/In3.php index 51e5d7bf..d86beb12 100644 --- a/src/PaymentMethods/In3.php +++ b/src/PaymentMethods/In3.php @@ -14,6 +14,7 @@ public function getConfig(): array 'settingsDescription' => '', 'defaultDescription' => __('Pay in 3 instalments, 0% interest', 'mollie-payments-for-woocommerce'), 'paymentFields' => true, + 'additionalFields' => ['birthdate', 'phone'], 'instructions' => false, 'supports' => [ 'products', diff --git a/src/PaymentMethods/Payconiq.php b/src/PaymentMethods/Payconiq.php new file mode 100644 index 00000000..59f9899a --- /dev/null +++ b/src/PaymentMethods/Payconiq.php @@ -0,0 +1,29 @@ + 'payconiq', + 'defaultTitle' => __('payconiq', 'mollie-payments-for-woocommerce'), + 'settingsDescription' => '', + 'defaultDescription' => '', + 'paymentFields' => false, + 'instructions' => false, + 'supports' => ['products', 'refunds'], + 'filtersOnBuild' => false, + 'confirmationDelayed' => false, + 'SEPA' => false, + ]; + } + + public function getFormFields($generalFormFields): array + { + return $generalFormFields; + } +} diff --git a/src/PaymentMethods/PaymentFieldsStrategies/RivertyFieldsStrategy.php b/src/PaymentMethods/PaymentFieldsStrategies/RivertyFieldsStrategy.php new file mode 100644 index 00000000..ab8dedea --- /dev/null +++ b/src/PaymentMethods/PaymentFieldsStrategies/RivertyFieldsStrategy.php @@ -0,0 +1,91 @@ +getOrderIdOnPayForOrderPage(); + $phoneValue = $order->get_billing_phone(); + } + + if (is_checkout() && !is_checkout_pay_page() && !$isPhoneRequired) { + $showPhoneField = true; + } + if (is_checkout() && !is_checkout_pay_page()) { + $showBirthdateField = true; + } + + if ($showPhoneField) { + $this->phoneNumber($phoneValue); + } + + if ($showBirthdateField) { + $this->dateOfBirth(); + } + } + + protected function getOrderIdOnPayForOrderPage() + { + global $wp; + $orderId = absint($wp->query_vars['order-pay']); + return wc_get_order($orderId); + } + + protected function dateOfBirth() + { + ?> +

+ + + +

+ customer->get_billing_country(); + $countryCodes = [ + 'BE' => '+32xxxxxxxxx', + 'NL' => '+316xxxxxxxx', + 'DE' => '+49xxxxxxxxx', + 'AT' => '+43xxxxxxxxx', + ]; + $placeholder = in_array($country, array_keys($countryCodes)) ? $countryCodes[$country] : $countryCodes['NL']; + ?> +

+ + + + +

+ 'riverty', + 'defaultTitle' => __('Riverty', 'mollie-payments-for-woocommerce'), + 'settingsDescription' => __( + 'To accept payments via Riverty, all default WooCommerce checkout fields should be enabled and required.', + 'mollie-payments-for-woocommerce' + ), + 'defaultDescription' => '', + 'paymentFields' => true, + 'additionalFields' => ['birthdate', 'phone'], + 'instructions' => false, + 'supports' => [ + 'products', + 'refunds', + ], + 'filtersOnBuild' => true, + 'confirmationDelayed' => false, + 'SEPA' => false, + 'orderMandatory' => true, + 'phonePlaceholder' => __('Please enter your phone here. +316xxxxxxxx', 'mollie-payments-for-woocommerce'), + 'birthdatePlaceholder' => __('Please enter your birthdate here.', 'mollie-payments-for-woocommerce'), + ]; + } + + public function getFormFields($generalFormFields): array + { + return $generalFormFields; + } + + public function filtersOnBuild() + { + add_action( + 'woocommerce_checkout_posted_data', + [$this, 'switchFields'], + 11 + ); + add_action('woocommerce_rest_checkout_process_payment_with_context', [$this, 'addPhoneWhenRest'], 11); + add_action('woocommerce_rest_checkout_process_payment_with_context', [$this, 'addBirthdateWhenRest'], 11); + add_action('woocommerce_before_pay_action', [$this, 'fieldsMandatoryPayForOrder'], 11); + } + + /** + * @param $order + */ + public function fieldsMandatoryPayForOrder($order) + { + $paymentMethod = filter_input(INPUT_POST, 'payment_method', FILTER_SANITIZE_SPECIAL_CHARS) ?? false; + + if ($paymentMethod !== 'mollie_wc_gateway_riverty') { + return; + } + + $phoneValue = filter_input(INPUT_POST, 'billing_phone_riverty', FILTER_SANITIZE_SPECIAL_CHARS) ?? false; + $phoneValid = $phoneValue || null; + + if ($phoneValid) { + $order->set_billing_phone($phoneValue); + } + } + + public function switchFields($data) + { + if (isset($data['payment_method']) && $data['payment_method'] === 'mollie_wc_gateway_riverty') { + $fieldName = 'billing_phone_' . $this->getConfig()['id']; + $fieldPosted = filter_input(INPUT_POST, $fieldName, FILTER_SANITIZE_SPECIAL_CHARS) ?? false; + if (!empty($fieldPosted)) { + $data['billing_phone'] = $fieldPosted; + } + } + return $data; + } + + public function addPhoneWhenRest($arrayContext) + { + $context = $arrayContext; + $phoneMandatoryGateways = ['mollie_wc_gateway_riverty']; + $paymentMethod = $context->payment_data['payment_method'] ?? null; + if ($paymentMethod && in_array($paymentMethod, $phoneMandatoryGateways)) { + $billingPhone = $context->order->get_billing_phone(); + if (!empty($billingPhone)) { + return; + } + + $billingPhone = $context->payment_data['billing_phone'] ?? null; + if ($billingPhone) { + $context->order->set_billing_phone($billingPhone); + $context->order->save(); + } + } + } + + /** + * @throws RouteException + */ + public function addBirthdateWhenRest($context) + { + $paymentMethod = $context->payment_data['payment_method'] ?? null; + if ($paymentMethod) { + $billingBirthdate = $context->payment_data['billing_birthdate'] ?? null; + if ($billingBirthdate && $this->isBirthValid($billingBirthdate)) { + $context->order->update_meta_data('billing_birthdate', $billingBirthdate); + $context->order->save(); + } + } + } + + private function isBirthValid($billing_birthdate): bool + { + return isMollieBirthValid($billing_birthdate); + } +} diff --git a/src/Shared/SharedDataDictionary.php b/src/Shared/SharedDataDictionary.php index 3519fdde..10cf5c0e 100644 --- a/src/Shared/SharedDataDictionary.php +++ b/src/Shared/SharedDataDictionary.php @@ -36,6 +36,8 @@ class SharedDataDictionary 'Mollie_WC_Gateway_Bancomatpay', 'Mollie_WC_Gateway_Alma', 'Mollie_WC_Gateway_Trustly', + 'Mollie_WC_Gateway_Payconiq', + 'Mollie_WC_Gateway_Riverty', ]; public const MOLLIE_OPTIONS_NAMES = [ diff --git a/tests/php/Functional/ApplePayButton/DataToAppleButtonScriptsTest.php b/tests/php/Functional/ApplePayButton/DataToAppleButtonScriptsTest.php index 0638a28d..1d4c79ce 100644 --- a/tests/php/Functional/ApplePayButton/DataToAppleButtonScriptsTest.php +++ b/tests/php/Functional/ApplePayButton/DataToAppleButtonScriptsTest.php @@ -5,6 +5,7 @@ use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mollie\WooCommerce\Buttons\ApplePayButton\DataToAppleButtonScripts; use Mollie\WooCommerceTests\Stubs\postDTOTestsStubs; +use Mollie\WooCommerceTests\Stubs\WC_Product; use Mollie\WooCommerceTests\TestCase; use function Brain\Monkey\Functions\stubs; @@ -119,7 +120,7 @@ public function testApplePayScriptDataOnCart() private function wcProduct() { $item = $this->createConfiguredMock( - 'WC_Product', + WC_Product::class, [ 'get_price' => '1', 'get_type' => 'simple', diff --git a/tests/php/Functional/HelperMocks.php b/tests/php/Functional/HelperMocks.php index 61a80371..bb264c1d 100644 --- a/tests/php/Functional/HelperMocks.php +++ b/tests/php/Functional/HelperMocks.php @@ -10,6 +10,7 @@ use Mollie\WooCommerce\Payment\MollieOrder; use Mollie\WooCommerce\Payment\MollieOrderService; use Mollie\WooCommerce\Payment\OrderInstructionsService; +use Mollie\WooCommerce\Payment\OrderItemsRefunder; use Mollie\WooCommerce\Payment\OrderLines; use Mollie\WooCommerce\Payment\MollieObject; use Mollie\WooCommerce\Payment\PaymentFactory; @@ -100,6 +101,13 @@ public function mollieOrderService() ] ); } + + public function orderItemsRefunder() + { + return $this->getMockBuilder(OrderItemsRefunder::class) + ->disableOriginalConstructor() + ->getMock(); + } public function orderLines($apiClientMock){ return new OrderLines( $this->dataHelper($apiClientMock), diff --git a/tests/php/Functional/PayPalButton/DataToPayPalButtonScriptsTest.php b/tests/php/Functional/PayPalButton/DataToPayPalButtonScriptsTest.php index 44557a86..961c4c48 100644 --- a/tests/php/Functional/PayPalButton/DataToPayPalButtonScriptsTest.php +++ b/tests/php/Functional/PayPalButton/DataToPayPalButtonScriptsTest.php @@ -3,18 +3,13 @@ namespace Mollie\WooCommerceTests\Functional\PayPalButton; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; -use Mollie\Api\Endpoints\OrderEndpoint; -use Mollie\Api\Endpoints\WalletEndpoint; -use Mollie\Api\MollieApiClient; use Mollie\WooCommerce\Buttons\PayPalButton\DataToPayPal; use Mollie\WooCommerceTests\Stubs\postDTOTestsStubs; +use Mollie\WooCommerceTests\Stubs\WC_Product; use Mollie\WooCommerceTests\TestCase; use Mollie_WC_ApplePayButton_DataToAppleButtonScripts; -use Mollie_WC_Helper_Api; use Mollie_WC_ApplePayButton_DataObjectHttp; use Mollie_WC_Helper_ApplePayDirectHandler; -use Mollie_WC_Helper_Data; -use Mollie_WC_Payment_RefundLineItemsBuilder; use Mollie_WC_PayPalButton_DataToPayPalScripts; use PHPUnit_Framework_Exception; use PHPUnit_Framework_MockObject_MockObject; @@ -120,7 +115,7 @@ public function testApplePayScriptDataOnCart() private function wcProduct() { return $this->createConfiguredMock( - 'WC_Product', + WC_Product::class, [ 'get_price' => '1', 'get_type' => 'simple', diff --git a/tests/php/Functional/Payment/PaymentServiceTest.php b/tests/php/Functional/Payment/PaymentServiceTest.php index acf34319..f8107c8e 100644 --- a/tests/php/Functional/Payment/PaymentServiceTest.php +++ b/tests/php/Functional/Payment/PaymentServiceTest.php @@ -13,6 +13,7 @@ use Mollie\WooCommerceTests\Functional\HelperMocks; use Mollie\WooCommerceTests\Stubs\WC_Order_Item_Product; use Mollie\WooCommerceTests\Stubs\WC_Settings_API; +use Mollie\WooCommerceTests\Stubs\WC_Product; use Mollie\WooCommerceTests\TestCase; @@ -263,7 +264,7 @@ private function wcProduct() { $item = $this->createConfiguredMock( - 'WC_Product', + WC_Product::class, [ 'get_price' => '1', 'get_id'=>'1', diff --git a/tests/php/Functional/Payment/RequestObjectTest.php b/tests/php/Functional/Payment/RequestObjectTest.php new file mode 100644 index 00000000..221b1698 --- /dev/null +++ b/tests/php/Functional/Payment/RequestObjectTest.php @@ -0,0 +1,259 @@ + [ + 'id' => 1, + 'name' => 'product 1', + 'price' => '11.123' + ], + '2' => [ + 'id' => 2, + 'name' => 'product 2', + 'price' => '9.00' + ], + '3' => [ + 'id' => 3, + 'name' => 'product 3', + 'price' => '26.1234' + ], + '4' => [ + 'id' => 4, + 'name' => 'product 4', + 'price' => '30.01' + ] + ]; + + public function __construct($name = null, array $data = [], $dataName = '') + { + parent::__construct($name, $data, $dataName); + $this->helperMocks = new HelperMocks(); + } + + /** + * GIVEN A PAYMENT REQUEST + * WHEN THE TOTAL AMOUNT HAS DECIMALS + * THEN THE TOTAL AMOUNT HAS TO BE EQUAL TO THE SUM OF THE LINES + * + * + * @test + */ + public function processPayment_decimals_and_taxes_request_no_missmatch() + { + $testDataSet = $this->generateTestDataSet(); + + foreach ($testDataSet as $order) { + $this->executeTest($order); + } + } + + private function generateTestDataSet(): array + { + $products = $this->products; + return [ + $this->wcOrder('76.25', [ + $this->wcOrderItem($products['1']['id'], $products['1']['name'], $products['1']['price'], 1), + $this->wcOrderItem($products['2']['id'], $products['2']['name'], $products['2']['price'], 1), + $this->wcOrderItem($products['3']['id'], $products['3']['name'], $products['3']['price'], 1), + $this->wcOrderItem($products['4']['id'], $products['4']['name'], $products['4']['price'], 1) + ]), + $this->wcOrder('46.2464', [ + $this->wcOrderItem($products['1']['id'], $products['1']['name'], $products['1']['price'], 1), + $this->wcOrderItem($products['2']['id'], $products['2']['name'], $products['2']['price'], 1), + $this->wcOrderItem($products['3']['id'], $products['3']['name'], $products['3']['price'], 1) + ]), + $this->wcOrder('20.1234', [ + $this->wcOrderItem($products['1']['id'], $products['1']['name'], $products['1']['price'], 1), + $this->wcOrderItem($products['2']['id'], $products['2']['name'], $products['2']['price'], 1) + ]), + $this->wcOrder('11.123', [ + $this->wcOrderItem($products['1']['id'], $products['1']['name'], $products['1']['price'], 1) + ]) + ]; + } + + public function executeTest($order) + { + $customerId = 1; + $wrapperMock = $this->createMock(WC_Product::class); + + $callback = function ($productId) { + $price = $this->products[$productId]['price']; + $productMock = $this->wcProduct($productId, $price); + + return $productMock; + }; + $wrapperMock->method('getProduct')->willReturnCallback($callback); + + stubs([ + 'wc_get_payment_gateway_by_order' => $this->mollieGateway( + 'ideal', + $this->helperMocks->paymentService() + ), + 'add_query_arg' => 'https://webshop.example.org/wc-api/mollie_return?order_id=1&key=wc_order_hxZniP1zDcnM8', + 'WC' => $this->wooCommerce(), + 'get_option' => ['enabled' => false], + 'wc_get_product' => $wrapperMock, + 'wc_clean' => false + ]); + $apiClientMock = $this->createConfiguredMock( + MollieApiClient::class, + [] + ); + $orderLines = new OrderLines($this->helperMocks->dataHelper($apiClientMock), $this->helperMocks->pluginId()); + $testee = new MollieOrder( + $this->helperMocks->orderItemsRefunder(), + 'order', + $this->helperMocks->pluginId(), + $this->helperMocks->apiHelper($apiClientMock), + $this->helperMocks->settingsHelper(), + $this->helperMocks->dataHelper($apiClientMock), + $this->helperMocks->loggerMock(), + $orderLines + ); + + /* + * Execute Test + */ + + $arrayResult = $testee->getPaymentRequestData($order, $customerId); + $expectedResult = $this->noMissmatchError($arrayResult); + $this->assertTrue($expectedResult); + } + + + protected function setUp(): void + { + $_POST = []; + parent::setUp(); + + when('__')->returnArg(1); + } + + protected function mollieGateway($paymentMethodName, $testee, $isSepa = false, $isSubscription = false) + { + return $this->helperMocks->mollieGatewayBuilder($paymentMethodName, $isSepa, $isSubscription, [], $testee); + } + + + /** + * + * @param $total + * @param $items + * @return (object&MockObject)|MockObject|\WC_Order|(\WC_Order&object&MockObject)|(\WC_Order&MockObject) + */ + private function wcOrder($total, $items) + { + $item = $this->createConfiguredMock( + 'WC_Order', + [ + 'get_id' => 1, + 'get_order_key' => 'wc_order_hxZniP1zDcnM8', + 'get_total' => $total, + 'get_items' => $items, + 'get_billing_first_name' => 'billingggivenName', + 'get_billing_last_name' => 'billingfamilyName', + 'get_billing_email' => 'billingemail', + 'get_shipping_first_name' => 'shippinggivenName', + 'get_shipping_last_name' => 'shippingfamilyName', + 'get_billing_address_1' => 'shippingstreetAndNumber', + 'get_billing_address_2' => 'billingstreetAdditional', + 'get_billing_postcode' => 'billingpostalCode', + 'get_billing_city' => 'billingcity', + 'get_billing_state' => 'billingregion', + 'get_billing_country' => 'billingcountry', + 'get_billing_phone' => '+34345678900', + 'get_billing_company' => 'billingorganizationName', + 'get_shipping_address_1' => 'shippingstreetAndNumber', + 'get_shipping_address_2' => 'shippingstreetAdditional', + 'get_shipping_postcode' => 'shippingpostalCode', + 'get_shipping_city' => 'shippingcity', + 'get_shipping_state' => 'shippingregion', + 'get_shipping_country' => 'shippingcountry', + 'get_shipping_methods' => false, + 'get_order_number' => 1, + 'get_payment_method' => 'mollie_wc_gateway_ideal', + 'get_currency' => 'EUR', + ] + ); + + return $item; + } + + /** + * + * @return PHPUnit_Framework_MockObject_MockObject + * @throws PHPUnit_Framework_Exception + */ + public function wooCommerce() + { + $item = $this->createConfiguredMock( + 'WooCommerce', + [ + 'api_request_url' => 'https://webshop.example.org/wc-api/mollie_return' + ] + ); + + return $item; + } + + /** + * + * @return PHPUnit_Framework_MockObject_MockObject + * @throws PHPUnit_Framework_Exception + */ + private function wcOrderItem($id, $name, $price, $quantity) + { + $item = new \WC_Order_Item_Product(); + + $item['quantity'] = $quantity; + $item['variation_id'] = null; + $item['product_id'] = $id; + $item['line_subtotal_tax'] = 0; + $item['line_total'] = $price; + $item['line_subtotal'] = $price; + $item['line_tax'] = 0; + $item['tax_status'] = ''; + $item['total'] = $price; + $item['name'] = $name; + + return $item; + } + + private function noMissmatchError(array $arrayResult) + { + //array result total equals the sum of the lines + $total = ($arrayResult['amount']['value']) * 1000; + $lines = $arrayResult['lines']; + $sum = 0.0; + foreach ($lines as $line) { + $lineValue = ($line['totalAmount']['value']) * 1000; + $sum += $lineValue; + } + return $total == $sum; + } +} + + + diff --git a/tests/php/Stubs/WC_Product.php b/tests/php/Stubs/WC_Product.php new file mode 100644 index 00000000..c36dbde7 --- /dev/null +++ b/tests/php/Stubs/WC_Product.php @@ -0,0 +1,44 @@ +