From 3b1e75a00f73446dca947d39e5a8b6e58e8e5551 Mon Sep 17 00:00:00 2001 From: Matic Luznar Date: Tue, 1 Aug 2023 09:13:23 +0200 Subject: [PATCH 01/23] Show notice to user if payment is not successful --- src/Gateway/MolliePaymentGateway.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index f71438a8..a176fd44 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -570,6 +570,12 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string if ($order_status_cancelled_payments === 'cancelled') { return $this->get_return_url($order); } else { + + wc_add_notice(__( + 'You have cancelled your payment. Please complete your order with a different payment method.', + 'mollie-payments-for-woocommerce' + ), "error"); + $this->notice->addNotice( 'notice', __( @@ -591,6 +597,12 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string && !$payment->isPaid() && !$payment->isAuthorized() ) { + + wc_add_notice(__( + 'Your payment was not successful. Please complete your order with a different payment method.', + 'mollie-payments-for-woocommerce' + ), "error"); + $this->notice->addNotice( 'notice', __( @@ -605,6 +617,12 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string $this->paymentMethod->debugGiftcardDetails($payment, $order); } } catch (UnexpectedValueException $exc) { + + wc_add_notice(__( + 'Your payment was not successful. Please complete your order with a different payment method.', + 'mollie-payments-for-woocommerce' + ), "error"); + $this->notice->addNotice( 'notice', __( From c93a39ba3a0f233fb62047e4fbfca3c355c55204 Mon Sep 17 00:00:00 2001 From: Matic Luznar Date: Tue, 1 Aug 2023 09:25:04 +0200 Subject: [PATCH 02/23] Fix spacing phpcs errors --- src/Gateway/MolliePaymentGateway.php | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index a176fd44..32117df6 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -570,12 +570,7 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string if ($order_status_cancelled_payments === 'cancelled') { return $this->get_return_url($order); } else { - - wc_add_notice(__( - 'You have cancelled your payment. Please complete your order with a different payment method.', - 'mollie-payments-for-woocommerce' - ), "error"); - + wc_add_notice(__('You have cancelled your payment. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce'), "error"); $this->notice->addNotice( 'notice', __( @@ -583,7 +578,6 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string 'mollie-payments-for-woocommerce' ) ); - // Return to order payment page return $failedRedirect; } @@ -597,12 +591,7 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string && !$payment->isPaid() && !$payment->isAuthorized() ) { - - wc_add_notice(__( - 'Your payment was not successful. Please complete your order with a different payment method.', - 'mollie-payments-for-woocommerce' - ), "error"); - + wc_add_notice(__('Your payment was not successful. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce'), "error"); $this->notice->addNotice( 'notice', __( @@ -617,12 +606,7 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string $this->paymentMethod->debugGiftcardDetails($payment, $order); } } catch (UnexpectedValueException $exc) { - - wc_add_notice(__( - 'Your payment was not successful. Please complete your order with a different payment method.', - 'mollie-payments-for-woocommerce' - ), "error"); - + wc_add_notice(__('Your payment was not successful. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce'), "error"); $this->notice->addNotice( 'notice', __( From bd741a7933119ffe6a4dbef6eeee09c766212bd3 Mon Sep 17 00:00:00 2001 From: Matic Luznar Date: Tue, 1 Aug 2023 11:40:59 +0200 Subject: [PATCH 03/23] Create FrontendNotice class and use it in the MolliePaymentGateway module. --- src/Gateway/GatewayModule.php | 5 +++-- src/Gateway/MolliePaymentGateway.php | 9 +++------ src/Notice/FrontendNotice.php | 13 +++++++++++++ src/Notice/NoticeModule.php | 6 +++--- 4 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 src/Notice/FrontendNotice.php diff --git a/src/Gateway/GatewayModule.php b/src/Gateway/GatewayModule.php index b4c8a062..a7411952 100644 --- a/src/Gateway/GatewayModule.php +++ b/src/Gateway/GatewayModule.php @@ -18,6 +18,7 @@ use Mollie\WooCommerce\Buttons\PayPalButton\PayPalButtonHandler; use Mollie\WooCommerce\Gateway\Voucher\MaybeDisableGateway; use Mollie\WooCommerce\Notice\AdminNotice; +use Mollie\WooCommerce\Notice\FrontendNotice; use Mollie\WooCommerce\Notice\NoticeInterface; use Mollie\WooCommerce\Payment\MollieObject; use Mollie\WooCommerce\Payment\MollieOrderService; @@ -495,8 +496,8 @@ public function instantiatePaymentMethodGateways(ContainerInterface $container): { $logger = $container->get(Logger::class); assert($logger instanceof Logger); - $notice = $container->get(AdminNotice::class); - assert($notice instanceof AdminNotice); + $notice = $container->get(FrontendNotice::class); + assert($notice instanceof FrontendNotice); $paymentService = $container->get(PaymentService::class); assert($paymentService instanceof PaymentService); $mollieOrderService = $container->get(MollieOrderService::class); diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index 32117df6..c0992203 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -570,9 +570,8 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string if ($order_status_cancelled_payments === 'cancelled') { return $this->get_return_url($order); } else { - wc_add_notice(__('You have cancelled your payment. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce'), "error"); $this->notice->addNotice( - 'notice', + 'error', __( 'You have cancelled your payment. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce' @@ -591,9 +590,8 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string && !$payment->isPaid() && !$payment->isAuthorized() ) { - wc_add_notice(__('Your payment was not successful. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce'), "error"); $this->notice->addNotice( - 'notice', + 'error', __( 'Your payment was not successful. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce' @@ -606,9 +604,8 @@ public function getReturnRedirectUrlForOrder(WC_Order $order): string $this->paymentMethod->debugGiftcardDetails($payment, $order); } } catch (UnexpectedValueException $exc) { - wc_add_notice(__('Your payment was not successful. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce'), "error"); $this->notice->addNotice( - 'notice', + 'error', __( 'Your payment was not successful. Please complete your order with a different payment method.', 'mollie-payments-for-woocommerce' diff --git a/src/Notice/FrontendNotice.php b/src/Notice/FrontendNotice.php new file mode 100644 index 00000000..02c59bef --- /dev/null +++ b/src/Notice/FrontendNotice.php @@ -0,0 +1,13 @@ + static function (): AdminNotice { return new AdminNotice(); }, + FrontendNotice::class => static function (): FrontendNotice { + return new FrontendNotice(); + }, ]; } } From a65635504661094b20327e802b26556070523c48 Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Tue, 12 Sep 2023 13:22:27 +0200 Subject: [PATCH 04/23] Check if WC_Subscription class exists instead the subscription plugin file. --- src/Shared/Data.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/Data.php b/src/Shared/Data.php index 536b2866..3cb509e5 100644 --- a/src/Shared/Data.php +++ b/src/Shared/Data.php @@ -77,7 +77,7 @@ public function isBlockPluginActive(): bool public function isSubscriptionPluginActive(): bool { - $subscriptionPlugin = is_plugin_active('woocommerce-subscriptions/woocommerce-subscriptions.php'); + $subscriptionPlugin = class_exists('WC_Subscriptions'); return apply_filters('mollie_wc_subscription_plugin_active', $subscriptionPlugin); } From 3e7c7563d0f31b969e6e7a9d94239b7ceaf73b8c Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Tue, 19 Sep 2023 13:48:58 +0200 Subject: [PATCH 05/23] Check if surcharge array key exists. --- src/Gateway/Surcharge.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Gateway/Surcharge.php b/src/Gateway/Surcharge.php index 17340bf4..8bb077a0 100644 --- a/src/Gateway/Surcharge.php +++ b/src/Gateway/Surcharge.php @@ -105,6 +105,10 @@ public function aboveMaxLimit(float $totalAmount, array $gatewaySettings): bool */ public function calculateFeeAmount(WC_Cart $cart, array $gatewaySettings): float { + if (!isset($gatewaySettings['payment_surcharge'])) { + return 0.0; + } + $surchargeType = $gatewaySettings['payment_surcharge']; $methodName = sprintf('calculate_%s', $surchargeType); @@ -154,7 +158,7 @@ protected function calculate_no_fee(WC_Cart $cart, array $gatewaySettings): floa */ protected function calculate_fixed_fee($cart, array $gatewaySettings) { - return !empty($gatewaySettings[Surcharge::FIXED_FEE]) ? (float) $gatewaySettings[Surcharge::FIXED_FEE] : 0.0; + return !empty($gatewaySettings[Surcharge::FIXED_FEE]) ? (float)$gatewaySettings[Surcharge::FIXED_FEE] : 0.0; } /** @@ -294,7 +298,12 @@ protected function name_fixed_fee_percentage($paymentMethod) $currency = get_woocommerce_currency_symbol(); $amountPercent = $paymentMethod->getProperty(self::PERCENTAGE); /* translators: Placeholder 1: Fee amount tag. Placeholder 2: Currency. Placeholder 3: Percentage amount. */ - return sprintf(__(' + %1$s %2$s + %3$s%% fee might apply', 'mollie-payments-for-woocommerce'), $currency, $amountFix, $amountPercent); + return sprintf( + __(' + %1$s %2$s + %3$s%% fee might apply', 'mollie-payments-for-woocommerce'), + $currency, + $amountFix, + $amountPercent + ); } /** From b6860866207a0d34e17edf09803d8dbfee9a8f87 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 17 Oct 2023 17:44:11 +0200 Subject: [PATCH 06/23] Add details to customer email --- src/Gateway/MolliePaymentGateway.php | 43 ++++++++++++++++++---------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index f71438a8..d45d98e9 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -30,7 +30,8 @@ class MolliePaymentGateway extends WC_Payment_Gateway implements MolliePaymentGa /** * @var bool */ - protected static $alreadyDisplayedInstructions = false; + protected static $alreadyDisplayedAdminInstructions = false; + protected static $alreadyDisplayedCustomerInstructions = false; /** * Recurring total, zero does not define a recurring total * @@ -115,7 +116,7 @@ public function __construct( $this->paymentFactory = $paymentFactory; $this->pluginId = $pluginId; - // No plugin id, gateway id is unique enough + // No plugin id, gateway id is unique enough $this->plugin_id = ''; // Use gateway class name as gateway id $this->gatewayId(); @@ -156,6 +157,12 @@ public function __construct( 10, 3 ); + add_action( + 'woocommerce_email_order_meta', + [$this, 'displayInstructions'], + 10, + 3 + ); // Adjust title and text on Order Received page in some cases, see issue #166 add_filter('the_title', [$this, 'onOrderReceivedTitle'], 10, 2); @@ -267,17 +274,17 @@ public function isValidForUse(): bool $test_mode = $this->dataService->isTestModeEnabled(); $this->errors[] = ($test_mode ? __( - 'Test mode enabled.', - 'mollie-payments-for-woocommerce' - ) . ' ' : '') . sprintf( + 'Test mode enabled.', + 'mollie-payments-for-woocommerce' + ) . ' ' : '') . sprintf( /* translators: The surrounding %s's Will be replaced by a link to the global setting page */ - __( - 'No API key provided. Please %1$sset you Mollie API key%2$s first.', - 'mollie-payments-for-woocommerce' - ), - '', - '' - ); + __( + 'No API key provided. Please %1$sset you Mollie API key%2$s first.', + 'mollie-payments-for-woocommerce' + ), + '', + '' + ); return false; } @@ -806,8 +813,9 @@ public function displayInstructions( $admin_instructions = false, $plain_text = false ) { - - if (!$this::$alreadyDisplayedInstructions) { + if (($admin_instructions && !$this::$alreadyDisplayedAdminInstructions) + || (!$admin_instructions && !$this::$alreadyDisplayedCustomerInstructions) + ) { $order_payment_method = $order->get_payment_method(); // Invalid gateway @@ -846,7 +854,12 @@ public function displayInstructions( } } } - $this::$alreadyDisplayedInstructions = true; + if ($admin_instructions && !$this::$alreadyDisplayedAdminInstructions) { + $this::$alreadyDisplayedAdminInstructions = true; + } + if (!$admin_instructions && !$this::$alreadyDisplayedCustomerInstructions) { + $this::$alreadyDisplayedCustomerInstructions = true; + } } /** From a92bdd011a0bb96c073146ff44d5e39eca7aea37 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Wed, 18 Oct 2023 12:56:37 +0200 Subject: [PATCH 07/23] Change status on webhook completed & cbf --- .../ApplePayButton/ApplePayDataObjectHttp.php | 2 +- src/Payment/MollieOrder.php | 7 +++++++ src/Payment/MollieOrderService.php | 17 +++++++++-------- src/PaymentMethods/Kbc.php | 14 +++++++------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php b/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php index 38f91294..db7f6919 100644 --- a/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php +++ b/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php @@ -326,7 +326,7 @@ protected function addressHasRequiredFieldsValues( sprintf('ApplePay Data Error: Missing value for %s', $requiredField) ); $this->errors[] - = [ + = [ 'errorCode' => $errorCode, 'contactField' => $errorValue, ]; diff --git a/src/Payment/MollieOrder.php b/src/Payment/MollieOrder.php index 7f63813b..cb61dbc0 100644 --- a/src/Payment/MollieOrder.php +++ b/src/Payment/MollieOrder.php @@ -371,6 +371,13 @@ public function onWebhookCompleted(WC_Order $order, $payment, $paymentMethodTitl if ($payment->method === 'paypal') { $this->addPaypalTransactionIdToOrder($order); } + add_filter('woocommerce_valid_order_statuses_for_payment_complete', static function ($statuses) { + $statuses[] = 'processing'; + return $statuses; + }); + add_filter('woocommerce_payment_complete_order_status', static function ($status) use ($order) { + return $order->get_status() === 'processing' ? 'completed' : $status; + }); $order->payment_complete($payment->id); // Add messages to log $this->logger->debug(__METHOD__ . ' WooCommerce payment_complete() processed and returned to ' . __METHOD__ . ' for order ' . $orderId); diff --git a/src/Payment/MollieOrderService.php b/src/Payment/MollieOrderService.php index 898c0686..0c7ba253 100644 --- a/src/Payment/MollieOrderService.php +++ b/src/Payment/MollieOrderService.php @@ -80,7 +80,7 @@ public function onWebhookAction() $data_helper = $this->data; $order = wc_get_order($order_id); - if (!$order) { + if (!$order instanceof WC_Order) { $this->httpResponse->setHttpResponseCode(404); $this->logger->debug(__METHOD__ . ": Could not find order $order_id."); return; @@ -135,7 +135,11 @@ public function onWebhookAction() // Log a message that webhook was called, doesn't mean the payment is actually processed $this->logger->debug($this->gateway->id . ": Mollie payment object {$payment->id} (" . $payment->mode . ") webhook call for order {$order->get_id()}.", [true]); + // Get payment method title + $payment_method_title = $this->getPaymentMethodTitle($payment); + // Create the method name based on the payment status + $method_name = 'onWebhook' . ucfirst($payment->status); // Order does not need a payment if (! $this->orderNeedsPayment($order)) { // TODO David: move to payment object? @@ -145,7 +149,10 @@ public function onWebhookAction() // Check and process a possible refund or chargeback $this->processRefunds($order, $payment); $this->processChargebacks($order, $payment); - + //if the order gets updated to completed at mollie, we need to update the order status + if ($order->get_status() === 'processing' && $payment->isCompleted() && method_exists($payment_object, 'onWebhookCompleted')) { + $payment_object->onWebhookCompleted($order, $payment, $payment_method_title); + } return; } @@ -154,12 +161,6 @@ public function onWebhookAction() $this->setBillingAddressAfterPayment($payment, $order); } - // Get payment method title - $payment_method_title = $this->getPaymentMethodTitle($payment); - - // Create the method name based on the payment status - $method_name = 'onWebhook' . ucfirst($payment->status); - if (method_exists($payment_object, $method_name)) { $payment_object->{$method_name}($order, $payment, $payment_method_title); } else { diff --git a/src/PaymentMethods/Kbc.php b/src/PaymentMethods/Kbc.php index 361116ed..5c220cac 100644 --- a/src/PaymentMethods/Kbc.php +++ b/src/PaymentMethods/Kbc.php @@ -6,10 +6,10 @@ class Kbc extends AbstractPaymentMethod implements PaymentMethodI { -protected const DEFAULT_ISSUERS_DROPDOWN = 'yes'; -protected function getConfig(): array -{ - return [ + protected const DEFAULT_ISSUERS_DROPDOWN = 'yes'; + protected function getConfig(): array + { + return [ 'id' => 'kbc', 'defaultTitle' => __('KBC/CBC Payment Button', 'mollie-payments-for-woocommerce'), 'settingsDescription' => '', @@ -23,10 +23,10 @@ protected function getConfig(): array 'filtersOnBuild' => false, 'confirmationDelayed' => true, 'SEPA' => true, - ]; -} + ]; + } -public function getFormFields($generalFormFields): array + public function getFormFields($generalFormFields): array { $paymentMethodFormFieds = [ 'issuers_dropdown_shown' => [ From 17bf7e47092719461280e14618a953ee1234630a Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Wed, 18 Oct 2023 15:57:19 +0200 Subject: [PATCH 08/23] Check 422 fraud exception and bail --- .../ApplePayButton/ApplePayDataObjectHttp.php | 2 +- src/Payment/PaymentService.php | 25 +++++++++++++++++++ src/PaymentMethods/Kbc.php | 14 +++++------ src/SDK/Api.php | 11 ++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php b/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php index 38f91294..db7f6919 100644 --- a/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php +++ b/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php @@ -326,7 +326,7 @@ protected function addressHasRequiredFieldsValues( sprintf('ApplePay Data Error: Missing value for %s', $requiredField) ); $this->errors[] - = [ + = [ 'errorCode' => $errorCode, 'contactField' => $errorValue, ]; diff --git a/src/Payment/PaymentService.php b/src/Payment/PaymentService.php index ea7b53f3..1e56d636 100644 --- a/src/Payment/PaymentService.php +++ b/src/Payment/PaymentService.php @@ -387,6 +387,8 @@ protected function processAsMollieOrder( } } catch (ApiException $e) { $this->handleMollieOutage($e); + //if exception is 422 do not try to create a payment + $this->handleMollieFraudRejection($e); // Don't try to create a Mollie Payment for Klarna payment methods $order_payment_method = $order->get_payment_method(); $orderMandatoryPaymentMethods = [ @@ -873,4 +875,27 @@ public function handleMollieOutage(ApiException $e): void ); } } + + /** + * Check if the exception is a fraud rejection, if so bail, log and inform user + * @param ApiException $e + * @return void + * @throws ApiException + */ + public function handleMollieFraudRejection(ApiException $e): void + { + $isMollieFraudException = $this->apiHelper->isMollieFraudException($e); + if ($isMollieFraudException) { + $this->logger->debug( + "Creating payment object: The payment was declined due to suspected fraud, stopping process." + ); + + throw new ApiException( + __( + 'Payment failed due to: The payment was declined due to suspected fraud.', + 'mollie-payments-for-woocommerce' + ) + ); + } + } } diff --git a/src/PaymentMethods/Kbc.php b/src/PaymentMethods/Kbc.php index 361116ed..5c220cac 100644 --- a/src/PaymentMethods/Kbc.php +++ b/src/PaymentMethods/Kbc.php @@ -6,10 +6,10 @@ class Kbc extends AbstractPaymentMethod implements PaymentMethodI { -protected const DEFAULT_ISSUERS_DROPDOWN = 'yes'; -protected function getConfig(): array -{ - return [ + protected const DEFAULT_ISSUERS_DROPDOWN = 'yes'; + protected function getConfig(): array + { + return [ 'id' => 'kbc', 'defaultTitle' => __('KBC/CBC Payment Button', 'mollie-payments-for-woocommerce'), 'settingsDescription' => '', @@ -23,10 +23,10 @@ protected function getConfig(): array 'filtersOnBuild' => false, 'confirmationDelayed' => true, 'SEPA' => true, - ]; -} + ]; + } -public function getFormFields($generalFormFields): array + public function getFormFields($generalFormFields): array { $paymentMethodFormFieds = [ 'issuers_dropdown_shown' => [ diff --git a/src/SDK/Api.php b/src/SDK/Api.php index 7b7cd5fb..d3526fb7 100644 --- a/src/SDK/Api.php +++ b/src/SDK/Api.php @@ -82,4 +82,15 @@ public function isMollieOutageException(\Mollie\Api\Exceptions\ApiException $e): } return false; } + + public function isMollieFraudException(\Mollie\Api\Exceptions\ApiException $e): bool + { + $isFraudCode = $e->getCode() === 422; + $isFraudMessage = strpos($e->getMessage(), 'The payment was declined due to suspected fraud') !== false; + + if ($isFraudCode && $isFraudMessage) { + return true; + } + return false; + } } From 1b842f667fe3b670a75064500b0aeb5a5e9bd946 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Thu, 19 Oct 2023 15:53:21 +0200 Subject: [PATCH 09/23] Update wiki and support links --- src/Settings/Page/MollieSettingsPage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Settings/Page/MollieSettingsPage.php b/src/Settings/Page/MollieSettingsPage.php index 03f4c402..fd265b69 100644 --- a/src/Settings/Page/MollieSettingsPage.php +++ b/src/Settings/Page/MollieSettingsPage.php @@ -146,10 +146,10 @@ public function addGlobalSettingsFields(array $settings): array ) . '

'; $presentation = '' - . '
' . __( + . '' From 4069e886996b76434d33707b21b15abb443c6a0c Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Fri, 20 Oct 2023 11:28:27 +0200 Subject: [PATCH 10/23] Add metabox to show payment details --- src/Gateway/GatewayModule.php | 29 +++++++++++++++++++++++++++- src/Gateway/MolliePaymentGateway.php | 6 ++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Gateway/GatewayModule.php b/src/Gateway/GatewayModule.php index bc5b2a18..1e1a1004 100644 --- a/src/Gateway/GatewayModule.php +++ b/src/Gateway/GatewayModule.php @@ -6,6 +6,7 @@ namespace Mollie\WooCommerce\Gateway; +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; @@ -39,6 +40,7 @@ use Mollie\WooCommerce\PaymentMethods\Constants; use Mollie\WooCommerce\Vendor\Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface as Logger; +use WP_Post; class GatewayModule implements ServiceModule, ExecutableModule { @@ -301,10 +303,35 @@ static function ($paymentContext) { $order->save(); } ); - + add_action('add_meta_boxes_woocommerce_page_wc-orders', [$this, 'addShopOrderMetabox'], 10); return true; } + /** + * @param Object $post + * @return void + */ + public function addShopOrderMetabox(object $post) + { + if (! $post instanceof \WC_Order) { + return; + } + $meta = $post->get_meta('_mollie_payment_instructions'); + if (empty($meta)) { + return; + } + $screen = wc_get_container()->get(CustomOrdersTableController::class)->custom_orders_table_usage_is_enabled() + ? wc_get_page_screen_id('shop-order') + : 'shop_order'; + add_meta_box('mollie_order_details', __('Mollie Payment Details', 'mollie-payments-for-woocommerce'), static function () use ($meta) { + $allowedTags = ['strong' => []]; + printf( + '

%s

', + wp_kses($meta, $allowedTags) + ); + }, $screen, 'side', 'high'); + } + /** * Disable Bank Transfer Gateway * diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index f71438a8..6f3b6600 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -836,6 +836,12 @@ public function displayInstructions( if (!empty($instructions)) { $instructions = wptexturize($instructions); + //save instructions in order meta + $order->update_meta_data( + '_mollie_payment_instructions', + $instructions + ); + $order->save(); if ($plain_text) { echo esc_html($instructions) . PHP_EOL; From ce1b8981ef6da32418f45f9ea86c0a2ad3c3aedd Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Fri, 20 Oct 2023 11:51:33 +0200 Subject: [PATCH 11/23] update initial_order_status description --- src/Settings/General/MollieGeneralSettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Settings/General/MollieGeneralSettings.php b/src/Settings/General/MollieGeneralSettings.php index b0d018b9..7ac6b9d7 100644 --- a/src/Settings/General/MollieGeneralSettings.php +++ b/src/Settings/General/MollieGeneralSettings.php @@ -290,7 +290,7 @@ public function gatewayFormFields( /* translators: Placeholder 1: Default order status, placeholder 2: Link to 'Hold Stock' setting */ 'description' => sprintf( __( - 'Some payment methods take longer than a few hours to complete. The initial order state is then set to \'%1$s\'. This ensures the order is not cancelled when the setting %2$s is used.', + 'Some payment methods take longer than a few hours to complete. The initial order state is then set to \'%1$s\'. This ensures the order is not cancelled when the setting %2$s is used. This will also prevent the order to be canceled when expired.', 'mollie-payments-for-woocommerce' ), wc_get_order_status_name( From ddd3d2cbcebb55268eb17444391a648fd71576e6 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Fri, 20 Oct 2023 17:45:20 +0200 Subject: [PATCH 12/23] Add unit test to check exception --- tests/php/Functional/HelperMocks.php | 7 ++ .../Functional/Payment/PaymentServiceTest.php | 69 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/tests/php/Functional/HelperMocks.php b/tests/php/Functional/HelperMocks.php index 9ea1a44a..61a80371 100644 --- a/tests/php/Functional/HelperMocks.php +++ b/tests/php/Functional/HelperMocks.php @@ -7,6 +7,7 @@ use Mollie\Api\MollieApiClient; use Mollie\WooCommerce\Gateway\MolliePaymentGateway; use Mollie\WooCommerce\Notice\AdminNotice; +use Mollie\WooCommerce\Payment\MollieOrder; use Mollie\WooCommerce\Payment\MollieOrderService; use Mollie\WooCommerce\Payment\OrderInstructionsService; use Mollie\WooCommerce\Payment\OrderLines; @@ -59,6 +60,12 @@ public function paymentFactory($apiClientMock){ ); } + public function mollieOrderMock() + { + return $this->getMockBuilder(MollieOrder::class) + ->disableOriginalConstructor() + ->getMock(); + } public function noticeMock() { return $this->getMockBuilder(AdminNotice::class) diff --git a/tests/php/Functional/Payment/PaymentServiceTest.php b/tests/php/Functional/Payment/PaymentServiceTest.php index b8c536b3..3ef13e93 100644 --- a/tests/php/Functional/Payment/PaymentServiceTest.php +++ b/tests/php/Functional/Payment/PaymentServiceTest.php @@ -3,6 +3,7 @@ namespace Mollie\WooCommerceTests\Functional\Payment; use Mollie\Api\Endpoints\OrderEndpoint; +use Mollie\Api\Exceptions\ApiException; use Mollie\Api\MollieApiClient; use Mollie\WooCommerce\Gateway\MolliePaymentGateway; use Mollie\WooCommerce\Payment\PaymentCheckoutRedirectService; @@ -129,6 +130,64 @@ public function processPayment_Order_success(){ self::assertEquals($expectedResult, $arrayResult); } + /** + * @test + * @throws ApiException + */ + public function processAsMollieOrder_BailsIf_FraudException() + { + stubs([ + 'array_filter' => [], + ]); + $mockedException = new TestApiException(); + $mockedException->setTestCode(422); + $mockedException->setTestMessage('The payment was declined due to suspected fraud'); + $orderEndpointsMock = $this->createConfiguredMock( + OrderEndpoint::class, + [ + 'create' => new MollieOrderResponse(), + ] + ); + $paymentMethodId = 'Ideal'; + $orderEndpointsMock->method('create')->with([]) + ->will($this->throwException($mockedException)); + + $apiClientMock = $this->createMock(MollieApiClient::class); + $apiClientMock->orders = $orderEndpointsMock; + $voucherDefaultCategory = Voucher::NO_CATEGORY; + + $testee = new PaymentService( + $this->helperMocks->noticeMock(), + $this->helperMocks->loggerMock(), + $this->helperMocks->paymentFactory($apiClientMock), + $this->helperMocks->dataHelper($apiClientMock), + $this->helperMocks->apiHelper($apiClientMock), + $this->helperMocks->settingsHelper(), + $this->helperMocks->pluginId(), + $this->paymentCheckoutService($apiClientMock), + $voucherDefaultCategory + ); + $gateway = $this->mollieGateway($paymentMethodId, $testee); + $testee->setGateway($gateway); + $wcOrderId = 1; + $wcOrderKey = 'wc_order_hxZniP1zDcnM8'; + $wcOrder = $this->wcOrder($wcOrderId, $wcOrderKey); + $cusomerId = 1; + $apiKey = 'test_test'; + $method = new \ReflectionMethod(PaymentService::class, 'processAsMollieOrder'); + $method->setAccessible(true); + + $this->expectException(ApiException::class); + + $method->invoke( + $testee, + $this->helperMocks->mollieOrderMock(), + $wcOrder, + $cusomerId, + $apiKey + ); + } + protected function setUp(): void { @@ -451,5 +510,15 @@ public function getCheckoutUrl() } +class TestApiException extends \Mollie\Api\Exceptions\ApiException { + public function setTestCode($code) { + $this->code = $code; + } + + public function setTestMessage($message) { + $this->message = $message; + } +} + From c2077b45e56069f043aa14092912ab919a112953 Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Tue, 24 Oct 2023 09:52:48 +0200 Subject: [PATCH 13/23] Capture payment manually --- inc/settings/mollie_advanced_settings.php | 30 +++ mollie-payments-for-woocommerce.php | 2 + resources/js/advancedSettings.js | 54 ++++- .../ApplePayButton/ApplePayDataObjectHttp.php | 2 +- .../Action/AbstractPaymentCaptureAction.php | 40 ++++ .../Capture/Action/CapturePayment.php | 65 ++++++ .../Capture/Action/VoidPayment.php | 34 +++ .../Capture/Type/ManualCapture.php | 51 +++++ .../Capture/Type/StateChangeCapture.php | 55 +++++ src/MerchantCapture/ManualCaptureStatus.php | 14 ++ src/MerchantCapture/MerchantCaptureModule.php | 194 ++++++++++++++++++ .../OrderListPaymentColumn.php | 56 +++++ src/MerchantCapture/UI/OrderActionBlock.php | 19 ++ src/MerchantCapture/UI/StatusButton.php | 15 ++ src/MerchantCapture/UI/StatusRenderer.php | 36 ++++ src/Payment/MollieOrderService.php | 1 + src/Payment/MolliePayment.php | 4 + src/Payment/PaymentModule.php | 2 +- src/Payment/PaymentService.php | 7 +- src/PaymentMethods/Kbc.php | 14 +- src/SDK/WordPressHttpAdapter.php | 3 +- 21 files changed, 677 insertions(+), 21 deletions(-) create mode 100644 src/MerchantCapture/Capture/Action/AbstractPaymentCaptureAction.php create mode 100644 src/MerchantCapture/Capture/Action/CapturePayment.php create mode 100644 src/MerchantCapture/Capture/Action/VoidPayment.php create mode 100644 src/MerchantCapture/Capture/Type/ManualCapture.php create mode 100644 src/MerchantCapture/Capture/Type/StateChangeCapture.php create mode 100644 src/MerchantCapture/ManualCaptureStatus.php create mode 100644 src/MerchantCapture/MerchantCaptureModule.php create mode 100644 src/MerchantCapture/OrderListPaymentColumn.php create mode 100644 src/MerchantCapture/UI/OrderActionBlock.php create mode 100644 src/MerchantCapture/UI/StatusButton.php create mode 100644 src/MerchantCapture/UI/StatusRenderer.php diff --git a/inc/settings/mollie_advanced_settings.php b/inc/settings/mollie_advanced_settings.php index 31750d6b..e4026f49 100644 --- a/inc/settings/mollie_advanced_settings.php +++ b/inc/settings/mollie_advanced_settings.php @@ -199,6 +199,36 @@ class="mollie-settings-advanced-payment-desc-label button button-secondary butto __('Clear now', 'mollie-payments-for-woocommerce') ) . ')', ], + [ + 'id' => $pluginName . '_place_payment_onhold', + 'title' => __('Placing payments on Hold', 'mollie-payments-for-woocommerce'), + 'type' => 'select', + 'desc_tip' => true, + 'options' => [ + 'immediate_capture' => __('Capture payments immediately', 'mollie-payments-for-woocommerce'), + 'later_capture' => __('Authorize payments for a later capture', 'mollie-payments-for-woocommerce'), + ], + 'default' => 'immediate_capture', + 'desc' => sprintf( + __( + 'Authorized payment can be captured or voided by changing the order status instead of doing it manually.', + 'mollie-payments-for-woocommerce' + ) + ), + ], + [ + 'id' => $pluginName . '_capture_or_void', + 'title' => __( + 'Capture or void on status change', + 'mollie-payments-for-woocommerce' + ), + 'type' => 'checkbox', + 'default' => 'no', + 'desc' => __( + 'Capture authorized payments automatically when setting the order status to Processing or Completed. Void the payment by setting the order status Canceled.', + 'mollie-payments-for-woocommerce' + ), + ], [ 'id' => $pluginName . '_sectionend', 'type' => 'sectionend', diff --git a/mollie-payments-for-woocommerce.php b/mollie-payments-for-woocommerce.php index 4ff8709a..75cb243c 100644 --- a/mollie-payments-for-woocommerce.php +++ b/mollie-payments-for-woocommerce.php @@ -19,6 +19,7 @@ namespace Mollie\WooCommerce; +use Mollie\WooCommerce\MerchantCapture\MerchantCaptureModule; use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Package; use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Properties\PluginProperties; use Mollie\WooCommerce\Activation\ActivationModule; @@ -161,6 +162,7 @@ function initialize() new GatewayModule(), new VoucherModule(), new PaymentModule(), + new MerchantCaptureModule(), new UninstallModule(), ]; $modules = apply_filters('mollie_wc_plugin_modules', $modules); diff --git a/resources/js/advancedSettings.js b/resources/js/advancedSettings.js index d78b22aa..3aa2bc0d 100644 --- a/resources/js/advancedSettings.js +++ b/resources/js/advancedSettings.js @@ -1,9 +1,9 @@ ( - function ({_, jQuery }) { + function ({_, jQuery}) { function mollie_settings__insertTextAtCursor(target, text, dontIgnoreSelection) { if (target.setRangeText) { - if ( !dontIgnoreSelection ) { + if (!dontIgnoreSelection) { // insert at end target.setRangeText(text, target.value.length, target.value.length, "end"); } else { @@ -16,13 +16,14 @@ } target.focus(); } - jQuery(document).ready(function($) { + + jQuery(document).ready(function ($) { $(".mollie-settings-advanced-payment-desc-label") .data("ignore-click", "false") - .on("mousedown", function(e) { + .on("mousedown", function (e) { const input = document.getElementById("mollie-payments-for-woocommerce_api_payment_description"); - if ( document.activeElement && input === document.activeElement ) { - $(this).on("mouseup.molliesettings", function(e) { + if (document.activeElement && input === document.activeElement) { + $(this).on("mouseup.molliesettings", function (e) { $(this).data("ignore-click", "true"); $(this).off(".molliesettings"); const tag = $(this).data("tag"); @@ -31,15 +32,15 @@ }); } let $this = $(this); - $(window).on("mouseup.molliesettings drag.molliesettings blur.molliesettings", function(e) { + $(window).on("mouseup.molliesettings drag.molliesettings blur.molliesettings", function (e) { $this.off(".molliesettings"); $(window).off(".molliesettings"); }); }) - .on("click", function(e) { + .on("click", function (e) { e.preventDefault(); e.stopImmediatePropagation(); - if ( $(this).data("ignore-click") === "false" ) { + if ($(this).data("ignore-click") === "false") { const tag = $(this).data("tag"); const input = document.getElementById("mollie-payments-for-woocommerce_api_payment_description"); mollie_settings__insertTextAtCursor(input, tag, false); @@ -48,9 +49,44 @@ } }) ; + registerManualCaptureFields(); }); } ) ( window ) + +function registerManualCaptureFields() { + const onHoldSelect = jQuery('[name="mollie-payments-for-woocommerce_place_payment_onhold"]'); + if (onHoldSelect.length === 0) { + return; + } + toggleManualCaptureFields(onHoldSelect); + onHoldSelect.on('change', function(){ + toggleManualCaptureFields(onHoldSelect); + }) +} + +function toggleManualCaptureFields(onHoldSelect) { + const currentValue = onHoldSelect.find('option:selected'); + if (currentValue.length === 0) { + return; + } + + const captureStatusChangeField = jQuery('[name="mollie-payments-for-woocommerce_capture_or_void"]'); + if (captureStatusChangeField.length === 0) { + return; + } + + const captureStatusChangeFieldParent = captureStatusChangeField.closest('tr'); + if (captureStatusChangeFieldParent.length === 0) { + return; + } + + if (currentValue.val() === 'later_capture') { + captureStatusChangeFieldParent.show(); + } else { + captureStatusChangeFieldParent.hide(); + } +} diff --git a/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php b/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php index 38f91294..db7f6919 100644 --- a/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php +++ b/src/Buttons/ApplePayButton/ApplePayDataObjectHttp.php @@ -326,7 +326,7 @@ protected function addressHasRequiredFieldsValues( sprintf('ApplePay Data Error: Missing value for %s', $requiredField) ); $this->errors[] - = [ + = [ 'errorCode' => $errorCode, 'contactField' => $errorValue, ]; diff --git a/src/MerchantCapture/Capture/Action/AbstractPaymentCaptureAction.php b/src/MerchantCapture/Capture/Action/AbstractPaymentCaptureAction.php new file mode 100644 index 00000000..70d3ef8b --- /dev/null +++ b/src/MerchantCapture/Capture/Action/AbstractPaymentCaptureAction.php @@ -0,0 +1,40 @@ +apiHelper = $apiHelper; + $this->settingsHelper = $settingsHelper; + $this->order = wc_get_order($orderId); + $this->logger = $logger; + $this->pluginId = $pluginId; + $this->setApiKey(); + } + + protected function setApiKey() + { + $this->apiKey = $this->settingsHelper->getApiKey(); + } +} diff --git a/src/MerchantCapture/Capture/Action/CapturePayment.php b/src/MerchantCapture/Capture/Action/CapturePayment.php new file mode 100644 index 00000000..41fd5ab7 --- /dev/null +++ b/src/MerchantCapture/Capture/Action/CapturePayment.php @@ -0,0 +1,65 @@ +order->get_meta('_mollie_payment_id'); + + if (!$paymentId) { + $this->logger->error('Missing Mollie payment ID in order ' . $this->order->get_id()); + $this->order->add_order_note( + __( + 'The Mollie payment ID is missing, and we are unable to capture the funds.', + 'mollie-payments-for-woocommerce' + ) + ); + return; + } + + $paymentCapturesApi = $this->apiHelper->getApiClient($this->apiKey)->paymentCaptures; + $captureData = [ + 'amount' => [ + 'currency' => $this->order->get_currency(), + 'value' => $this->order->get_total(), + ], + ]; + $this->logger->debug( + 'SEND AN ORDER CAPTURE, orderId: ' . $this->order->get_id( + ) . ' transactionId: ' . $paymentId . 'Capture data: ' . json_encode($captureData) + ); + $paymentCapturesApi->createForId($paymentId, $captureData); + $this->order->update_meta_data( + MerchantCaptureModule::ORDER_PAYMENT_STATUS_META_KEY, + ManualCaptureStatus::STATUS_WAITING + ); + $this->order->add_order_note( + sprintf( + __( + 'The payment capture of %s has been sent successfully, and we are currently awaiting confirmation.', + 'mollie-payments-for-woocommerce' + ), + wc_price($this->order->get_total()) + ) + ); + $this->order->save(); + } catch (ApiException $exception) { + $this->logger->error($exception->getMessage()); + $this->order->add_order_note( + __( + 'Payment Capture Failed. We encountered an issue while processing the payment capture.', + 'mollie-payments-for-woocommerce' + ) + ); + } + } +} diff --git a/src/MerchantCapture/Capture/Action/VoidPayment.php b/src/MerchantCapture/Capture/Action/VoidPayment.php new file mode 100644 index 00000000..0ef1d152 --- /dev/null +++ b/src/MerchantCapture/Capture/Action/VoidPayment.php @@ -0,0 +1,34 @@ +order->get_meta('_mollie_payment_id'); + $paymentCapturesApi = $this->apiHelper->getApiClient($this->apiKey)->payments; + try { + $paymentCapturesApi->cancel($paymentId); + $this->order->update_meta_data( + MerchantCaptureModule::ORDER_PAYMENT_STATUS_META_KEY, + ManualCaptureStatus::STATUS_VOIDED + ); + $this->order->save(); + } catch (ApiException $exception) { + $this->logger->error($exception->getMessage()); + $this->order->add_order_note( + __( + 'Payment Void Failed. We encountered an issue while canceling the pre-authorized payment.', + 'mollie-payments-for-woocommerce' + ) + ); + } + } +} diff --git a/src/MerchantCapture/Capture/Type/ManualCapture.php b/src/MerchantCapture/Capture/Type/ManualCapture.php new file mode 100644 index 00000000..70c890c2 --- /dev/null +++ b/src/MerchantCapture/Capture/Type/ManualCapture.php @@ -0,0 +1,51 @@ +container = $container; + add_action('woocommerce_order_actions', [$this, 'enableOrderCaptureButton'], 10, 2); + add_action('woocommerce_order_action_' . self::MOLLIE_MANUAL_CAPTURE_ACTION, [$this, 'manualCapture']); + add_filter('woocommerce_mollie_wc_gateway_creditcard_args', [$this, 'sendManualCaptureMode']); + } + + public function enableOrderCaptureButton(array $actions, \WC_Order $order): array + { + if (!$this->container->get('merchant.manual_capture.can_capture_the_order')($order)) { + return $actions; + } + $actions[self::MOLLIE_MANUAL_CAPTURE_ACTION] = __( + 'Capture authorized Mollie payment', + 'mollie-payments-for-woocommerce' + ); + return $actions; + } + + public function sendManualCaptureMode(array $paymentData): array + { + if ($this->container->get('merchant.manual_capture.enabled')) { + $paymentData['captureMode'] = 'manual'; + } + return $paymentData; + } + + public function manualCapture(\WC_Order $order) + { + + ($this->container->get(CapturePayment::class))($order->get_id()); + } +} diff --git a/src/MerchantCapture/Capture/Type/StateChangeCapture.php b/src/MerchantCapture/Capture/Type/StateChangeCapture.php new file mode 100644 index 00000000..9e9ec3e8 --- /dev/null +++ b/src/MerchantCapture/Capture/Type/StateChangeCapture.php @@ -0,0 +1,55 @@ +container = $container; + add_action('woocommerce_order_status_changed', [$this, "orderStatusChange"], 10, 3); + } + + public function orderStatusChange(int $orderId, string $oldStatus, string $newStatus) + { + $stateChangeCaptureEnabled = $this->container->get('merchant.manual_capture.on_status_change_enabled'); + if (empty($stateChangeCaptureEnabled) || $stateChangeCaptureEnabled === 'no') { + return; + } + + if (!in_array($oldStatus, $this->container->get('merchant.manual_capture.void_statuses'))) { + return; + } + + if (in_array($newStatus, [SharedDataDictionary::STATUS_PROCESSING, SharedDataDictionary::STATUS_COMPLETED])) { + $this->capturePayment($orderId); + return; + } + + if ($newStatus === SharedDataDictionary::STATUS_CANCELLED) { + $this->voidPayment($orderId); + } + } + + protected function capturePayment(int $orderId) + { + ($this->container->get(CapturePayment::class))($orderId); + } + + protected function voidPayment(int $orderId) + { + ($this->container->get(VoidPayment::class))($orderId); + } +} diff --git a/src/MerchantCapture/ManualCaptureStatus.php b/src/MerchantCapture/ManualCaptureStatus.php new file mode 100644 index 00000000..06c34985 --- /dev/null +++ b/src/MerchantCapture/ManualCaptureStatus.php @@ -0,0 +1,14 @@ + static function () { + $captureType = get_option('mollie-payments-for-woocommerce_place_payment_onhold'); + return $captureType === 'later_capture'; + }, + 'merchant.manual_capture.supported_methods' => static function () { + return ['mollie_wc_gateway_creditcard']; + }, + 'merchant.manual_capture.void_statuses' => static function () { + return apply_filters('mollie_wc_gateway_void_order_state', [SharedDataDictionary::STATUS_ON_HOLD]); + }, + 'merchant.manual_capture.capture_statuses' => static function () { + return apply_filters('mollie_wc_gateway_capture_order_state', [SharedDataDictionary::STATUS_ON_HOLD]); + }, + 'merchant.manual_capture.is_authorized' => static function ($container) { + return static function (WC_Order $order) use ($container) { + $orderIsAuthorized = $order->get_meta( + self::ORDER_PAYMENT_STATUS_META_KEY + ) === ManualCaptureStatus::STATUS_AUTHORIZED; + $isManualCaptureMethod = in_array( + $order->get_payment_method(), + $container->get('merchant.manual_capture.supported_methods') + ); + + return $isManualCaptureMethod && $orderIsAuthorized; + }; + }, + 'merchant.manual_capture.is_waiting' => static function ($container) { + return static function (WC_Order $order) use ($container) { + $orderIsWaiting = $order->get_meta( + self::ORDER_PAYMENT_STATUS_META_KEY + ) === ManualCaptureStatus::STATUS_WAITING; + $isManualCaptureMethod = in_array( + $order->get_payment_method(), + $container->get('merchant.manual_capture.supported_methods') + ); + + return $isManualCaptureMethod && $orderIsWaiting; + }; + }, + 'merchant.manual_capture.can_capture_the_order' => static function ($container) { + return static function (WC_Order $order) use ($container) { + $orderIsAuthorized = $order->get_meta( + self::ORDER_PAYMENT_STATUS_META_KEY + ) === ManualCaptureStatus::STATUS_AUTHORIZED; + $isManualCaptureMethod = in_array( + $order->get_payment_method(), + $container->get('merchant.manual_capture.supported_methods') + ); + $isCorrectState = in_array( + $order->get_status(), + $container->get('merchant.manual_capture.capture_statuses') + ); + return $isManualCaptureMethod && $orderIsAuthorized && $isCorrectState; + }; + }, + 'merchant.manual_capture.on_status_change_enabled' => static function () { + return get_option('mollie-payments-for-woocommerce_capture_or_void', false); + }, + CapturePayment::class => static function ($container) { + return static function (int $orderId) use ($container) { + /** @var Api $api */ + $api = $container->get('SDK.api_helper'); + + /** @var Settings $settings */ + $settings = $container->get('settings.settings_helper'); + + /** @var Logger $logger */ + $logger = $container->get(Logger::class); + + $pluginId = $container->get('shared.plugin_id'); + + return (new CapturePayment($orderId, $api, $settings, $logger, $pluginId))(); + }; + }, + VoidPayment::class => static function ($container) { + return static function (int $orderId) use ($container) { + /** @var Api $api */ + $api = $container->get('SDK.api_helper'); + + /** @var Settings $settings */ + $settings = $container->get('settings.settings_helper'); + + /** @var Logger $logger */ + $logger = $container->get(Logger::class); + + $pluginId = $container->get('shared.plugin_id'); + + return (new VoidPayment($orderId, $api, $settings, $logger, $pluginId))(); + }; + }, + ]; + } + + public function run(ContainerInterface $container): bool + { + $pluginId = $container->get('shared.plugin_id'); + add_action($pluginId . '_after_webhook_action', static function (Payment $payment, WC_Order $order) use ($container) { + if ($payment->isAuthorized()) { + if (!$payment->getAmountCaptured() == 0.0) { + return; + } + $order->set_status(SharedDataDictionary::STATUS_ON_HOLD); + $order->update_meta_data(self::ORDER_PAYMENT_STATUS_META_KEY, ManualCaptureStatus::STATUS_AUTHORIZED); + $order->save(); + } elseif ($payment->isPaid() && ($container->get('merchant.manual_capture.is_waiting'))($order)) { + $order->update_meta_data(self::ORDER_PAYMENT_STATUS_META_KEY, ManualCaptureStatus::STATUS_CAPTURED); + $order->save(); + } + }, 10, 2); + + add_action('woocommerce_order_refunded', static function (int $orderId) use ($container) { + $order = wc_get_order($orderId); + if (!is_a($order, WC_Order::class)) { + return; + } + $merchantCanCapture = ($container->get('merchant.manual_capture.is_authorized'))($order); + if ($merchantCanCapture) { + ($container->get(VoidPayment::class))($order->get_id()); + } + }); + add_action('woocommerce_order_actions_start', static function (int $orderId) use ($container) { + $order = wc_get_order($orderId); + if (!is_a($order, WC_Order::class)) { + return; + } + $paymentStatus = $order->get_meta(MerchantCaptureModule::ORDER_PAYMENT_STATUS_META_KEY, true); + $actionBlockParagraphs = []; + + ob_start(); + (new StatusRenderer())($paymentStatus); + + $actionBlockParagraphs[] = ob_get_clean(); + if (($container->get('merchant.manual_capture.can_capture_the_order'))($order)) { + $actionBlockParagraphs[] = __( + 'To capture the authorized payment, select capture action from the list below.', + 'mollie-payments-for-woocommerce' + ); + } elseif (($container->get('merchant.manual_capture.is_authorized'))($order)) { + $actionBlockParagraphs[] = __( + 'Before capturing the authorized payment, ensure to set the order status to On Hold.', + 'mollie-payments-for-woocommerce' + ); + } + (new OrderActionBlock())($actionBlockParagraphs); + }); + add_filter( + 'mollie_wc_gateway_disable_ship_and_capture', + static function ($disableShipAndCapture, WC_Order $order) use ($container) { + if ($disableShipAndCapture) { + return true; + } + return $container->get('merchant.manual_capture.is_waiting')($order); + }, + 10, + 2 + ); + new OrderListPaymentColumn(); + new ManualCapture($container); + new StateChangeCapture($container); + return true; + } +} diff --git a/src/MerchantCapture/OrderListPaymentColumn.php b/src/MerchantCapture/OrderListPaymentColumn.php new file mode 100644 index 00000000..6447336b --- /dev/null +++ b/src/MerchantCapture/OrderListPaymentColumn.php @@ -0,0 +1,56 @@ + $column) { + $newColumns[$columnId] = $column; + if ($columnId === 'order_number') { + $newColumns['mollie_capture_payment_status'] = __( + 'Payment Status', + 'mollie-payments-for-woocommerce' + ); + $mollieColumnAdded = true; + } + } + + if (!$mollieColumnAdded) { + $newColumns['mollie_capture_payment_status'] = __('Payment Status', 'mollie-payments-for-woocommerce'); + } + + return $newColumns; + } + + public function renderColumnValue(string $column, int $orderId) + { + if ($column !== 'mollie_capture_payment_status') { + return; + } + /** @var \WC_Order $order */ + $order = wc_get_order($orderId); + + if (!is_a($order, \WC_Order::class)) { + return; + } + + $molliePaymentStatus = $order->get_meta(MerchantCaptureModule::ORDER_PAYMENT_STATUS_META_KEY); + + (new StatusRenderer())($molliePaymentStatus); + } +} diff --git a/src/MerchantCapture/UI/OrderActionBlock.php b/src/MerchantCapture/UI/OrderActionBlock.php new file mode 100644 index 00000000..d0fbd656 --- /dev/null +++ b/src/MerchantCapture/UI/OrderActionBlock.php @@ -0,0 +1,19 @@ +"; + foreach ($paragraphs as $paragraph) { + ?> +

['class' => []], 'span' => []]); ?>

+ '; + } +} diff --git a/src/MerchantCapture/UI/StatusButton.php b/src/MerchantCapture/UI/StatusButton.php new file mode 100644 index 00000000..063217a5 --- /dev/null +++ b/src/MerchantCapture/UI/StatusButton.php @@ -0,0 +1,15 @@ + + + pluginId . '_after_webhook_action', $payment, $order); // Status 200 } /** diff --git a/src/Payment/MolliePayment.php b/src/Payment/MolliePayment.php index 3d955c79..82636f65 100644 --- a/src/Payment/MolliePayment.php +++ b/src/Payment/MolliePayment.php @@ -453,6 +453,10 @@ public function refund(\WC_Order $order, $orderId, $paymentObject, $amount = nul return new WP_Error('1', $errorMessage); } + if ($paymentObject->isAuthorized()) { + return true; + } + if (! $paymentObject->isPaid()) { $errorMessage = "Can not refund payment $paymentObject->id for WooCommerce order $orderId as it is not paid."; diff --git a/src/Payment/PaymentModule.php b/src/Payment/PaymentModule.php index 884a3d4b..440968aa 100644 --- a/src/Payment/PaymentModule.php +++ b/src/Payment/PaymentModule.php @@ -339,7 +339,7 @@ public function shipAndCaptureOrderAtMollie($order_id) // To disable automatic shipping and capturing of the Mollie order when a WooCommerce order status is updated to completed, // store an option 'mollie-payments-for-woocommerce_disableShipOrderAtMollie' with value 1 - if (get_option($this->pluginId . '_' . 'disableShipOrderAtMollie', '0') === '1') { + if (apply_filters('mollie_wc_gateway_disable_ship_and_capture', get_option($this->pluginId . '_' . 'disableShipOrderAtMollie', '0') === '1', $order)) { return; } diff --git a/src/Payment/PaymentService.php b/src/Payment/PaymentService.php index ea7b53f3..59fc6875 100644 --- a/src/Payment/PaymentService.php +++ b/src/Payment/PaymentService.php @@ -24,6 +24,9 @@ class PaymentService { public const PAYMENT_METHOD_TYPE_ORDER = 'order'; public const PAYMENT_METHOD_TYPE_PAYMENT = 'payment'; + + public const PAYMENT_METHOD_TYPE_CAPTURE = 'capture'; + /** * @var MolliePaymentGatewayI */ @@ -369,8 +372,7 @@ protected function processAsMollieOrder( : '', 'orderNumber' => isset($data['orderNumber']) ? $data['orderNumber'] : '', - 'lines' => isset($data['lines']) ? $data['lines'] : '', - ]; + 'lines' => isset($data['lines']) ? $data['lines'] : '', ]; $this->logger->debug(json_encode($apiCallLog)); $paymentOrder = $paymentObject; @@ -550,6 +552,7 @@ protected function processPaymentForMollie( $apiKey ); } + return $paymentObject; } diff --git a/src/PaymentMethods/Kbc.php b/src/PaymentMethods/Kbc.php index 361116ed..5c220cac 100644 --- a/src/PaymentMethods/Kbc.php +++ b/src/PaymentMethods/Kbc.php @@ -6,10 +6,10 @@ class Kbc extends AbstractPaymentMethod implements PaymentMethodI { -protected const DEFAULT_ISSUERS_DROPDOWN = 'yes'; -protected function getConfig(): array -{ - return [ + protected const DEFAULT_ISSUERS_DROPDOWN = 'yes'; + protected function getConfig(): array + { + return [ 'id' => 'kbc', 'defaultTitle' => __('KBC/CBC Payment Button', 'mollie-payments-for-woocommerce'), 'settingsDescription' => '', @@ -23,10 +23,10 @@ protected function getConfig(): array 'filtersOnBuild' => false, 'confirmationDelayed' => true, 'SEPA' => true, - ]; -} + ]; + } -public function getFormFields($generalFormFields): array + public function getFormFields($generalFormFields): array { $paymentMethodFormFieds = [ 'issuers_dropdown_shown' => [ diff --git a/src/SDK/WordPressHttpAdapter.php b/src/SDK/WordPressHttpAdapter.php index 1bffd185..5337cd0c 100644 --- a/src/SDK/WordPressHttpAdapter.php +++ b/src/SDK/WordPressHttpAdapter.php @@ -18,6 +18,7 @@ class WordPressHttpAdapter implements MollieHttpAdapterInterface * HTTP status code for an empty ok response. */ const HTTP_NO_CONTENT = 204; + const PAYMENT_HTTP_NO_CONTENT = 202; /** * @param string $httpMethod @@ -65,7 +66,7 @@ protected function parseResponse($response) $statusCode = wp_remote_retrieve_response_code($response); $httpBody = wp_remote_retrieve_body($response); if (empty($httpBody)) { - if ($statusCode === self::HTTP_NO_CONTENT) { + if ($statusCode === self::HTTP_NO_CONTENT || $statusCode === self::PAYMENT_HTTP_NO_CONTENT) { return null; } From 5732e5ce2e68503a19a15d955c6f0ced042881bc Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Tue, 24 Oct 2023 10:30:57 +0200 Subject: [PATCH 14/23] Remove payment method type capture const from PaymentService --- src/Payment/PaymentService.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Payment/PaymentService.php b/src/Payment/PaymentService.php index 59fc6875..cb9be0cd 100644 --- a/src/Payment/PaymentService.php +++ b/src/Payment/PaymentService.php @@ -25,8 +25,6 @@ class PaymentService public const PAYMENT_METHOD_TYPE_ORDER = 'order'; public const PAYMENT_METHOD_TYPE_PAYMENT = 'payment'; - public const PAYMENT_METHOD_TYPE_CAPTURE = 'capture'; - /** * @var MolliePaymentGatewayI */ From 257ce096734f136be41202351daac430521e7d81 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 24 Oct 2023 11:07:40 +0200 Subject: [PATCH 15/23] Fix CS --- src/Gateway/MolliePaymentGateway.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index d45d98e9..7cf669eb 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -274,17 +274,17 @@ public function isValidForUse(): bool $test_mode = $this->dataService->isTestModeEnabled(); $this->errors[] = ($test_mode ? __( - 'Test mode enabled.', - 'mollie-payments-for-woocommerce' - ) . ' ' : '') . sprintf( + 'Test mode enabled.', + 'mollie-payments-for-woocommerce' + ) . ' ' : '') . sprintf( /* translators: The surrounding %s's Will be replaced by a link to the global setting page */ - __( - 'No API key provided. Please %1$sset you Mollie API key%2$s first.', - 'mollie-payments-for-woocommerce' - ), - '', - '' - ); + __( + 'No API key provided. Please %1$sset you Mollie API key%2$s first.', + 'mollie-payments-for-woocommerce' + ), + '', + '' + ); return false; } @@ -813,7 +813,9 @@ public function displayInstructions( $admin_instructions = false, $plain_text = false ) { - if (($admin_instructions && !$this::$alreadyDisplayedAdminInstructions) + + if ( + ($admin_instructions && !$this::$alreadyDisplayedAdminInstructions) || (!$admin_instructions && !$this::$alreadyDisplayedCustomerInstructions) ) { $order_payment_method = $order->get_payment_method(); From 207c5f6a01e435484ca71b553acf8ebe40e0dbd4 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 24 Oct 2023 11:16:33 +0200 Subject: [PATCH 16/23] Update version and pot --- languages/en_GB.pot | 112 ++++++++++++++++------------ mollie-payments-for-woocommerce.php | 2 +- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/languages/en_GB.pot b/languages/en_GB.pot index ecfbc1ea..eb67e741 100644 --- a/languages/en_GB.pot +++ b/languages/en_GB.pot @@ -2,13 +2,14 @@ # This file is distributed under the GPLv2 or later. msgid "" msgstr "" -"Project-Id-Version: Mollie Payments for WooCommerce 7.4.0\n" -"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/WooCommerce\n" +"Project-Id-Version: Mollie Payments for WooCommerce 7.4.1-beta\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/html\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2023-10-20T15:51:47+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.8.1\n" "X-Domain: mollie-payments-for-woocommerce\n" @@ -508,7 +509,7 @@ msgstr "" #: src/Buttons/ApplePayButton/AppleAjaxRequests.php:700 #: src/Buttons/PayPalButton/PayPalAjaxRequests.php:113 #: src/Buttons/PayPalButton/PayPalAjaxRequests.php:157 -#: src/Payment/PaymentService.php:716 +#: src/Payment/PaymentService.php:718 msgid "Could not create %s payment." msgstr "" @@ -520,85 +521,89 @@ msgstr "" msgid "%1$sApple Pay Validation Error%2$s Check %3$sApple Server requirements page%4$s to fix it in order to make the Apple Pay button work" msgstr "" -#: src/Gateway/GatewayModule.php:655 +#: src/Gateway/GatewayModule.php:326 +msgid "Mollie Payment Details" +msgstr "" + +#: src/Gateway/GatewayModule.php:682 msgid "Error processing %1$s payment, the %2$s field is required." msgstr "" -#: src/Gateway/GatewayModule.php:669 +#: src/Gateway/GatewayModule.php:696 msgid "Please enter your %1$s, this is required for %2$s payments" msgstr "" -#: src/Gateway/MolliePaymentGateway.php:269 +#: src/Gateway/MolliePaymentGateway.php:276 #: src/Settings/Page/MollieSettingsPage.php:314 msgid "Test mode enabled." msgstr "" #. translators: The surrounding %s's Will be replaced by a link to the global setting page -#: src/Gateway/MolliePaymentGateway.php:274 +#: src/Gateway/MolliePaymentGateway.php:281 msgid "No API key provided. Please %1$sset you Mollie API key%2$s first." msgstr "" #. translators: Placeholder 1: payment method title. The surrounding %s's Will be replaced by a link to the Mollie profile -#: src/Gateway/MolliePaymentGateway.php:289 +#: src/Gateway/MolliePaymentGateway.php:296 msgid "%1$s not enabled in your Mollie profile. You can enable it by editing your %2$sMollie profile%3$s." msgstr "" #. translators: Placeholder 1: WooCommerce currency, placeholder 2: Supported Mollie currencies -#: src/Gateway/MolliePaymentGateway.php:304 +#: src/Gateway/MolliePaymentGateway.php:311 msgid "Current shop currency %1$s not supported by Mollie. Read more about %2$ssupported currencies and payment methods.%3$s " msgstr "" -#: src/Gateway/MolliePaymentGateway.php:575 +#: src/Gateway/MolliePaymentGateway.php:582 msgid "You have cancelled your payment. Please complete your order with a different payment method." msgstr "" -#: src/Gateway/MolliePaymentGateway.php:596 -#: src/Gateway/MolliePaymentGateway.php:610 +#: src/Gateway/MolliePaymentGateway.php:603 +#: src/Gateway/MolliePaymentGateway.php:617 msgid "Your payment was not successful. Please complete your order with a different payment method." msgstr "" -#: src/Gateway/MolliePaymentGateway.php:646 +#: src/Gateway/MolliePaymentGateway.php:653 msgid "Could not load order %s" msgstr "" -#: src/Gateway/MolliePaymentGateway.php:899 +#: src/Gateway/MolliePaymentGateway.php:918 msgid "Order cancelled" msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment ID -#: src/Gateway/MolliePaymentGateway.php:933 +#: src/Gateway/MolliePaymentGateway.php:952 msgid "%1$s payment still pending (%2$s) but customer already returned to the store. Status should be updated automatically in the future, if it doesn't this might indicate a communication issue between the site and Mollie." msgstr "" -#: src/Gateway/MolliePaymentGateway.php:939 +#: src/Gateway/MolliePaymentGateway.php:958 #: src/Payment/MollieObject.php:682 #: src/Payment/MollieObject.php:714 #: src/Payment/MollieOrder.php:281 #: src/Payment/MollieOrder.php:338 -#: src/Payment/MollieOrder.php:382 -#: src/Payment/MollieOrder.php:463 -#: src/Payment/MollieOrder.php:534 -#: src/Payment/MollieOrder.php:877 -#: src/Payment/MollieOrderService.php:171 -#: src/Payment/MollieOrderService.php:437 -#: src/Payment/MollieOrderService.php:500 -#: src/Payment/MollieOrderService.php:714 +#: src/Payment/MollieOrder.php:389 +#: src/Payment/MollieOrder.php:470 +#: src/Payment/MollieOrder.php:541 +#: src/Payment/MollieOrder.php:884 +#: src/Payment/MollieOrderService.php:172 +#: src/Payment/MollieOrderService.php:438 +#: src/Payment/MollieOrderService.php:501 +#: src/Payment/MollieOrderService.php:715 #: src/Payment/MolliePayment.php:236 #: src/Payment/MolliePayment.php:323 #: src/Payment/MolliePayment.php:399 #: src/Payment/MolliePayment.php:423 -#: src/Payment/PaymentService.php:801 +#: src/Payment/PaymentService.php:803 #: src/Subscription/MollieSepaRecurringGateway.php:137 #: src/Subscription/MollieSepaRecurringGateway.php:204 #: src/Subscription/MollieSubscriptionGateway.php:458 msgid "test mode" msgstr "" -#: src/Gateway/MolliePaymentGateway.php:950 +#: src/Gateway/MolliePaymentGateway.php:969 msgid ", payment pending." msgstr "" -#: src/Gateway/MolliePaymentGateway.php:982 +#: src/Gateway/MolliePaymentGateway.php:1001 msgid "Your order has been cancelled." msgstr "" @@ -766,72 +771,72 @@ msgid "Order authorized using %1$s payment (%2$s). Set order to completed in Woo msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment ID -#: src/Payment/MollieOrder.php:380 +#: src/Payment/MollieOrder.php:387 msgid "Order completed at Mollie for %1$s order (%2$s). At least one order line completed. Remember: Completed status for an order at Mollie is not the same as Completed status in WooCommerce!" msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment ID -#: src/Payment/MollieOrder.php:461 +#: src/Payment/MollieOrder.php:468 msgid "%1$s order (%2$s) cancelled ." msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment ID -#: src/Payment/MollieOrder.php:532 +#: src/Payment/MollieOrder.php:539 msgid "%1$s order expired (%2$s) but not cancelled because of another pending payment (%3$s)." msgstr "" -#: src/Payment/MollieOrder.php:627 +#: src/Payment/MollieOrder.php:634 msgctxt "Order note error" msgid "The sum of refunds for all order lines is not identical to the refund amount, so this refund will be processed as a payment amount refund, not an order line refund." msgstr "" -#: src/Payment/MollieOrder.php:759 +#: src/Payment/MollieOrder.php:766 msgctxt "Order note error" msgid "Can not refund order amount that has status %1$s at Mollie." msgstr "" -#: src/Payment/MollieOrder.php:781 +#: src/Payment/MollieOrder.php:788 msgid "Amount refund of %1$s%2$s refunded in WooCommerce and at Mollie.%3$s Refund ID: %4$s." msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment ID -#: src/Payment/MollieOrder.php:872 +#: src/Payment/MollieOrder.php:879 msgid "%1$s order (%2$s) expired ." msgstr "" -#: src/Payment/MollieOrder.php:1095 +#: src/Payment/MollieOrder.php:1102 msgid "%1$sx %2$s cancelled for %3$s%4$s in WooCommerce and at Mollie." msgstr "" -#: src/Payment/MollieOrder.php:1119 +#: src/Payment/MollieOrder.php:1126 msgid "%1$sx %2$s refunded for %3$s%4$s in WooCommerce and at Mollie.%5$s Refund ID: %6$s." msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment status, placeholder 3: payment ID -#: src/Payment/MollieOrderService.php:168 +#: src/Payment/MollieOrderService.php:169 msgid "%1$s payment %2$s (%3$s), not processed." msgstr "" -#: src/Payment/MollieOrderService.php:400 +#: src/Payment/MollieOrderService.php:401 msgid "New chargeback %s processed! Order note and order status updated." msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment ID -#: src/Payment/MollieOrderService.php:432 +#: src/Payment/MollieOrderService.php:433 msgid "%1$s payment charged back via Mollie (%2$s). You will need to manually review the payment (and adjust product stocks if you use it)." msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment ID -#: src/Payment/MollieOrderService.php:494 +#: src/Payment/MollieOrderService.php:495 msgid "%1$s payment charged back via Mollie (%2$s). Subscription status updated, please review (and adjust product stocks if you use it)." msgstr "" #. translators: Placeholder 1: payment method title, placeholder 2: payment ID -#: src/Payment/MollieOrderService.php:701 +#: src/Payment/MollieOrderService.php:702 msgid "%1$s payment %2$s via Mollie (%3$s %4$s). You will need to manually review the payment (and adjust product stocks if you use it)." msgstr "" -#: src/Payment/MollieOrderService.php:814 +#: src/Payment/MollieOrderService.php:815 msgid "New refund %s processed in Mollie Dashboard! Order note added, but order not updated." msgstr "" @@ -930,35 +935,39 @@ msgctxt "Order note info" msgid "Order could not be canceled at Mollie, because order status is " msgstr "" -#: src/Payment/PaymentService.php:622 +#: src/Payment/PaymentService.php:624 msgid "Subscription switch failed, no valid mandate found. Place a completely new order to change your subscription." msgstr "" -#: src/Payment/PaymentService.php:628 +#: src/Payment/PaymentService.php:630 msgid "Failed switching subscriptions, no valid mandate." msgstr "" -#: src/Payment/PaymentService.php:638 +#: src/Payment/PaymentService.php:640 msgid "Order completed internally because of an existing valid mandate at Mollie." msgstr "" -#: src/Payment/PaymentService.php:779 +#: src/Payment/PaymentService.php:781 #: src/Subscription/MollieSepaRecurringGateway.php:126 #: src/Subscription/MollieSubscriptionGateway.php:449 msgid "Awaiting payment confirmation." msgstr "" #. translators: Placeholder 1: Payment method title, placeholder 2: payment ID -#: src/Payment/PaymentService.php:799 +#: src/Payment/PaymentService.php:801 #: src/Subscription/MollieSepaRecurringGateway.php:135 #: src/Subscription/MollieSubscriptionGateway.php:456 msgid "%1$s payment started (%2$s)." msgstr "" -#: src/Payment/PaymentService.php:869 +#: src/Payment/PaymentService.php:871 msgid "Payment failed due to: Mollie is out of service. Please try again later." msgstr "" +#: src/Payment/PaymentService.php:894 +msgid "Payment failed due to: The payment was declined due to suspected fraud." +msgstr "" + #: src/Payment/RefundLineItemsBuilder.php:126 msgid "Mollie doesn't allow a partial refund of the full amount or quantity of at least one order line. Trying to process this as an amount refund instead." msgstr "" @@ -1149,6 +1158,7 @@ msgstr "" #: src/PaymentMethods/Ideal.php:17 #: src/PaymentMethods/Ideal.php:55 #: src/PaymentMethods/Kbc.php:16 +#: src/PaymentMethods/Kbc.php:60 msgid "Select your bank" msgstr "" @@ -1274,6 +1284,10 @@ msgstr "" msgid "If you disable this, a dropdown with various KBC/CBC banks will not be shown in the WooCommerce checkout, so users will select a KBC/CBC bank on the Mollie payment page after checkout." msgstr "" +#: src/PaymentMethods/Kbc.php:54 +msgid "This text will be displayed as the first option in the KBC/CBC issuers drop down, if nothing is entered, 'Select your bank' will be shown. Only if the above 'Show KBC/CBC banks dropdown' is enabled." +msgstr "" + #: src/PaymentMethods/Klarna.php:13 msgid "Pay with Klarna" msgstr "" @@ -1838,7 +1852,7 @@ msgid "Initial order status" msgstr "" #: src/Settings/General/MollieGeneralSettings.php:292 -msgid "Some payment methods take longer than a few hours to complete. The initial order state is then set to '%1$s'. This ensures the order is not cancelled when the setting %2$s is used." +msgid "Some payment methods take longer than a few hours to complete. The initial order state is then set to '%1$s'. This ensures the order is not cancelled when the setting %2$s is used. This will also prevent the order to be canceled when expired." msgstr "" #: src/Settings/Page/MollieSettingsPage.php:112 diff --git a/mollie-payments-for-woocommerce.php b/mollie-payments-for-woocommerce.php index 4ff8709a..fcadd0a9 100644 --- a/mollie-payments-for-woocommerce.php +++ b/mollie-payments-for-woocommerce.php @@ -3,7 +3,7 @@ * Plugin Name: Mollie Payments for WooCommerce * Plugin URI: https://www.mollie.com * Description: Accept payments in WooCommerce with the official Mollie plugin - * Version: 7.4.0 + * Version: 7.4.1-beta * Author: Mollie * Author URI: https://www.mollie.com * Requires at least: 5.0 From 71b2253d6652a4ff5a00ce6ffb8a5fe2a073440d Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Tue, 24 Oct 2023 13:42:24 +0200 Subject: [PATCH 17/23] Fix NoticeModule namespace. --- src/Notice/NoticeModule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Notice/NoticeModule.php b/src/Notice/NoticeModule.php index 49861917..e2cb7cc5 100644 --- a/src/Notice/NoticeModule.php +++ b/src/Notice/NoticeModule.php @@ -6,8 +6,8 @@ namespace Mollie\WooCommerce\Notice; -use Inpsyde\Modularity\Module\ModuleClassNameIdTrait; -use Inpsyde\Modularity\Module\ServiceModule; +use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; class NoticeModule implements ServiceModule { From dd902c70cc6b08fddb7c4dffa6d27d247aa041cc Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Tue, 24 Oct 2023 13:56:36 +0200 Subject: [PATCH 18/23] Set minimum expiry time to 10. --- src/Settings/General/MollieGeneralSettings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Settings/General/MollieGeneralSettings.php b/src/Settings/General/MollieGeneralSettings.php index b0d018b9..4673e689 100644 --- a/src/Settings/General/MollieGeneralSettings.php +++ b/src/Settings/General/MollieGeneralSettings.php @@ -264,8 +264,8 @@ public function gatewayFormFields( 'mollie-payments-for-woocommerce' ) ), - 'custom_attributes' => ['step' => '1', 'min' => '0', 'max' => '526000'], - 'default' => '0', + 'custom_attributes' => ['step' => '1', 'min' => '10', 'max' => '526000'], + 'default' => '10', 'desc_tip' => false, ], ]; From bdbd3dd3c47ea215c90efde2538bb0cfe2e6573f Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Wed, 25 Oct 2023 08:48:28 +0200 Subject: [PATCH 19/23] Remove dueDate description. --- src/Settings/General/MollieGeneralSettings.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Settings/General/MollieGeneralSettings.php b/src/Settings/General/MollieGeneralSettings.php index 4673e689..a611f7e0 100644 --- a/src/Settings/General/MollieGeneralSettings.php +++ b/src/Settings/General/MollieGeneralSettings.php @@ -258,12 +258,6 @@ public function gatewayFormFields( 'order_dueDate' => [ 'title' => sprintf(__('Expiry time', 'mollie-payments-for-woocommerce')), 'type' => 'number', - 'description' => sprintf( - __( - 'Number of MINUTES after the order will expire and will be canceled at Mollie and WooCommerce. A value of 0 means no expiry time will be considered.', - 'mollie-payments-for-woocommerce' - ) - ), 'custom_attributes' => ['step' => '1', 'min' => '10', 'max' => '526000'], 'default' => '10', 'desc_tip' => false, From 69ed8f7d393d5052982d87ca2b967c5f06a8942f Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Thu, 26 Oct 2023 11:11:19 +0200 Subject: [PATCH 20/23] Replace addNotice with wc_print_notice --- src/Payment/PaymentService.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Payment/PaymentService.php b/src/Payment/PaymentService.php index c627d9e9..5032d931 100644 --- a/src/Payment/PaymentService.php +++ b/src/Payment/PaymentService.php @@ -717,7 +717,9 @@ protected function reportPaymentCreationFailure($orderId, $e, $paymentMethodId): $message .= 'hii ' . $e->getMessage(); } - $this->notice->addNotice('error', $message); + add_action('before_woocommerce_pay_form', function() use($message){ + wc_print_notice($message, 'error'); + }); } /** From 7fc11568299ff45ed7c1a20a1e0ad6cb32fe9d53 Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Thu, 26 Oct 2023 11:18:55 +0200 Subject: [PATCH 21/23] Code style. --- src/Payment/PaymentService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Payment/PaymentService.php b/src/Payment/PaymentService.php index 5032d931..20462e9c 100644 --- a/src/Payment/PaymentService.php +++ b/src/Payment/PaymentService.php @@ -717,7 +717,7 @@ protected function reportPaymentCreationFailure($orderId, $e, $paymentMethodId): $message .= 'hii ' . $e->getMessage(); } - add_action('before_woocommerce_pay_form', function() use($message){ + add_action('before_woocommerce_pay_form', static function () use ($message) { wc_print_notice($message, 'error'); }); } From 0e82e62c676252f0482bd950755d5091021cb98b Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Thu, 26 Oct 2023 12:10:13 +0200 Subject: [PATCH 22/23] Change the description, fix banktransfer and skip gateways wihout expiry --- src/Payment/PaymentModule.php | 5 +++++ src/Settings/General/MollieGeneralSettings.php | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/Payment/PaymentModule.php b/src/Payment/PaymentModule.php index 884a3d4b..055ada90 100644 --- a/src/Payment/PaymentModule.php +++ b/src/Payment/PaymentModule.php @@ -167,6 +167,11 @@ public function cancelOrderOnExpiryDate() foreach ($classNames as $gateway) { $gatewayName = strtolower($gateway) . '_settings'; $gatewaySettings = get_option($gatewayName); + + if (empty($gatewaySettings["activate_expiry_days_setting"]) || $gatewaySettings["activate_expiry_days_setting"] === 'no') { + continue; + } + $heldDuration = isset($gatewaySettings) && isset($gatewaySettings['order_dueDate']) ? $gatewaySettings['order_dueDate'] : 0; if ($heldDuration < 1) { diff --git a/src/Settings/General/MollieGeneralSettings.php b/src/Settings/General/MollieGeneralSettings.php index a611f7e0..d4436375 100644 --- a/src/Settings/General/MollieGeneralSettings.php +++ b/src/Settings/General/MollieGeneralSettings.php @@ -259,6 +259,12 @@ public function gatewayFormFields( 'title' => sprintf(__('Expiry time', 'mollie-payments-for-woocommerce')), 'type' => 'number', 'custom_attributes' => ['step' => '1', 'min' => '10', 'max' => '526000'], + 'description' => sprintf( + __( + 'Number of MINUTES after the order will expire and will be canceled at Mollie and WooCommerce.', + 'mollie-payments-for-woocommerce' + ) + ), 'default' => '10', 'desc_tip' => false, ], From c82b9dbeedc6028c56c0946c547892e00ec9fddf Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Thu, 26 Oct 2023 12:12:32 +0200 Subject: [PATCH 23/23] Fix wrong gateway name when canceling the order --- src/Payment/PaymentModule.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Payment/PaymentModule.php b/src/Payment/PaymentModule.php index 055ada90..6228d62b 100644 --- a/src/Payment/PaymentModule.php +++ b/src/Payment/PaymentModule.php @@ -178,7 +178,7 @@ public function cancelOrderOnExpiryDate() continue; } $heldDurationInSeconds = $heldDuration * 60; - if ($gateway === 'mollie_wc_gateway_bankTransfer') { + if ($gateway === 'Mollie_WC_Gateway_Banktransfer') { $durationInHours = absint($heldDuration) * 24; $durationInMinutes = $durationInHours * 60; $heldDurationInSeconds = $durationInMinutes * 60; @@ -479,6 +479,12 @@ public function cancelOrderAtMollie($order_id) */ public function handleExpiryDateCancelation($paymentMethods) { + add_action( + 'init', + [$this, 'cancelOrderOnExpiryDate'], + 11, + 2 + ); if (!$this->IsExpiryDateEnabled($paymentMethods)) { as_unschedule_action('mollie_woocommerce_cancel_unpaid_orders'); return;