From cd65d07a5a5c8dc03524b65470b8eca356f64d73 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Thu, 17 Oct 2024 08:35:36 +0200 Subject: [PATCH 01/23] Add filters for cancel and capture at mollie --- src/Payment/PaymentModule.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Payment/PaymentModule.php b/src/Payment/PaymentModule.php index 798640775..9bb669c20 100644 --- a/src/Payment/PaymentModule.php +++ b/src/Payment/PaymentModule.php @@ -104,12 +104,14 @@ public function run(ContainerInterface $container): bool // Show Mollie instructions on order details page add_action('woocommerce_order_details_after_order_table', [ $this, 'onOrderDetails' ], 10, 1); - + // Custom filter to choose on which action to cancel orders at Mollie + $cancelOrderAction = apply_filters('mollie-payments-for-woocommerce_cancel_order', 'woocommerce_order_status_cancelled'); // Cancel order at Mollie (for Orders API/Klarna) - add_action('woocommerce_order_status_cancelled', [ $this, 'cancelOrderAtMollie' ]); - + add_action($cancelOrderAction, [ $this, 'cancelOrderAtMollie' ]); + // Custom filter to choose on which action to ship and capture orders at Mollie + $captureOrderAction = apply_filters('mollie-payments-for-woocommerce_ship_capture_order', 'woocommerce_order_status_completed'); // Capture order at Mollie (for Orders API/Klarna) - add_action('woocommerce_order_status_completed', [ $this, 'shipAndCaptureOrderAtMollie' ]); + add_action($captureOrderAction, [ $this, 'shipAndCaptureOrderAtMollie' ]); add_filter( 'woocommerce_cancel_unpaid_order', From 7953228c1f1236014e0dbc5b748b7a392e9680f8 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Thu, 17 Oct 2024 11:14:01 +0200 Subject: [PATCH 02/23] Include the order-functions file via composer --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f226bbd3c..8edac1fd7 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,10 @@ "Mollie\\WooCommerceTests\\": "tests/php", "Mollie\\WooCommerceTests\\Unit\\": "tests/php/Unit", "Mollie\\WooCommerceTests\\Functional\\": "tests/php/Functional" - } + }, + "files": [ + "inc/api/order-functions.php" + ] }, "scripts": { "check-coding-standards": "vendor/bin/phpcs --parallel=8 -s", From 912c88433e82c34ee8e62c8ed619302cc008e0f1 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Thu, 17 Oct 2024 11:14:52 +0200 Subject: [PATCH 03/23] Extract _mollie_order_id as constant and refactor calls --- src/Gateway/MolliePaymentGateway.php | 5 +++-- src/Payment/MollieObject.php | 4 ++-- src/Payment/MollieOrder.php | 4 ++-- src/Payment/PaymentModule.php | 4 ++-- src/Subscription/MaybeFixSubscription.php | 4 +++- src/Subscription/MollieSubscriptionGateway.php | 2 +- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index e880f4836..58466a168 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -27,6 +27,7 @@ class MolliePaymentGateway extends WC_Payment_Gateway implements MolliePaymentGatewayI { + const ORDER_ID_META_KEY = '_mollie_order_id'; /** * @var bool */ @@ -1029,8 +1030,8 @@ public function getSelectedIssuer(): ?string */ public function get_transaction_url($order): string { - $isPaymentApi = substr($order->get_meta('_mollie_order_id', true), 0, 3) === 'tr_' ; - $resource = ($order->get_meta('_mollie_order_id', true) && !$isPaymentApi) ? 'orders' : 'payments'; + $isPaymentApi = substr($order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true), 0, 3) === 'tr_' ; + $resource = ($order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true) && !$isPaymentApi) ? 'orders' : 'payments'; $this->view_transaction_url = 'https://my.mollie.com/dashboard/' . $resource . '/%s?utm_source=woocommerce&utm_medium=plugin&utm_campaign=partner'; diff --git a/src/Payment/MollieObject.php b/src/Payment/MollieObject.php index d2f444807..d7907a606 100644 --- a/src/Payment/MollieObject.php +++ b/src/Payment/MollieObject.php @@ -171,7 +171,7 @@ public function setActiveMolliePaymentForOrders($order_id) { static::$order = wc_get_order($order_id); - static::$order->update_meta_data('_mollie_order_id', $this->data->id); + static::$order->update_meta_data(MolliePaymentGateway::ORDER_ID_META_KEY, $this->data->id); static::$order->update_meta_data('_mollie_payment_id', static::$paymentId); static::$order->update_meta_data('_mollie_payment_mode', $this->data->mode); @@ -325,7 +325,7 @@ public function getActiveMolliePaymentId($order_id) public function getActiveMollieOrderId($order_id) { $order = wc_get_order($order_id); - return $order->get_meta('_mollie_order_id', true); + return $order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true); } /** diff --git a/src/Payment/MollieOrder.php b/src/Payment/MollieOrder.php index 62557c57d..64e9cf1fc 100644 --- a/src/Payment/MollieOrder.php +++ b/src/Payment/MollieOrder.php @@ -172,7 +172,7 @@ public function setActiveMolliePayment($orderId) self::$customerId = $this->getMollieCustomerIdFromPaymentObject(); self::$order = wc_get_order($orderId); - self::$order->update_meta_data('_mollie_order_id', $this->data->id); + self::$order->update_meta_data(MolliePaymentGateway::ORDER_ID_META_KEY, $this->data->id); self::$order->save(); return parent::setActiveMolliePayment($orderId); @@ -511,7 +511,7 @@ public function onWebhookFailed(WC_Order $order, $payment, $paymentMethodTitle) public function onWebhookExpired(WC_Order $order, $payment, $paymentMethodTitle) { $orderId = $order->get_id(); - $molliePaymentId = $order->get_meta('_mollie_order_id', true); + $molliePaymentId = $order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true); // Add messages to log $this->logger->debug(__METHOD__ . ' called for order ' . $orderId); diff --git a/src/Payment/PaymentModule.php b/src/Payment/PaymentModule.php index 9bb669c20..c03c03351 100644 --- a/src/Payment/PaymentModule.php +++ b/src/Payment/PaymentModule.php @@ -355,7 +355,7 @@ public function shipAndCaptureOrderAtMollie($order_id) $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Try to process completed order for a potential capture at Mollie.'); // Does WooCommerce order contain a Mollie Order? - $mollie_order_id = ( $mollie_order_id = $order->get_meta('_mollie_order_id', true) ) ? $mollie_order_id : false; + $mollie_order_id = ( $mollie_order_id = $order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true) ) ? $mollie_order_id : false; // Is it a payment? you cannot ship a payment if ($mollie_order_id === false || substr($mollie_order_id, 0, 3) === 'tr_') { $message = _x('Processing a payment, no capture needed', 'Order note info', 'mollie-payments-for-woocommerce'); @@ -426,7 +426,7 @@ public function cancelOrderAtMollie($order_id) $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Try to process cancelled order at Mollie.'); - $mollie_order_id = ( $mollie_order_id = $order->get_meta('_mollie_order_id', true) ) ? $mollie_order_id : false; + $mollie_order_id = ( $mollie_order_id = $order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true) ) ? $mollie_order_id : false; if ($mollie_order_id === false) { $message = _x('Order contains Mollie payment method, but not a valid Mollie Order ID. Canceling order failed.', 'Order note info', 'mollie-payments-for-woocommerce'); diff --git a/src/Subscription/MaybeFixSubscription.php b/src/Subscription/MaybeFixSubscription.php index 9d5ae1ca8..5c6c147bc 100644 --- a/src/Subscription/MaybeFixSubscription.php +++ b/src/Subscription/MaybeFixSubscription.php @@ -4,6 +4,8 @@ namespace Mollie\WooCommerce\Subscription; +use Mollie\WooCommerce\Gateway\MolliePaymentGateway; + class MaybeFixSubscription { public function maybeFix() @@ -39,7 +41,7 @@ public function retrieveAndFixBrokenSubscriptions() $parent = $subscription->get_parent(); if ($parent) { $subscription->update_meta_data('_mollie_customer_id', $parent->get_meta('_mollie_customer_id')); - $subscription->update_meta_data('_mollie_order_id', $parent->get_meta('_mollie_order_id')); + $subscription->update_meta_data(MolliePaymentGateway::ORDER_ID_META_KEY, $parent->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY)); $subscription->update_meta_data('_mollie_payment_id', $parent->get_meta('_mollie_payment_id')); $subscription->update_meta_data('_mollie_payment_mode', $parent->get_meta('_mollie_payment_mode')); $subscription->save(); diff --git a/src/Subscription/MollieSubscriptionGateway.php b/src/Subscription/MollieSubscriptionGateway.php index ec3d17fa9..8d003cf3a 100644 --- a/src/Subscription/MollieSubscriptionGateway.php +++ b/src/Subscription/MollieSubscriptionGateway.php @@ -748,7 +748,7 @@ protected function initialPaymentUsedOrderAPI($subscriptionParentOrder): bool if (!$subscriptionParentOrder) { return false; } - $orderIdMeta = $subscriptionParentOrder->get_meta('_mollie_order_id'); + $orderIdMeta = $subscriptionParentOrder->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY); $parentOrderMeta = $orderIdMeta ?: PaymentService::PAYMENT_METHOD_TYPE_PAYMENT; From b61f2d94f491f07aff134e0c9dac84843e818666 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 08:36:39 +0200 Subject: [PATCH 04/23] Move cancel, ship and refund to mollie object --- src/Gateway/MolliePaymentGateway.php | 65 +------- src/Payment/MollieObject.php | 214 +++++++++++++++++++++++++++ src/Payment/PaymentModule.php | 154 ++----------------- 3 files changed, 225 insertions(+), 208 deletions(-) diff --git a/src/Gateway/MolliePaymentGateway.php b/src/Gateway/MolliePaymentGateway.php index 58466a168..10750ef90 100644 --- a/src/Gateway/MolliePaymentGateway.php +++ b/src/Gateway/MolliePaymentGateway.php @@ -709,70 +709,7 @@ protected function activePaymentObject($orderId, $useCache): Payment */ public function process_refund($order_id, $amount = null, $reason = '') { - // Get the WooCommerce order - $order = wc_get_order($order_id); - - // WooCommerce order not found - if (!$order) { - $error_message = "Could not find WooCommerce order $order_id."; - - $this->logger->debug( - __METHOD__ . ' - ' . $error_message - ); - - return new WP_Error('1', $error_message); - } - - // Check if there is a Mollie Payment Order object connected to this WooCommerce order - $payment_object_id = $this->paymentObject()->getActiveMollieOrderId( - $order_id - ); - - // If there is no Mollie Payment Order object, try getting a Mollie Payment Payment object - if (!$payment_object_id) { - $payment_object_id = $this->paymentObject() - ->getActiveMolliePaymentId($order_id); - } - - // Mollie Payment object not found - if (!$payment_object_id) { - $error_message = "Can\'t process refund. Could not find Mollie Payment object id for order $order_id."; - - $this->logger->debug( - __METHOD__ . ' - ' . $error_message - ); - - return new WP_Error('1', $error_message); - } - - try { - $payment_object = $this->paymentFactory - ->getPaymentObject( - $payment_object_id - ); - } catch (ApiException $exception) { - $exceptionMessage = $exception->getMessage(); - $this->logger->debug($exceptionMessage); - return new WP_Error('error', $exceptionMessage); - } - - if (!$payment_object) { - $error_message = "Can\'t process refund. Could not find Mollie Payment object data for order $order_id."; - - $this->logger->debug( - __METHOD__ . ' - ' . $error_message - ); - - return new WP_Error('1', $error_message); - } - - return $payment_object->refund( - $order, - $order_id, - $payment_object, - $amount, - $reason - ); + return $this->paymentObject()->processRefund($order_id, $amount, $reason); } /** diff --git a/src/Payment/MollieObject.php b/src/Payment/MollieObject.php index d7907a606..2ea6b9933 100644 --- a/src/Payment/MollieObject.php +++ b/src/Payment/MollieObject.php @@ -15,6 +15,7 @@ use WC_Payment_Gateway; use Psr\Log\LoggerInterface as Logger; use stdClass; +use WP_Error; class MollieObject { @@ -72,6 +73,73 @@ public function customerId() return self::$customerId; } + public function processRefund(int $order_id, ?float $amount, string $reason) + { + // Get the WooCommerce order + $order = wc_get_order($order_id); + + // WooCommerce order not found + if (!$order) { + $error_message = "Could not find WooCommerce order $order_id."; + + $this->logger->debug( + __METHOD__ . ' - ' . $error_message + ); + + return new WP_Error('1', $error_message); + } + + // Check if there is a Mollie Payment Order object connected to this WooCommerce order + $payment_object_id = $this->getActiveMollieOrderId( + $order_id + ); + + // If there is no Mollie Payment Order object, try getting a Mollie Payment Payment object + if (!$payment_object_id) { + $payment_object_id = $this->getActiveMolliePaymentId($order_id); + } + + // Mollie Payment object not found + if (!$payment_object_id) { + $error_message = "Can\'t process refund. Could not find Mollie Payment object id for order $order_id."; + + $this->logger->debug( + __METHOD__ . ' - ' . $error_message + ); + + return new WP_Error('1', $error_message); + } + + try { + $payment_object = $this->paymentFactory + ->getPaymentObject( + $payment_object_id + ); + } catch (ApiException $exception) { + $exceptionMessage = $exception->getMessage(); + $this->logger->debug($exceptionMessage); + return new WP_Error('error', $exceptionMessage); + } + + if (!$payment_object) { + $error_message = "Can\'t process refund. Could not find Mollie Payment object data for order $order_id."; + + $this->logger->debug( + __METHOD__ . ' - ' . $error_message + ); + + return new WP_Error('1', $error_message); + } + + return $payment_object->refund( + $order, + $order_id, + $payment_object, + $amount, + $reason + ); + } + /** * Get Mollie payment from cache or load from Mollie * Skip cache by setting $use_cache to false @@ -409,6 +477,79 @@ public function hasActiveMollieOrder($order_id) return ! empty($mollie_payment_id); } + /** + * Cancel an order at Mollie. + * + */ + public function cancelOrderAtMollie($orderId) + { + $order = wc_get_order($orderId); + + // Does WooCommerce order contain a Mollie payment? + if (strstr($order->get_payment_method(), 'mollie_wc_gateway_') === false) { + return; + } + + // To disable automatic canceling of the Mollie order when a WooCommerce order status is updated to canceled, + // store an option 'mollie-payments-for-woocommerce_disableCancelOrderAtMollie' with value 1 + if (get_option($this->pluginId . '_' . 'disableCancelOrderAtMollie', '0') === '1') { + return; + } + + $this->logger->debug(__METHOD__ . ' - ' . $orderId . ' - Try to process cancelled order at Mollie.'); + + $mollie_order_id = ( $mollie_order_id = $order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true) ) ? $mollie_order_id : false; + + if ($mollie_order_id === false) { + $message = _x('Order contains Mollie payment method, but not a valid Mollie Order ID. Canceling order failed.', 'Order note info', 'mollie-payments-for-woocommerce'); + $order->add_order_note($message); + $this->logger->debug(__METHOD__ . ' - ' . $orderId . ' - Order contains Mollie payment method, but not a valid Mollie Order ID. Canceling order failed.'); + + return; + } + + $orderStr = "ord_"; + if (substr($mollie_order_id, 0, strlen($orderStr)) !== $orderStr) { + $this->logger->debug(__METHOD__ . ' - ' . $orderId . ' - Order uses Payment API, cannot cancel as order.'); + + return; + } + + $apiKey = $this->settingsHelper->getApiKey(); + try { + // Get the order from the Mollie API + $mollie_order = $this->apiHelper->getApiClient($apiKey)->orders->get($mollie_order_id); + + // Check that order is not already canceled at Mollie + if ($mollie_order->isCanceled()) { + $message = _x('Order already canceled at Mollie, can not be canceled again.', 'Order note info', 'mollie-payments-for-woocommerce'); + $order->add_order_note($message); + $this->logger->debug(__METHOD__ . ' - ' . $orderId . ' - Order already canceled at Mollie, can not be canceled again.'); + + return; + } + + // Check that order has the correct status to be canceled + if ($mollie_order->isCreated() || $mollie_order->isAuthorized() || $mollie_order->isShipping()) { + $this->apiHelper->getApiClient($apiKey)->orders->get($mollie_order_id)->cancel(); + $message = _x('Order also cancelled at Mollie.', 'Order note info', 'mollie-payments-for-woocommerce'); + //todo Check status of the Woo order and cancel it if it is not already cancelled + + $order->add_order_note($message); + $this->logger->debug(__METHOD__ . ' - ' . $orderId . ' - Order cancelled in WooCommerce, also cancelled at Mollie.'); + + return; + } + $message = _x('Order could not be canceled at Mollie, because order status is ', 'Order note info', 'mollie-payments-for-woocommerce'); + $order->add_order_note($message . $mollie_order->status . '.'); + $this->logger->debug(__METHOD__ . ' - ' . $orderId . ' - Order could not be canceled at Mollie, because order status is ' . $mollie_order->status . '.'); + } catch (ApiException $e) { + $this->logger->debug(__METHOD__ . ' - ' . $orderId . ' - Updating order to canceled at Mollie failed, error: ' . $e->getMessage()); + } + + return; + } + /** * @param int $order_id * @param string $payment_id @@ -469,6 +610,79 @@ public function hasCancelledMolliePayment($order_id) return ! empty($cancelled_payment_id); } + /** + * Ship all order lines and capture an order at Mollie. + * + */ + public function shipAndCaptureOrderAtMollie($order_id) + { + $order = wc_get_order($order_id); + + // Does WooCommerce order contain a Mollie payment? + if (strstr($order->get_payment_method(), 'mollie_wc_gateway_') === false) { + return; + } + + // 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 (apply_filters('mollie_wc_gateway_disable_ship_and_capture', get_option($this->pluginId . '_' . 'disableShipOrderAtMollie', '0') === '1', $order)) { + return; + } + + $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Try to process completed order for a potential capture at Mollie.'); + + // Does WooCommerce order contain a Mollie Order? + $mollie_order_id = ( $mollie_order_id = $order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true) ) ? $mollie_order_id : false; + // Is it a payment? you cannot ship a payment + if ($mollie_order_id === false || substr($mollie_order_id, 0, 3) === 'tr_') { + $message = _x('Processing a payment, no capture needed', 'Order note info', 'mollie-payments-for-woocommerce'); + $order->add_order_note($message); + $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Processing a payment, no capture needed.'); + + return; + } + + $apiKey = $this->settingsHelper->getApiKey(); + try { + // Get the order from the Mollie API + $mollie_order = $this->apiHelper->getApiClient($apiKey)->orders->get($mollie_order_id); + + // Check that order is Paid or Authorized and can be captured + if ($mollie_order->isCanceled()) { + $message = _x('Order already canceled at Mollie, can not be shipped/captured.', 'Order note info', 'mollie-payments-for-woocommerce'); + $order->add_order_note($message); + $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order already canceled at Mollie, can not be shipped/captured.'); + + return; + } + + if ($mollie_order->isCompleted()) { + $message = _x('Order already completed at Mollie, can not be shipped/captured.', 'Order note info', 'mollie-payments-for-woocommerce'); + $order->add_order_note($message); + $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order already completed at Mollie, can not be shipped/captured.'); + + return; + } + + if ($mollie_order->isPaid() || $mollie_order->isAuthorized()) { + $this->apiHelper->getApiClient($apiKey)->orders->get($mollie_order_id)->shipAll(); + $message = _x('Order successfully updated to shipped at Mollie, capture of funds underway.', 'Order note info', 'mollie-payments-for-woocommerce'); + $order->add_order_note($message); + $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order successfully updated to shipped at Mollie, capture of funds underway.'); + + return; + } + $message = _x('Order not paid or authorized at Mollie yet, can not be shipped.', 'Order note info', 'mollie-payments-for-woocommerce'); + $order->add_order_note($message); + $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order not paid or authorized at Mollie yet, can not be shipped.'); + } catch (ApiException $e) { + $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Processing shipment & capture failed, error: ' . $e->getMessage()); + } + + return; + } + + public function getMolliePaymentIdFromPaymentObject() { } diff --git a/src/Payment/PaymentModule.php b/src/Payment/PaymentModule.php index c03c03351..71cd588e3 100644 --- a/src/Payment/PaymentModule.php +++ b/src/Payment/PaymentModule.php @@ -107,11 +107,19 @@ public function run(ContainerInterface $container): bool // Custom filter to choose on which action to cancel orders at Mollie $cancelOrderAction = apply_filters('mollie-payments-for-woocommerce_cancel_order', 'woocommerce_order_status_cancelled'); // Cancel order at Mollie (for Orders API/Klarna) - add_action($cancelOrderAction, [ $this, 'cancelOrderAtMollie' ]); + add_action($cancelOrderAction, function ($orderId) use ($container) { + $mollieObject = $container->get(MollieObject::class); + assert($mollieObject instanceof MollieObject); + $mollieObject->cancelOrderAtMollie($orderId); + }); // Custom filter to choose on which action to ship and capture orders at Mollie $captureOrderAction = apply_filters('mollie-payments-for-woocommerce_ship_capture_order', 'woocommerce_order_status_completed'); // Capture order at Mollie (for Orders API/Klarna) - add_action($captureOrderAction, [ $this, 'shipAndCaptureOrderAtMollie' ]); + add_action($captureOrderAction, function ($orderId) use ($container) { + $mollieObject = $container->get(MollieObject::class); + assert($mollieObject instanceof MollieObject); + $mollieObject->shipAndCaptureOrderAtMollie($orderId); + }); add_filter( 'woocommerce_cancel_unpaid_order', @@ -333,148 +341,6 @@ public function onOrderDetails(WC_Order $order) $gateway->displayInstructions($order); } - /** - * Ship all order lines and capture an order at Mollie. - * - */ - public function shipAndCaptureOrderAtMollie($order_id) - { - $order = wc_get_order($order_id); - - // Does WooCommerce order contain a Mollie payment? - if (strstr($order->get_payment_method(), 'mollie_wc_gateway_') === false) { - return; - } - - // 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 (apply_filters('mollie_wc_gateway_disable_ship_and_capture', get_option($this->pluginId . '_' . 'disableShipOrderAtMollie', '0') === '1', $order)) { - return; - } - - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Try to process completed order for a potential capture at Mollie.'); - - // Does WooCommerce order contain a Mollie Order? - $mollie_order_id = ( $mollie_order_id = $order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true) ) ? $mollie_order_id : false; - // Is it a payment? you cannot ship a payment - if ($mollie_order_id === false || substr($mollie_order_id, 0, 3) === 'tr_') { - $message = _x('Processing a payment, no capture needed', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Processing a payment, no capture needed.'); - - return; - } - - $apiKey = $this->settingsHelper->getApiKey(); - try { - // Get the order from the Mollie API - $mollie_order = $this->apiHelper->getApiClient($apiKey)->orders->get($mollie_order_id); - - // Check that order is Paid or Authorized and can be captured - if ($mollie_order->isCanceled()) { - $message = _x('Order already canceled at Mollie, can not be shipped/captured.', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order already canceled at Mollie, can not be shipped/captured.'); - - return; - } - - if ($mollie_order->isCompleted()) { - $message = _x('Order already completed at Mollie, can not be shipped/captured.', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order already completed at Mollie, can not be shipped/captured.'); - - return; - } - - if ($mollie_order->isPaid() || $mollie_order->isAuthorized()) { - $this->apiHelper->getApiClient($apiKey)->orders->get($mollie_order_id)->shipAll(); - $message = _x('Order successfully updated to shipped at Mollie, capture of funds underway.', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order successfully updated to shipped at Mollie, capture of funds underway.'); - - return; - } - $message = _x('Order not paid or authorized at Mollie yet, can not be shipped.', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order not paid or authorized at Mollie yet, can not be shipped.'); - } catch (ApiException $e) { - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Processing shipment & capture failed, error: ' . $e->getMessage()); - } - - return; - } - - /** - * Cancel an order at Mollie. - * - */ - public function cancelOrderAtMollie($order_id) - { - $order = wc_get_order($order_id); - - // Does WooCommerce order contain a Mollie payment? - if (strstr($order->get_payment_method(), 'mollie_wc_gateway_') === false) { - return; - } - - // To disable automatic canceling of the Mollie order when a WooCommerce order status is updated to canceled, - // store an option 'mollie-payments-for-woocommerce_disableCancelOrderAtMollie' with value 1 - if (get_option($this->pluginId . '_' . 'disableCancelOrderAtMollie', '0') === '1') { - return; - } - - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Try to process cancelled order at Mollie.'); - - $mollie_order_id = ( $mollie_order_id = $order->get_meta(MolliePaymentGateway::ORDER_ID_META_KEY, true) ) ? $mollie_order_id : false; - - if ($mollie_order_id === false) { - $message = _x('Order contains Mollie payment method, but not a valid Mollie Order ID. Canceling order failed.', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order contains Mollie payment method, but not a valid Mollie Order ID. Canceling order failed.'); - - return; - } - - $orderStr = "ord_"; - if (substr($mollie_order_id, 0, strlen($orderStr)) !== $orderStr) { - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order uses Payment API, cannot cancel as order.'); - - return; - } - - $apiKey = $this->settingsHelper->getApiKey(); - try { - // Get the order from the Mollie API - $mollie_order = $this->apiHelper->getApiClient($apiKey)->orders->get($mollie_order_id); - - // Check that order is not already canceled at Mollie - if ($mollie_order->isCanceled()) { - $message = _x('Order already canceled at Mollie, can not be canceled again.', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order already canceled at Mollie, can not be canceled again.'); - - return; - } - - // Check that order has the correct status to be canceled - if ($mollie_order->isCreated() || $mollie_order->isAuthorized() || $mollie_order->isShipping()) { - $this->apiHelper->getApiClient($apiKey)->orders->get($mollie_order_id)->cancel(); - $message = _x('Order also cancelled at Mollie.', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order cancelled in WooCommerce, also cancelled at Mollie.'); - - return; - } - $message = _x('Order could not be canceled at Mollie, because order status is ', 'Order note info', 'mollie-payments-for-woocommerce'); - $order->add_order_note($message . $mollie_order->status . '.'); - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Order could not be canceled at Mollie, because order status is ' . $mollie_order->status . '.'); - } catch (ApiException $e) { - $this->logger->debug(__METHOD__ . ' - ' . $order_id . ' - Updating order to canceled at Mollie failed, error: ' . $e->getMessage()); - } - - return; - } /** * Add/remove scheduled action to cancel orders on expiration date From fe83804c6abccb43c0101721c946e0bb85d0b5cf Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 08:37:17 +0200 Subject: [PATCH 05/23] Create Mollie Api Module --- src/PluginApi/PluginApiModule.php | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/PluginApi/PluginApiModule.php diff --git a/src/PluginApi/PluginApiModule.php b/src/PluginApi/PluginApiModule.php new file mode 100644 index 000000000..1b88c9e4b --- /dev/null +++ b/src/PluginApi/PluginApiModule.php @@ -0,0 +1,33 @@ + static function (ContainerInterface $container): MolliePluginApi { + MolliePluginApi::init( + $container->get(CapturePayment::class), + $container->get(VoidPayment::class), + $container->get(MollieObject::class) + ); + return MolliePluginApi::getInstance(); + }, + ]; + } +} From 91b72ca7a388cdab73fbbd490f80d96944f39bbc Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 08:39:34 +0200 Subject: [PATCH 06/23] Create Mollie Plugin Api class This class acts as intermediate with the new functions, to handle Mollie object actions --- src/PluginApi/MolliePluginApi.php | 103 ++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/PluginApi/MolliePluginApi.php diff --git a/src/PluginApi/MolliePluginApi.php b/src/PluginApi/MolliePluginApi.php new file mode 100644 index 000000000..9d1a87bf7 --- /dev/null +++ b/src/PluginApi/MolliePluginApi.php @@ -0,0 +1,103 @@ +capturePayment = $capturePayment; + $this->voidPayment = $voidPayment; + $this->mollieObject = $mollieObject; + } + + /** + * Initializes the MolliePluginApi with necessary dependencies. + */ + public static function init( + CapturePayment $capturePayment, + VoidPayment $voidPayment, + MollieObject $mollieObject + ): void { + if (self::$instance === null) { + self::$instance = new self( + $capturePayment, + $voidPayment, + $mollieObject + ); + } + } + + /** + * Returns the singleton instance of MolliePluginApi. + * + * @throws \LogicException If the API has not been initialized. + */ + public static function getInstance(): self { + if (self::$instance === null) { + throw new \LogicException('MolliePluginApi has not been initialized.'); + } + return self::$instance; + } + + /** + * Captures the Mollie order for the given WooCommerce order. + * + * @param \WC_Order $wcOrder The WooCommerce order. + */ + public function captureOrder(\WC_Order $wcOrder): void { + $this->capturePayment($wcOrder->get_id()); + } + + /** + * Refunds the Mollie order for the given WooCommerce order. + * + * @param \WC_Order $wcOrder The WooCommerce order. + * @param float $amount The refund amount. + * @param string $reason The reason for the refund. + * @return \WP_Error|array The result of the refund operation. + */ + public function refundOrder(\WC_Order $wcOrder, float $amount, string $reason = '') { + return $this->mollieObject->processRefund($wcOrder->get_id(), $amount , $reason); + } + + /** + * Voids the authorization for the given WooCommerce order. + * + * @param \WC_Order $wcOrder The WooCommerce order. + */ + public function voidOrder(\WC_Order $wcOrder): void { + $this->voidPayment($wcOrder->get_id()); + } + + /** + * Cancels the Order at Mollie and also in WooCommerce if was not already done. + * + * @param \WC_Order $wcOrder The WooCommerce order. + */ + public function cancelOrder(string $orderId): void { + $this->mollieObject->cancelOrderAtMollie($orderId); + } + + /** + * Ship all order lines and capture an order at Mollie. + * + * @param string $orderId The WooCommerce order ID. + */ + public function shipOrderAndCapture(string $orderId): void + { + $this->mollieObject->shipAndCaptureOrderAtMollie($orderId); + } +} From 02fee5f7b98b9f5e612e37a15ba3d6d065f544e2 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 08:42:44 +0200 Subject: [PATCH 07/23] Add plugin api module to modules list --- mollie-payments-for-woocommerce.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mollie-payments-for-woocommerce.php b/mollie-payments-for-woocommerce.php index 2085bf537..9d67cb9b6 100644 --- a/mollie-payments-for-woocommerce.php +++ b/mollie-payments-for-woocommerce.php @@ -26,6 +26,7 @@ use Mollie\WooCommerce\Activation\ActivationModule; use Mollie\WooCommerce\Activation\ConstraintsChecker; use Mollie\WooCommerce\Assets\AssetsModule; +use Mollie\WooCommerce\PluginApi\PluginApiModule; use Mollie\WooCommerce\Shared\SharedModule; use Mollie\WooCommerce\Gateway\GatewayModule; use Mollie\WooCommerce\Gateway\Voucher\VoucherModule; @@ -165,6 +166,7 @@ function initialize() new PaymentModule(), new MerchantCaptureModule(), new UninstallModule(), + new PluginApiModule(), ]; $modules = apply_filters('mollie_wc_plugin_modules', $modules); $bootstrap->boot(...$modules); From d73e6c78e43716df706af7c13dc3c62f5973d17d Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 09:19:29 +0200 Subject: [PATCH 08/23] Bail if no order --- src/Payment/MollieObject.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Payment/MollieObject.php b/src/Payment/MollieObject.php index 2ea6b9933..183109e03 100644 --- a/src/Payment/MollieObject.php +++ b/src/Payment/MollieObject.php @@ -73,6 +73,12 @@ public function customerId() return self::$customerId; } + /** + * @param int $order_id + * @param float|null $amount + * @param string $reason + * @return bool|WP_Error + */ public function processRefund(int $order_id, ?float $amount, string $reason) { // Get the WooCommerce order @@ -485,6 +491,12 @@ public function cancelOrderAtMollie($orderId) { $order = wc_get_order($orderId); + if (!$order) { + // Order not found, log and exit early. + $this->logger->debug(__METHOD__ . ' - Order not found.'); + return; + } + // Does WooCommerce order contain a Mollie payment? if (strstr($order->get_payment_method(), 'mollie_wc_gateway_') === false) { return; @@ -618,6 +630,12 @@ public function shipAndCaptureOrderAtMollie($order_id) { $order = wc_get_order($order_id); + if (!$order) { + // Order not found, log and exit early. + $this->logger->debug(__METHOD__ . ' - Order not found.'); + return; + } + // Does WooCommerce order contain a Mollie payment? if (strstr($order->get_payment_method(), 'mollie_wc_gateway_') === false) { return; From 240303ec0ff0289f913d4b1ff78a6b740235894e Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 09:20:10 +0200 Subject: [PATCH 09/23] Add functions to trigger commands --- inc/api/order-functions.php | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 inc/api/order-functions.php diff --git a/inc/api/order-functions.php b/inc/api/order-functions.php new file mode 100644 index 000000000..48e7100f8 --- /dev/null +++ b/inc/api/order-functions.php @@ -0,0 +1,84 @@ +captureOrder($wc_order); +} + +/** + * Refunds the Mollie order. + * + * @param WC_Order $wc_order The WC order. + * @param float $amount The refund amount. + * @param string $reason The reason for the refund. + * @return \WP_Error|bool The result of the refund operation. + */ +function mollie_refund_order(WC_Order $wc_order, float $amount, string $reason = '') { + $mollieApi = MolliePluginApi::getInstance(); + return $mollieApi->refundOrder($wc_order, $amount, $reason); +} + +/** + * Voids the authorization. + * Logs the result of the operation. + * + * @param WC_Order $wc_order The WC order. + * + */ +function mollie_void_order(WC_Order $wc_order): void { + $mollieApi = MolliePluginApi::getInstance(); + $mollieApi->voidOrder($wc_order); +} + +/** + * Cancels the order at Mollie and also in WooCommerce if was not already done. + * Logs the result of the operation. + * + * @param WC_Order $wc_order The WC order. + */ +function mollie_cancel_order(WC_Order $wc_order): void { + $order_id = $wc_order->get_id(); + $mollieApi = MolliePluginApi::getInstance(); + $mollieApi->cancelOrder((string)$order_id); +} + +/** + * Ship all order lines and capture an order at Mollie. + * Logs the result of the operation. + * + * @param WC_Order $wc_order The WC order. + * + */ +function mollie_ship_order(WC_Order $wc_order): void +{ + $order_id = $wc_order->get_id(); + $mollieApi = MolliePluginApi::getInstance(); + $mollieApi->shipOrderAndCapture((string)$order_id); +} + + From 8fd643bbbb81a559932b99020f40d79a0468a128 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 09:20:47 +0200 Subject: [PATCH 10/23] Fix doc blocks --- src/PluginApi/MolliePluginApi.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PluginApi/MolliePluginApi.php b/src/PluginApi/MolliePluginApi.php index 9d1a87bf7..a56235996 100644 --- a/src/PluginApi/MolliePluginApi.php +++ b/src/PluginApi/MolliePluginApi.php @@ -54,6 +54,7 @@ public static function getInstance(): self { /** * Captures the Mollie order for the given WooCommerce order. + * Logs the result of the operation. * * @param \WC_Order $wcOrder The WooCommerce order. */ @@ -67,7 +68,7 @@ public function captureOrder(\WC_Order $wcOrder): void { * @param \WC_Order $wcOrder The WooCommerce order. * @param float $amount The refund amount. * @param string $reason The reason for the refund. - * @return \WP_Error|array The result of the refund operation. + * @return \WP_Error|bool The result of the refund operation. */ public function refundOrder(\WC_Order $wcOrder, float $amount, string $reason = '') { return $this->mollieObject->processRefund($wcOrder->get_id(), $amount , $reason); @@ -75,6 +76,7 @@ public function refundOrder(\WC_Order $wcOrder, float $amount, string $reason = /** * Voids the authorization for the given WooCommerce order. + * Logs the result of the operation. * * @param \WC_Order $wcOrder The WooCommerce order. */ @@ -84,6 +86,7 @@ public function voidOrder(\WC_Order $wcOrder): void { /** * Cancels the Order at Mollie and also in WooCommerce if was not already done. + * Logs the result of the operation. * * @param \WC_Order $wcOrder The WooCommerce order. */ @@ -93,6 +96,7 @@ public function cancelOrder(string $orderId): void { /** * Ship all order lines and capture an order at Mollie. + * Logs the result of the operation. * * @param string $orderId The WooCommerce order ID. */ From c01c3ed424feebf9564e9bca6cf53aa27eba2d63 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 17:11:35 +0200 Subject: [PATCH 11/23] Use capture and void as closures --- src/PluginApi/MolliePluginApi.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/PluginApi/MolliePluginApi.php b/src/PluginApi/MolliePluginApi.php index a56235996..a1e271437 100644 --- a/src/PluginApi/MolliePluginApi.php +++ b/src/PluginApi/MolliePluginApi.php @@ -2,20 +2,21 @@ namespace Mollie\WooCommerce\PluginApi; +use Closure; use Mollie\WooCommerce\MerchantCapture\Capture\Action\CapturePayment; use Mollie\WooCommerce\MerchantCapture\Capture\Action\VoidPayment; use Mollie\WooCommerce\Payment\MollieObject; class MolliePluginApi { private static $instance = null; - private CapturePayment $capturePayment; - private VoidPayment $voidPayment; + private Closure $capturePayment; + private Closure $voidPayment; private MollieObject $mollieObject; private function __construct( - CapturePayment $capturePayment, - VoidPayment $voidPayment, + Closure $capturePayment, + Closure $voidPayment, MollieObject $mollieObject ) { $this->capturePayment = $capturePayment; @@ -27,8 +28,8 @@ private function __construct( * Initializes the MolliePluginApi with necessary dependencies. */ public static function init( - CapturePayment $capturePayment, - VoidPayment $voidPayment, + Closure $capturePayment, + Closure $voidPayment, MollieObject $mollieObject ): void { if (self::$instance === null) { @@ -59,7 +60,7 @@ public static function getInstance(): self { * @param \WC_Order $wcOrder The WooCommerce order. */ public function captureOrder(\WC_Order $wcOrder): void { - $this->capturePayment($wcOrder->get_id()); + ($this->capturePayment)($wcOrder->get_id()); } /** @@ -81,7 +82,7 @@ public function refundOrder(\WC_Order $wcOrder, float $amount, string $reason = * @param \WC_Order $wcOrder The WooCommerce order. */ public function voidOrder(\WC_Order $wcOrder): void { - $this->voidPayment($wcOrder->get_id()); + ($this->voidPayment)($wcOrder->get_id()); } /** From 0eec7144188e83c08e46200a098b6bab1c31151b Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 17:13:13 +0200 Subject: [PATCH 12/23] Init instance on executable run --- src/PluginApi/PluginApiModule.php | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/PluginApi/PluginApiModule.php b/src/PluginApi/PluginApiModule.php index 1b88c9e4b..3294d4983 100644 --- a/src/PluginApi/PluginApiModule.php +++ b/src/PluginApi/PluginApiModule.php @@ -6,28 +6,23 @@ namespace Mollie\WooCommerce\PluginApi; +use Inpsyde\Modularity\Module\ExecutableModule; use Inpsyde\Modularity\Module\ModuleClassNameIdTrait; -use Inpsyde\Modularity\Module\ServiceModule; use Mollie\WooCommerce\MerchantCapture\Capture\Action\CapturePayment; use Mollie\WooCommerce\MerchantCapture\Capture\Action\VoidPayment; use Mollie\WooCommerce\Payment\MollieObject; use Psr\Container\ContainerInterface; -class PluginApiModule implements ServiceModule +class PluginApiModule implements ExecutableModule { use ModuleClassNameIdTrait; - public function services(): array - { - return [ - MolliePluginApi::class => static function (ContainerInterface $container): MolliePluginApi { - MolliePluginApi::init( - $container->get(CapturePayment::class), - $container->get(VoidPayment::class), - $container->get(MollieObject::class) - ); - return MolliePluginApi::getInstance(); - }, - ]; + public function run (ContainerInterface $container): bool { + MolliePluginApi::init( + $container->get(CapturePayment::class), + $container->get(VoidPayment::class), + $container->get(MollieObject::class) + ); + return true; } } From 4cdd25065939ca3323ac3d8e6a26257ac04d0fe0 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 22 Oct 2024 17:14:43 +0200 Subject: [PATCH 13/23] Update modules bootstrap after merge --- mollie-payments-for-woocommerce.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mollie-payments-for-woocommerce.php b/mollie-payments-for-woocommerce.php index 9d67cb9b6..cfd55f11f 100644 --- a/mollie-payments-for-woocommerce.php +++ b/mollie-payments-for-woocommerce.php @@ -169,7 +169,11 @@ function initialize() new PluginApiModule(), ]; $modules = apply_filters('mollie_wc_plugin_modules', $modules); - $bootstrap->boot(...$modules); + foreach ($modules as $module) { + $bootstrap->addModule($module); + } + $bootstrap->boot(); + } catch (Throwable $throwable) { handleException($throwable); } From b73e68d218301a4ce3cf09a201641760427fdf54 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Wed, 23 Oct 2024 08:58:01 +0200 Subject: [PATCH 14/23] Remove dependencies --- src/PluginApi/MolliePluginApi.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PluginApi/MolliePluginApi.php b/src/PluginApi/MolliePluginApi.php index a1e271437..7f3a1c1f7 100644 --- a/src/PluginApi/MolliePluginApi.php +++ b/src/PluginApi/MolliePluginApi.php @@ -3,8 +3,6 @@ namespace Mollie\WooCommerce\PluginApi; use Closure; -use Mollie\WooCommerce\MerchantCapture\Capture\Action\CapturePayment; -use Mollie\WooCommerce\MerchantCapture\Capture\Action\VoidPayment; use Mollie\WooCommerce\Payment\MollieObject; class MolliePluginApi { From 6869420d68b9868526db36dbb30fe8ea11d91c4c Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Wed, 23 Oct 2024 08:58:33 +0200 Subject: [PATCH 15/23] Add order-function tests --- .../OrderFunctions/MollieOrderTest.php | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/php/Functional/OrderFunctions/MollieOrderTest.php diff --git a/tests/php/Functional/OrderFunctions/MollieOrderTest.php b/tests/php/Functional/OrderFunctions/MollieOrderTest.php new file mode 100644 index 000000000..6ff51748d --- /dev/null +++ b/tests/php/Functional/OrderFunctions/MollieOrderTest.php @@ -0,0 +1,116 @@ +createMock(WC_Order::class); + $mockOrder->method('get_id')->willReturn('123'); + $capturePaymentMock = $this->getMockBuilder(stdClass::class) + ->addMethods(['__invoke']) + ->getMock(); + $capturePaymentClosure = function ($orderId) use ($capturePaymentMock) { + $capturePaymentMock->__invoke($orderId); + }; + $capturePaymentMock->expects($this->once()) + ->method('__invoke') + ->with($this->equalTo(123)); + + $voidPaymentMock = function() {}; + $mollieObjectMock = $this->createMock(MollieObject::class); + + MolliePluginApi::init($capturePaymentClosure, $voidPaymentMock, $mollieObjectMock); + mollie_capture_order($mockOrder); + } + + /** + * @runInSeparateProcess + */ + public function test_voidOrder_invokes_voidPayment_with_correct_order() { + $mockOrder = $this->createMock(WC_Order::class); + $mockOrder->method('get_id')->willReturn('123'); + $voidPaymentMock = $this->getMockBuilder(stdClass::class) + ->addMethods(['__invoke']) + ->getMock(); + $voidPaymentClosure = function ($orderId) use ($voidPaymentMock) { + $voidPaymentMock->__invoke($orderId); + }; + $voidPaymentMock->expects($this->once()) + ->method('__invoke') + ->with($this->equalTo(123)); + + $capturePaymentMock = function() {}; + $mollieObjectMock = $this->createMock(MollieObject::class); + + MolliePluginApi::init($capturePaymentMock, $voidPaymentClosure, $mollieObjectMock); + mollie_void_order($mockOrder); + } + + /** + * @runInSeparateProcess + */ + public function test_refundOrder_with_correct_order() { + $mockOrder = $this->createMock(WC_Order::class); + $mockOrder->method('get_id')->willReturn('123'); + $voidPaymentMock = function() {}; + $capturePaymentMock = function() {}; + $mollieObjectMock = $this->createConfiguredMock(MollieObject::class, [ + 'processRefund' => true + ]); + $mollieObjectMock->expects($this->once())->method('processRefund')->with('123', 10.0, 'reason'); + + MolliePluginApi::init($capturePaymentMock, $voidPaymentMock, $mollieObjectMock); + mollie_refund_order($mockOrder, 10.0, 'reason'); + } + + /** + * @runInSeparateProcess + */ + public function test_cancelOrder_with_correct_order() { + $mockOrder = $this->createMock(WC_Order::class); + $mockOrder->method('get_id')->willReturn('123'); + $voidPaymentMock = function() {}; + $capturePaymentMock = function() {}; + $mollieObjectMock = $this->createConfiguredMock(MollieObject::class, [ + 'cancelOrderAtMollie' => true + ]); + $mollieObjectMock->expects($this->once())->method('cancelOrderAtMollie')->with('123'); + + MolliePluginApi::init($capturePaymentMock, $voidPaymentMock, $mollieObjectMock); + mollie_cancel_order($mockOrder); + } + + /** + * @runInSeparateProcess + */ + public function test_shipOrder_with_correct_order() { + $mockOrder = $this->createMock(WC_Order::class); + $mockOrder->method('get_id')->willReturn('123'); + $voidPaymentMock = function() {}; + $capturePaymentMock = function() {}; + $mollieObjectMock = $this->createConfiguredMock(MollieObject::class, [ + 'shipAndCaptureOrderAtMollie' => true + ]); + $mollieObjectMock->expects($this->once())->method('shipAndCaptureOrderAtMollie')->with('123'); + + MolliePluginApi::init($capturePaymentMock, $voidPaymentMock, $mollieObjectMock); + mollie_ship_order($mockOrder); + } +} From eaf3f4f32e893231b7e70eadd656117492b94a19 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Wed, 23 Oct 2024 09:08:10 +0200 Subject: [PATCH 16/23] Document actions --- documentation/Plugin-API-Functions.md | 64 +++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 documentation/Plugin-API-Functions.md diff --git a/documentation/Plugin-API-Functions.md b/documentation/Plugin-API-Functions.md new file mode 100644 index 000000000..e50220c39 --- /dev/null +++ b/documentation/Plugin-API-Functions.md @@ -0,0 +1,64 @@ +### Programmatically capture, refund, void, cancel, and ship Mollie orders. + +With the Mollie API, you can programmatically capture, refund, void, cancel, and ship orders. +These actions are logged by the plugin. +Here are some examples of how to use these functions: + +#### Capture an order +```php +use function Mollie\WooCommerce\Inc\Api\mollie_capture_order; + +add_action('init', function () { + $order_id = 123; + $order = wc_get_order($order_id); + mollie_capture_order($order); +}); +``` + +#### Refund an order +```php +use function Mollie\WooCommerce\Inc\Api\mollie_refund_order; + +add_action('init', function () { + $order_id = 123; + $order = wc_get_order($order_id); + mollie_refund_order($order); +}); +``` + +#### Void an order +```php +use function Mollie\WooCommerce\Inc\Api\mollie_void_order; + +add_action('init', function () { + $order_id = 123; + $order = wc_get_order($order_id); + mollie_void_order($order); +}); +``` + +#### Cancel an order +```php +use function Mollie\WooCommerce\Inc\Api\mollie_cancel_order; + +add_action('init', function () { + $order_id = 123; + $order = wc_get_order($order_id); + mollie_cancel_order($order); +}); +``` + +#### Ship an order +```php +use function Mollie\WooCommerce\Inc\Api\mollie_ship_order; + +add_action('init', function () { + $order_id = 123; + $order = wc_get_order($order_id); + mollie_ship_order($order); +}); +``` + + + + From e427c0519f14a3b9f3fd67e479052cbaa61041dc Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Wed, 23 Oct 2024 09:15:13 +0200 Subject: [PATCH 17/23] Fix CS --- inc/api/order-functions.php | 23 +++++++++++++---------- src/Gateway/GatewayModule.php | 1 + src/Payment/MollieObject.php | 1 - src/PluginApi/MolliePluginApi.php | 28 ++++++++++++++++++++-------- src/PluginApi/PluginApiModule.php | 4 +++- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/inc/api/order-functions.php b/inc/api/order-functions.php index 48e7100f8..094fa654f 100644 --- a/inc/api/order-functions.php +++ b/inc/api/order-functions.php @@ -1,6 +1,5 @@ captureOrder($wc_order); } @@ -38,7 +37,9 @@ function mollie_capture_order(WC_Order $wc_order): void { * @param string $reason The reason for the refund. * @return \WP_Error|bool The result of the refund operation. */ -function mollie_refund_order(WC_Order $wc_order, float $amount, string $reason = '') { +function mollie_refund_order(WC_Order $wc_order, float $amount, string $reason = '') +{ + $mollieApi = MolliePluginApi::getInstance(); return $mollieApi->refundOrder($wc_order, $amount, $reason); } @@ -50,7 +51,9 @@ function mollie_refund_order(WC_Order $wc_order, float $amount, string $reason = * @param WC_Order $wc_order The WC order. * */ -function mollie_void_order(WC_Order $wc_order): void { +function mollie_void_order(WC_Order $wc_order): void +{ + $mollieApi = MolliePluginApi::getInstance(); $mollieApi->voidOrder($wc_order); } @@ -61,7 +64,9 @@ function mollie_void_order(WC_Order $wc_order): void { * * @param WC_Order $wc_order The WC order. */ -function mollie_cancel_order(WC_Order $wc_order): void { +function mollie_cancel_order(WC_Order $wc_order): void +{ + $order_id = $wc_order->get_id(); $mollieApi = MolliePluginApi::getInstance(); $mollieApi->cancelOrder((string)$order_id); @@ -80,5 +85,3 @@ function mollie_ship_order(WC_Order $wc_order): void $mollieApi = MolliePluginApi::getInstance(); $mollieApi->shipOrderAndCapture((string)$order_id); } - - diff --git a/src/Gateway/GatewayModule.php b/src/Gateway/GatewayModule.php index 72cade0d9..b8987f101 100644 --- a/src/Gateway/GatewayModule.php +++ b/src/Gateway/GatewayModule.php @@ -707,6 +707,7 @@ public function buildPaymentMethod( Surcharge $surchargeService, array $apiMethod ) { + $transformedId = ucfirst($id); $paymentMethodClassName = 'Mollie\\WooCommerce\\PaymentMethods\\' . $transformedId; $paymentMethod = new $paymentMethodClassName( diff --git a/src/Payment/MollieObject.php b/src/Payment/MollieObject.php index 183109e03..25cc08e4d 100644 --- a/src/Payment/MollieObject.php +++ b/src/Payment/MollieObject.php @@ -700,7 +700,6 @@ public function shipAndCaptureOrderAtMollie($order_id) return; } - public function getMolliePaymentIdFromPaymentObject() { } diff --git a/src/PluginApi/MolliePluginApi.php b/src/PluginApi/MolliePluginApi.php index 7f3a1c1f7..040a7af10 100644 --- a/src/PluginApi/MolliePluginApi.php +++ b/src/PluginApi/MolliePluginApi.php @@ -5,11 +5,11 @@ use Closure; use Mollie\WooCommerce\Payment\MollieObject; -class MolliePluginApi { +class MolliePluginApi +{ private static $instance = null; private Closure $capturePayment; private Closure $voidPayment; - private MollieObject $mollieObject; private function __construct( @@ -17,6 +17,7 @@ private function __construct( Closure $voidPayment, MollieObject $mollieObject ) { + $this->capturePayment = $capturePayment; $this->voidPayment = $voidPayment; $this->mollieObject = $mollieObject; @@ -30,6 +31,7 @@ public static function init( Closure $voidPayment, MollieObject $mollieObject ): void { + if (self::$instance === null) { self::$instance = new self( $capturePayment, @@ -44,7 +46,9 @@ public static function init( * * @throws \LogicException If the API has not been initialized. */ - public static function getInstance(): self { + public static function getInstance(): self + { + if (self::$instance === null) { throw new \LogicException('MolliePluginApi has not been initialized.'); } @@ -57,7 +61,9 @@ public static function getInstance(): self { * * @param \WC_Order $wcOrder The WooCommerce order. */ - public function captureOrder(\WC_Order $wcOrder): void { + public function captureOrder(\WC_Order $wcOrder): void + { + ($this->capturePayment)($wcOrder->get_id()); } @@ -69,8 +75,10 @@ public function captureOrder(\WC_Order $wcOrder): void { * @param string $reason The reason for the refund. * @return \WP_Error|bool The result of the refund operation. */ - public function refundOrder(\WC_Order $wcOrder, float $amount, string $reason = '') { - return $this->mollieObject->processRefund($wcOrder->get_id(), $amount , $reason); + public function refundOrder(\WC_Order $wcOrder, float $amount, string $reason = '') + { + + return $this->mollieObject->processRefund($wcOrder->get_id(), $amount, $reason); } /** @@ -79,7 +87,9 @@ public function refundOrder(\WC_Order $wcOrder, float $amount, string $reason = * * @param \WC_Order $wcOrder The WooCommerce order. */ - public function voidOrder(\WC_Order $wcOrder): void { + public function voidOrder(\WC_Order $wcOrder): void + { + ($this->voidPayment)($wcOrder->get_id()); } @@ -89,7 +99,9 @@ public function voidOrder(\WC_Order $wcOrder): void { * * @param \WC_Order $wcOrder The WooCommerce order. */ - public function cancelOrder(string $orderId): void { + public function cancelOrder(string $orderId): void + { + $this->mollieObject->cancelOrderAtMollie($orderId); } diff --git a/src/PluginApi/PluginApiModule.php b/src/PluginApi/PluginApiModule.php index 3294d4983..c63b35ed4 100644 --- a/src/PluginApi/PluginApiModule.php +++ b/src/PluginApi/PluginApiModule.php @@ -17,7 +17,9 @@ class PluginApiModule implements ExecutableModule { use ModuleClassNameIdTrait; - public function run (ContainerInterface $container): bool { + public function run(ContainerInterface $container): bool + { + MolliePluginApi::init( $container->get(CapturePayment::class), $container->get(VoidPayment::class), From b649641ec070a3bce13f7a8fcba9fd26776d6d83 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Wed, 23 Oct 2024 11:37:25 +0200 Subject: [PATCH 18/23] Update test version --- phpcs.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 3f6cd5194..33e29a680 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -7,7 +7,7 @@ ./src ./tests/ - + From cc68201dc326ba14ced0680b2d16b307bf830052 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Fri, 25 Oct 2024 11:00:37 +0200 Subject: [PATCH 19/23] Change hook name --- src/Payment/PaymentModule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Payment/PaymentModule.php b/src/Payment/PaymentModule.php index 71cd588e3..85cb31fca 100644 --- a/src/Payment/PaymentModule.php +++ b/src/Payment/PaymentModule.php @@ -105,7 +105,7 @@ public function run(ContainerInterface $container): bool // Show Mollie instructions on order details page add_action('woocommerce_order_details_after_order_table', [ $this, 'onOrderDetails' ], 10, 1); // Custom filter to choose on which action to cancel orders at Mollie - $cancelOrderAction = apply_filters('mollie-payments-for-woocommerce_cancel_order', 'woocommerce_order_status_cancelled'); + $cancelOrderAction = apply_filters('mollie-payments-for-woocommerce_cancel_order_action', 'woocommerce_order_status_cancelled'); // Cancel order at Mollie (for Orders API/Klarna) add_action($cancelOrderAction, function ($orderId) use ($container) { $mollieObject = $container->get(MollieObject::class); @@ -113,7 +113,7 @@ public function run(ContainerInterface $container): bool $mollieObject->cancelOrderAtMollie($orderId); }); // Custom filter to choose on which action to ship and capture orders at Mollie - $captureOrderAction = apply_filters('mollie-payments-for-woocommerce_ship_capture_order', 'woocommerce_order_status_completed'); + $captureOrderAction = apply_filters('mollie-payments-for-woocommerce_ship_capture_order_action', 'woocommerce_order_status_completed'); // Capture order at Mollie (for Orders API/Klarna) add_action($captureOrderAction, function ($orderId) use ($container) { $mollieObject = $container->get(MollieObject::class); From 03bcfcab5e7bd09c6de9484f97c955a3a40362c9 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Mon, 28 Oct 2024 14:17:00 +0100 Subject: [PATCH 20/23] Do not assume Woo refund --- src/Payment/MollieOrder.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Payment/MollieOrder.php b/src/Payment/MollieOrder.php index 64e9cf1fc..13c158732 100644 --- a/src/Payment/MollieOrder.php +++ b/src/Payment/MollieOrder.php @@ -601,7 +601,12 @@ public function refund(WC_Order $order, $orderId, $paymentObject, $amount = null $refunds = $order->get_refunds(); // Get latest refund - $woocommerceRefund = wc_get_order($refunds[0]); + $woocommerceRefund = isset($refunds[0]) ? wc_get_order($refunds[0]) : false; + + if(empty($woocommerceRefund)) { + $this->logger->debug(__METHOD__ . ' - No WooCoommerce refunds found for order ' . $orderId . ' perfoming an amount refund to Mollie API'); + return $this->refund_amount($order, $amount, $paymentObject, $reason); + } // Get order items from refund $items = $woocommerceRefund->get_items([ 'line_item', 'fee', 'shipping' ]); From 4c7a6b4db214046a6b92f966929bb08f492dadc0 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Mon, 28 Oct 2024 14:17:20 +0100 Subject: [PATCH 21/23] Hook into refund action and return it --- src/PluginApi/MolliePluginApi.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/PluginApi/MolliePluginApi.php b/src/PluginApi/MolliePluginApi.php index 040a7af10..4869b6a46 100644 --- a/src/PluginApi/MolliePluginApi.php +++ b/src/PluginApi/MolliePluginApi.php @@ -3,6 +3,7 @@ namespace Mollie\WooCommerce\PluginApi; use Closure; +use Mollie\Api\Resources\Refund; use Mollie\WooCommerce\Payment\MollieObject; class MolliePluginApi @@ -73,12 +74,16 @@ public function captureOrder(\WC_Order $wcOrder): void * @param \WC_Order $wcOrder The WooCommerce order. * @param float $amount The refund amount. * @param string $reason The reason for the refund. - * @return \WP_Error|bool The result of the refund operation. + * @return \WP_Error|Refund The result of the refund operation. */ public function refundOrder(\WC_Order $wcOrder, float $amount, string $reason = '') { - - return $this->mollieObject->processRefund($wcOrder->get_id(), $amount, $reason); + $mollieRefund = null; + add_action('mollie-payments-for-woocommerce_refund_amount_created', function ($refund, $order, $amount) use (&$mollieRefund) { + $mollieRefund = $refund; + }, 10, 3); + $refundCreated = $this->mollieObject->processRefund($wcOrder->get_id(), $amount, $reason); + return $refundCreated ? $mollieRefund : new \WP_Error('mollie_refund_failed', __('Refund failed', 'mollie-payments-for-woocommerce')); } /** From 7beb8951d8c17cb42e8af57e908c174480dfefe1 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Mon, 28 Oct 2024 14:17:42 +0100 Subject: [PATCH 22/23] Return refund in action --- inc/api/order-functions.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inc/api/order-functions.php b/inc/api/order-functions.php index 094fa654f..5af9c899f 100644 --- a/inc/api/order-functions.php +++ b/inc/api/order-functions.php @@ -12,6 +12,7 @@ namespace Mollie\WooCommerce\Inc\Api; +use Mollie\Api\Resources\Refund; use Mollie\WooCommerce\PluginApi\MolliePluginApi; use WC_Order; @@ -35,7 +36,7 @@ function mollie_capture_order(WC_Order $wc_order): void * @param WC_Order $wc_order The WC order. * @param float $amount The refund amount. * @param string $reason The reason for the refund. - * @return \WP_Error|bool The result of the refund operation. + * @return \WP_Error|Refund The result of the refund operation. */ function mollie_refund_order(WC_Order $wc_order, float $amount, string $reason = '') { From 3a8f4470e4f38b9276194919e2088f1196f82554 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 29 Oct 2024 12:13:58 +0100 Subject: [PATCH 23/23] Update documentation --- documentation/Plugin-API-Functions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/Plugin-API-Functions.md b/documentation/Plugin-API-Functions.md index e50220c39..be96422b6 100644 --- a/documentation/Plugin-API-Functions.md +++ b/documentation/Plugin-API-Functions.md @@ -22,7 +22,8 @@ use function Mollie\WooCommerce\Inc\Api\mollie_refund_order; add_action('init', function () { $order_id = 123; $order = wc_get_order($order_id); - mollie_refund_order($order); + $refund = mollie_refund_order($order, 10.00, 'Refund reason'); + // $refund is an instance of Mollie\Api\Resources\Refund }); ```