diff --git a/mollie-payments-for-woocommerce.php b/mollie-payments-for-woocommerce.php index 7fc6a19d8..e5805eff0 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.1 + * Version: 7.5.0-beta1 * Author: Mollie * Author URI: https://www.mollie.com * Requires at least: 5.0 diff --git a/public/images/blik.svg b/public/images/blik.svg new file mode 100644 index 000000000..1a9dd87b8 --- /dev/null +++ b/public/images/blik.svg @@ -0,0 +1 @@ + diff --git a/public/images/twint.svg b/public/images/twint.svg new file mode 100644 index 000000000..7e09e4aac --- /dev/null +++ b/public/images/twint.svg @@ -0,0 +1,44 @@ + diff --git a/resources/js/mollieIn3.js b/resources/js/mollieIn3.js deleted file mode 100644 index 0905a1a83..000000000 --- a/resources/js/mollieIn3.js +++ /dev/null @@ -1,44 +0,0 @@ -import {maybeRequireField, saveOriginalField} from "./wooCheckoutFieldsUtility"; - -( - function ({jQuery}) { - let positionField = 'li.wc_payment_method.payment_method_mollie_wc_gateway_in3'; - let gateway = 'mollie_wc_gateway_in3'; - let inputPhoneName = 'billing_phone'; - let originalPhone = saveOriginalField(inputPhoneName, {}); - let phoneId = 'billing_phone_field'; - let phoneField = jQuery('form[name="checkout"] p#billing_phone_field'); - let phoneMarkup = '
' - + '' - + '' - + '' - + '' - + '
' - let inputBirthName = 'billing_birthdate'; - let originalBirth = saveOriginalField(inputBirthName, {}); - let birthId = 'billing_birthdate_field'; - let birthField = jQuery('form[name="checkout"] p#billing_birthdate_field'); - let birthMarkup = '' - + '' - + '' - + '' - + '' - + '
' - jQuery(function () { - jQuery('body') - .on('updated_checkout payment_method_selected', function () { - phoneField = maybeRequireField(phoneField, positionField, phoneMarkup, inputPhoneName, phoneId, originalPhone, gateway); - birthField = maybeRequireField(birthField, positionField, birthMarkup, inputBirthName, birthId, originalBirth, gateway); - }); - }); - } -)( - window -) - - - diff --git a/src/Assets/AssetsModule.php b/src/Assets/AssetsModule.php index 9b46d9503..5a97e4e73 100644 --- a/src/Assets/AssetsModule.php +++ b/src/Assets/AssetsModule.php @@ -283,13 +283,6 @@ protected function registerFrontendScripts(string $pluginUrl, string $pluginPath (string) filemtime($this->getPluginPath($pluginPath, '/public/js/mollieBillie.min.js')), true ); - wp_register_script( - 'mollie-in3-classic-handles', - $this->getPluginUrl($pluginUrl, '/public/js/mollieIn3.min.js'), - ['underscore', 'jquery'], - (string) filemtime($this->getPluginPath($pluginPath, '/public/js/mollieIn3.min.js')), - true - ); } public function registerBlockScripts(string $pluginUrl, string $pluginPath): void @@ -322,11 +315,6 @@ public function enqueueFrontendScripts($container) if ($isBillieEnabled && $isBillieEnabledAtMollie) { wp_enqueue_script('mollie-billie-classic-handles'); } - $isIn3Enabled = mollieWooCommerceIsGatewayEnabled('mollie_wc_gateway_in3_settings', 'enabled'); - $isIn3EnabledAtMollie = in_array('in3', $allMethodsEnabledAtMollie, true); - if ($isIn3Enabled && $isIn3EnabledAtMollie) { - wp_enqueue_script('mollie-in3-classic-handles'); - } $applePayGatewayEnabled = mollieWooCommerceIsGatewayEnabled('mollie_wc_gateway_applepay_settings', 'enabled'); $isAppleEnabledAtMollie = in_array('applepay', $allMethodsEnabledAtMollie, true); diff --git a/src/Gateway/GatewayModule.php b/src/Gateway/GatewayModule.php index 9d997e071..d73dfb51b 100644 --- a/src/Gateway/GatewayModule.php +++ b/src/Gateway/GatewayModule.php @@ -6,6 +6,7 @@ namespace Mollie\WooCommerce\Gateway; +use Automattic\WooCommerce\Admin\Overrides\Order; use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use Mollie\WooCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; @@ -58,6 +59,9 @@ class GatewayModule implements ServiceModule, ExecutableModule */ protected $pluginId; + const FIELD_IN3_BIRTHDATE = 'billing_birthdate'; + const GATEWAY_NAME_IN3 = "mollie_wc_gateway_in3"; + public function services(): array { return [ @@ -101,7 +105,7 @@ public function services(): array }, 'gateway.getKlarnaPaymentMethodsAfterFeatureFlag' => static function (ContainerInterface $container): array { $availablePaymentMethods = $container->get('gateway.listAllMethodsAvailable'); - $klarnaOneFlag = apply_filters('inpsyde.feature-flags.mollie-woocommerce.klarna_one_enabled', getenv('MOL_KLARNA_ENABLED') === '1'); + $klarnaOneFlag = apply_filters('inpsyde.feature-flags.mollie-woocommerce.klarna_one_enabled', true); if (!$klarnaOneFlag) { return array_filter($availablePaymentMethods, static function ($method) { return $method['id'] !== Constants::KLARNA; @@ -255,6 +259,11 @@ static function () { 11, 2 ); + add_action( + 'woocommerce_before_pay_action', + [$this, 'in3FieldsMandatoryPayForOrder'], + 11 + ); } // Set order to paid and processed when eventually completed without Mollie add_action('woocommerce_payment_complete', [$this, 'setOrderPaidByOtherGateway'], 10, 1); @@ -614,18 +623,44 @@ public function BillieFieldsMandatory($fields, $errors) { $gatewayName = "mollie_wc_gateway_billie"; $field = 'billing_company'; - $paymentMethodName = 'Billie'; - return $this->addPaymentMethodMandatoryFields($fields, $gatewayName, $field, $errors, $paymentMethodName); + $companyLabel = __('Company', 'mollie-payments-for-woocommerce'); + return $this->addPaymentMethodMandatoryFields($fields, $gatewayName, $field, $companyLabel, $errors); } public function in3FieldsMandatory($fields, $errors) { $gatewayName = "mollie_wc_gateway_in3"; $phoneField = 'billing_phone'; - $birthdateField = 'billing_birthdate'; - $paymentMethodName = 'in3'; - $fields = $this->addPaymentMethodMandatoryFields($fields, $gatewayName, $phoneField, $errors, $paymentMethodName); - return $this->addPaymentMethodMandatoryFields($fields, $gatewayName, $birthdateField, $errors, $paymentMethodName); + $birthdateField = self::FIELD_IN3_BIRTHDATE; + $phoneLabel = __('Phone', 'mollie-payments-for-woocommerce'); + $birthDateLabel = __('Birthdate', 'mollie-payments-for-woocommerce'); + $fields = $this->addPaymentMethodMandatoryFields($fields, $gatewayName, $phoneField, $phoneLabel, $errors); + return $this->addPaymentMethodMandatoryFields($fields, $gatewayName, $birthdateField, $birthDateLabel, $errors); + } + + /** + * @param Order $order + */ + public function in3FieldsMandatoryPayForOrder(Order $order) + { + $paymentMethod = filter_input(INPUT_POST, 'payment_method', FILTER_SANITIZE_SPECIAL_CHARS) ?? false; + + if ($paymentMethod !== self::GATEWAY_NAME_IN3) { + return; + } + + $birthdateValue = filter_input(INPUT_POST, self::FIELD_IN3_BIRTHDATE, FILTER_SANITIZE_SPECIAL_CHARS) ?? false; + $birthDateLabel = __('Birthdate', 'mollie-payments-for-woocommerce'); + + if (!$birthdateValue) { + wc_add_notice( + sprintf( + __('%s is a required field.', 'woocommerce'), + "$birthDateLabel" + ), + 'error' + ); + } } /** @@ -664,10 +699,9 @@ public function buildPaymentMethod( * @param string $gatewayName * @param string $field * @param $errors - * @param string $paymentMethodName * @return mixed */ - public function addPaymentMethodMandatoryFields($fields, string $gatewayName, string $field, $errors, string $paymentMethodName) + public function addPaymentMethodMandatoryFields($fields, string $gatewayName, string $field, string $fieldLabel, $errors) { if ($fields['payment_method'] !== $gatewayName) { return $fields; @@ -680,29 +714,13 @@ public function addPaymentMethodMandatoryFields($fields, string $gatewayName, st $errors->add( 'validation', sprintf( - __( - 'Error processing %1$s payment, the %2$s field is required.', - 'mollie-payments-for-woocommerce' - ), - $paymentMethodName, - $field + __('%s is a required field.', 'woocommerce'), + "$fieldLabel" ) ); } } - if ($fields[$field] === '') { - $errors->add( - 'validation', - sprintf( - __( - 'Please enter your %1$s, this is required for %2$s payments', - 'mollie-payments-for-woocommerce' - ), - $field, - $paymentMethodName - ) - ); - } + return $fields; } } diff --git a/src/MerchantCapture/Capture/Action/VoidPayment.php b/src/MerchantCapture/Capture/Action/VoidPayment.php index 0ef1d152a..1b19a4649 100644 --- a/src/MerchantCapture/Capture/Action/VoidPayment.php +++ b/src/MerchantCapture/Capture/Action/VoidPayment.php @@ -25,7 +25,7 @@ public function __invoke() $this->logger->error($exception->getMessage()); $this->order->add_order_note( __( - 'Payment Void Failed. We encountered an issue while canceling the pre-authorized payment.', + 'Payment cancelation 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 index 70c890c25..81a782e97 100644 --- a/src/MerchantCapture/Capture/Type/ManualCapture.php +++ b/src/MerchantCapture/Capture/Type/ManualCapture.php @@ -37,7 +37,10 @@ public function enableOrderCaptureButton(array $actions, \WC_Order $order): arra public function sendManualCaptureMode(array $paymentData): array { - if ($this->container->get('merchant.manual_capture.enabled')) { + if ( + $this->container->get('merchant.manual_capture.enabled') && + $this->container->get('merchant.manual_capture.cart_can_be_captured') + ) { $paymentData['captureMode'] = 'manual'; } return $paymentData; diff --git a/src/MerchantCapture/Capture/Type/StateChangeCapture.php b/src/MerchantCapture/Capture/Type/StateChangeCapture.php index 9e9ec3e84..1bafca2dc 100644 --- a/src/MerchantCapture/Capture/Type/StateChangeCapture.php +++ b/src/MerchantCapture/Capture/Type/StateChangeCapture.php @@ -19,7 +19,16 @@ class StateChangeCapture public function __construct(ContainerInterface $container) { $this->container = $container; + $pluginId = $container->get('shared.plugin_id'); + add_action('woocommerce_order_status_changed', [$this, "orderStatusChange"], 10, 3); + + /** When the webhook process is activated we don't need automatic status change. Status change is handled + * by the webhook logic. + */ + add_action($pluginId . '_before_webhook_payment_action', function () { + remove_action('woocommerce_order_status_changed', [$this, "orderStatusChange"]); + }); } public function orderStatusChange(int $orderId, string $oldStatus, string $newStatus) diff --git a/src/MerchantCapture/MerchantCaptureModule.php b/src/MerchantCapture/MerchantCaptureModule.php index 15b3ca73a..a3177f880 100644 --- a/src/MerchantCapture/MerchantCaptureModule.php +++ b/src/MerchantCapture/MerchantCaptureModule.php @@ -88,6 +88,25 @@ public function services(): array 'merchant.manual_capture.on_status_change_enabled' => static function () { return get_option('mollie-payments-for-woocommerce_capture_or_void', false); }, + 'merchant.manual_capture.cart_can_be_captured' => static function (): bool { + if (!class_exists(\WC_Product_Subscription::class)) { + return true; + } + $cart = WC()->cart; + if (!is_a($cart, \WC_Cart::class)) { + return false; + } + $cartItems = $cart->get_cart_contents(); + + foreach ($cartItems as $cartItemData) { + $cartItem = $cartItemData['data']; + + if (is_a($cartItem, \WC_Product_Subscription::class)) { + return false; + } + } + return true; + }, CapturePayment::class => static function ($container) { return static function (int $orderId) use ($container) { /** @var Api $api */ @@ -128,14 +147,14 @@ public function run(ContainerInterface $container): bool add_action('init', static function () use ($container) { $pluginId = $container->get('shared.plugin_id'); $captureSettings = new MollieCaptureSettings(); - - if (!apply_filters('mollie_wc_gateway_enable_merchant_capture_module', false)) { + if (!apply_filters('mollie_wc_gateway_enable_merchant_capture_module', true)) { return; } add_action( $pluginId . '_after_webhook_action', static function (Payment $payment, WC_Order $order) use ($container) { + if ($payment->isAuthorized()) { if (!$payment->getAmountCaptured() == 0.0) { return; @@ -145,13 +164,30 @@ static function (Payment $payment, WC_Order $order) use ($container) { self::ORDER_PAYMENT_STATUS_META_KEY, ManualCaptureStatus::STATUS_AUTHORIZED ); + $order->set_transaction_id($payment->id); $order->save(); - } elseif ($payment->isPaid() && ($container->get('merchant.manual_capture.is_waiting'))($order)) { + } elseif ( + $payment->isPaid() && ( + ($container->get('merchant.manual_capture.is_waiting'))($order) || + ($container->get('merchant.manual_capture.is_authorized'))($order) + ) + ) { $order->update_meta_data( self::ORDER_PAYMENT_STATUS_META_KEY, ManualCaptureStatus::STATUS_CAPTURED ); $order->save(); + } elseif ( + $payment->isCanceled() && ( + ($container->get('merchant.manual_capture.is_waiting'))($order) || + ($container->get('merchant.manual_capture.is_authorized'))($order) + ) + ) { + $order->update_meta_data( + self::ORDER_PAYMENT_STATUS_META_KEY, + ManualCaptureStatus::STATUS_VOIDED + ); + $order->save(); } }, 10, @@ -199,7 +235,7 @@ static function ($disableShipAndCapture, WC_Order $order) use ($container) { if ($disableShipAndCapture) { return true; } - return $container->get('merchant.manual_capture.is_waiting')($order); + return $container->get('merchant.manual_capture.is_waiting')($order) || $container->get('merchant.manual_capture.is_authorized')($order); }, 10, 2 diff --git a/src/MerchantCapture/MollieCaptureSettings.php b/src/MerchantCapture/MollieCaptureSettings.php index f263c1b4e..cf5b9a9a3 100644 --- a/src/MerchantCapture/MollieCaptureSettings.php +++ b/src/MerchantCapture/MollieCaptureSettings.php @@ -26,7 +26,7 @@ public function settings(array $advancedSettings, string $pluginName): array 'default' => 'immediate_capture', 'desc' => sprintf( __( - 'Authorized payment can be captured or voided by changing the order status instead of doing it manually.', + 'Authorized payment can be captured or canceled by changing the order status instead of doing it manually.', 'mollie-payments-for-woocommerce' ) ), @@ -34,13 +34,13 @@ public function settings(array $advancedSettings, string $pluginName): array [ 'id' => $pluginName . '_capture_or_void', 'title' => __( - 'Capture or void on status change', + 'Capture or cancel 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.', + 'Capture authorized payments automatically when setting the order status to Processing or Completed. Cancel the payment by setting the order status Canceled.', 'mollie-payments-for-woocommerce' ), ], diff --git a/src/MerchantCapture/OrderListPaymentColumn.php b/src/MerchantCapture/OrderListPaymentColumn.php index 6447336b1..d74a9173d 100644 --- a/src/MerchantCapture/OrderListPaymentColumn.php +++ b/src/MerchantCapture/OrderListPaymentColumn.php @@ -4,6 +4,7 @@ namespace Mollie\WooCommerce\MerchantCapture; +use Automattic\WooCommerce\Admin\Overrides\Order; use Mollie\WooCommerce\MerchantCapture\UI\StatusRenderer; class OrderListPaymentColumn @@ -12,6 +13,12 @@ public function __construct() { add_filter('manage_edit-shop_order_columns', [$this, 'renderColumn']); add_action('manage_shop_order_posts_custom_column', [$this, 'renderColumnValue'], 10, 2); + + # HPOS hooks + add_filter('woocommerce_shop_order_list_table_columns', [$this, 'renderColumn']); + add_action('woocommerce_shop_order_list_table_custom_column', function (string $column, Order $order) { + $this->renderColumnValue($column, $order->get_id()); + }, 10, 2); } public function renderColumn(array $columns): array diff --git a/src/MerchantCapture/UI/StatusRenderer.php b/src/MerchantCapture/UI/StatusRenderer.php index b23f9a637..5da2f8563 100644 --- a/src/MerchantCapture/UI/StatusRenderer.php +++ b/src/MerchantCapture/UI/StatusRenderer.php @@ -18,7 +18,7 @@ public function __invoke(string $molliePaymentStatus) ); } elseif ($molliePaymentStatus === ManualCaptureStatus::STATUS_VOIDED) { (new StatusButton())( - __('Payment voided', 'mollie-payments-for-woocommerce'), + __('Payment canceled', 'mollie-payments-for-woocommerce'), SharedDataDictionary::STATUS_CANCELLED ); } elseif ($molliePaymentStatus === ManualCaptureStatus::STATUS_CAPTURED) { diff --git a/src/Payment/MollieOrder.php b/src/Payment/MollieOrder.php index cb61dbc00..4a3e0feb2 100644 --- a/src/Payment/MollieOrder.php +++ b/src/Payment/MollieOrder.php @@ -951,13 +951,24 @@ protected function createBillingAddress($order) self::MAXIMAL_LENGHT_REGION ); $billingAddress->organizationName = $this->billingCompanyField($order); - $phone = !empty($order->get_billing_phone()) ? $order->get_billing_phone() : $order->get_shipping_phone(); + $phone = $this->getPhoneNumber($order); $billingAddress->phone = (ctype_space($phone)) ? null : $this->getFormatedPhoneNumber($phone); return $billingAddress; } + protected function getPhoneNumber($order) + { + + $phone = !empty($order->get_billing_phone()) ? $order->get_billing_phone() : $order->get_shipping_phone(); + if (empty($phone)) { + //phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $phone = wc_clean(wp_unslash($_POST['billing_phone'] ?? '')); + } + return $phone; + } + /** * @param $order * @return stdClass diff --git a/src/Payment/MollieOrderService.php b/src/Payment/MollieOrderService.php index 277d7a4f0..ede0c6d05 100644 --- a/src/Payment/MollieOrderService.php +++ b/src/Payment/MollieOrderService.php @@ -150,7 +150,12 @@ public function onWebhookAction() $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')) { + if ( + $order->get_status() === 'processing' + && method_exists($payment, 'isCompleted') + && $payment->isCompleted() + && method_exists($payment_object, 'onWebhookCompleted') + ) { $payment_object->onWebhookCompleted($order, $payment, $payment_method_title); } return; @@ -162,6 +167,7 @@ public function onWebhookAction() } if (method_exists($payment_object, $method_name)) { + do_action($this->pluginId . '_before_webhook_payment_action', $payment, $order); $payment_object->{$method_name}($order, $payment, $payment_method_title); } else { $order->add_order_note(sprintf( diff --git a/src/PaymentMethods/Blik.php b/src/PaymentMethods/Blik.php new file mode 100644 index 000000000..83e05ef2c --- /dev/null +++ b/src/PaymentMethods/Blik.php @@ -0,0 +1,32 @@ + 'blik', + 'defaultTitle' => __('BLIK', 'mollie-payments-for-woocommerce'), + 'settingsDescription' => '', + 'defaultDescription' => '', + 'paymentFields' => false, + 'instructions' => false, + 'supports' => [ + 'products', + 'refunds', + ], + 'filtersOnBuild' => false, + 'confirmationDelayed' => false, + 'SEPA' => false, + ]; + } + + public function getFormFields($generalFormFields): array + { + return $generalFormFields; + } +} diff --git a/src/PaymentMethods/In3.php b/src/PaymentMethods/In3.php index 1253ee9ec..be4b7ec3f 100644 --- a/src/PaymentMethods/In3.php +++ b/src/PaymentMethods/In3.php @@ -13,7 +13,7 @@ public function getConfig(): array 'defaultTitle' => __('in3', 'mollie-payments-for-woocommerce'), 'settingsDescription' => '', 'defaultDescription' => __('Pay in 3 instalments, 0% interest', 'mollie-payments-for-woocommerce'), - 'paymentFields' => false, + 'paymentFields' => true, 'instructions' => false, 'supports' => [ 'products', diff --git a/src/PaymentMethods/PaymentFieldsStrategies/In3FieldsStrategy.php b/src/PaymentMethods/PaymentFieldsStrategies/In3FieldsStrategy.php new file mode 100644 index 000000000..47a0d762d --- /dev/null +++ b/src/PaymentMethods/PaymentFieldsStrategies/In3FieldsStrategy.php @@ -0,0 +1,86 @@ +getOrderIdOnPayForOrderPage(); + $showPhoneField = empty($order->get_billing_phone()); + $showBirthdateField = true; + } + + if (is_checkout() && !is_checkout_pay_page()) { + $checkoutFields = WC()->checkout()->get_checkout_fields(); + + if (!isset($checkoutFields["billing"][self::FIELD_PHONE])) { + $showPhoneField = true; + } + + if (!isset($checkoutFields["billing"][self::FIELD_BIRTHDATE])) { + $showBirthdateField = true; + } + } + + if ($showPhoneField) { + $this->phoneNumber(); + } + + if ($showBirthdateField) { + $this->dateOfBirth(); + } + } + + protected function getOrderIdOnPayForOrderPage() + { + global $wp; + $orderId = absint($wp->query_vars['order-pay']); + return wc_get_order($orderId); + } + + protected function dateOfBirth() + { + ?> ++ + + +
+ ++ + + + +
+ 'twint', + 'defaultTitle' => __('Twint', 'mollie-payments-for-woocommerce'), + 'settingsDescription' => '', + 'defaultDescription' => '', + 'paymentFields' => false, + 'instructions' => false, + 'supports' => [ + 'products', + 'refunds', + ], + 'filtersOnBuild' => false, + 'confirmationDelayed' => false, + 'SEPA' => false, + ]; + } + + public function getFormFields($generalFormFields): array + { + return $generalFormFields; + } +} diff --git a/src/Shared/Data.php b/src/Shared/Data.php index 3cb509e5e..387aa711a 100644 --- a/src/Shared/Data.php +++ b/src/Shared/Data.php @@ -87,12 +87,12 @@ public function isSubscriptionPluginActive(): bool public function isValidApiKeyProvided() { $settings = $this->settingsHelper; - $api_key = $settings->getApiKey(); + $apiKey = $settings->getApiKey(); - return !empty($api_key) + return !empty($apiKey) && preg_match( '/^(live|test)_\w{30,}$/', - $api_key + $apiKey ); } @@ -172,41 +172,41 @@ public function getTransientId($transient) /** * Get Mollie payment from cache or load from Mollie - * Skip cache by setting $use_cache to false + * Skip cache by setting $useCache to false * - * @param string $payment_id + * @param string $paymentId * @param string $apiKey (default: false) - * @param bool $use_cache (default: true) + * @param bool $useCache (default: true) * * @return \Mollie\Api\Resources\Payment|null */ - public function getPayment($payment_id, $apiKey, $use_cache = true): ?\Mollie\Api\Resources\Payment + public function getPayment($paymentId, $apiKey, $useCache = true): ?\Mollie\Api\Resources\Payment { try { - return $this->api_helper->getApiClient($apiKey)->payments->get($payment_id); + return $this->api_helper->getApiClient($apiKey)->payments->get($paymentId); } catch (\Mollie\Api\Exceptions\ApiException $apiException) { - $this->logger->debug(__FUNCTION__ . sprintf(': Could not load payment %s (', $payment_id) . "): " . $apiException->getMessage() . ' (' . get_class($apiException) . ')'); + $this->logger->debug(__FUNCTION__ . sprintf(': Could not load payment %s (', $paymentId) . "): " . $apiException->getMessage() . ' (' . get_class($apiException) . ')'); } return null; } /** - * @param bool $test_mode - * @param bool $use_cache + * @param bool $testMode + * @param bool $useCache * * @return array|mixed|\Mollie\Api\Resources\Method[]|\Mollie\Api\Resources\MethodCollection */ - public function getAllPaymentMethods($apiKey, $test_mode = false, $use_cache = true) + public function getAllPaymentMethods($apiKey, $testMode = false, $useCache = true) { - $result = $this->getRegularPaymentMethods($apiKey, $test_mode, $use_cache); + $result = $this->getRegularPaymentMethods($apiKey, $testMode, $useCache); if (!is_array($result)) { $result = unserialize($result); } $isSubscriptionPluginActive = $this->isSubscriptionPluginActive(); if ($isSubscriptionPluginActive) { - $result = $this->addRecurringPaymentMethods($apiKey, $test_mode, $use_cache, $result); + $result = $this->addRecurringPaymentMethods($apiKey, $testMode, $useCache, $result); } return $result; @@ -238,13 +238,13 @@ public function wooCommerceFiltersForCheckout(): array return $filters; } /** - * @param $order_total + * @param $orderTotal * @param $currency */ - protected function getAmountValue($order_total, $currency): string + protected function getAmountValue($orderTotal, $currency): string { return $this->formatCurrencyValue( - $order_total, + $orderTotal, $currency ); } @@ -301,49 +301,58 @@ public function getFilters( } /** - * @param bool $test_mode - * @param bool $use_cache + * @param bool $testMode + * @param bool $useCache * * @return array|mixed|\Mollie\Api\Resources\Method[]|\Mollie\Api\Resources\MethodCollection */ - public function getRegularPaymentMethods($apiKey, $test_mode = false, $use_cache = true) + public function getRegularPaymentMethods($apiKey, $testMode = false, $useCache = true) { // Already initialized - if ($use_cache && ! empty(self::$regular_api_methods)) { + if ($useCache && ! empty(self::$regular_api_methods)) { return self::$regular_api_methods; } - - self::$regular_api_methods = $this->getApiPaymentMethods($use_cache); + $testMode = $this->isTestModeEnabled(); + $methods = $this->getAllAvailablePaymentMethods($useCache); + // We cannot access allActive for all methods so we filter them out here + $filtered_methods = array_filter($methods, function ($method) use ($testMode) { + if ($testMode === "live") { + return $method['status'] === "activated"; + } else { + return in_array($method['status'], ["activated", "pending-review"]); + } + }); + self::$regular_api_methods = $filtered_methods; return self::$regular_api_methods; } - public function getRecurringPaymentMethods($apiKey, $test_mode = false, $use_cache = true) + public function getRecurringPaymentMethods($apiKey, $testMode = false, $useCache = true) { // Already initialized - if ($use_cache && ! empty(self::$recurring_api_methods)) { + if ($useCache && ! empty(self::$recurring_api_methods)) { return self::$recurring_api_methods; } - self::$recurring_api_methods = $this->getApiPaymentMethods($use_cache, [ 'sequenceType' => 'recurring' ]); + self::$recurring_api_methods = $this->getApiPaymentMethods($useCache, [ 'sequenceType' => 'recurring' ]); return self::$recurring_api_methods; } - public function getApiPaymentMethods($use_cache = true, $filters = []) + public function getApiPaymentMethods($useCache = true, $filters = []) { - $test_mode = $this->isTestModeEnabled(); + $testMode = $this->isTestModeEnabled(); $apiKey = $this->settingsHelper->getApiKey(); $methods = false; $filters_key = $filters; - $filters_key['mode'] = ( $test_mode ? 'test' : 'live' ); + $filters_key['mode'] = ( $testMode ? 'test' : 'live' ); $filters_key['api'] = 'methods'; $transient_id = $this->getTransientId(md5(http_build_query($filters_key))); try { - if ($use_cache) { + if ($useCache) { // When no cache exists $methods will be `false` $methods = get_transient($transient_id); } else { @@ -371,7 +380,7 @@ public function getApiPaymentMethods($use_cache = true, $filters = []) $methods = $methods_cleaned; // Set new transients (as cache) - if ($use_cache) { + if ($useCache) { set_transient($transient_id, $methods, HOUR_IN_SECONDS); } } @@ -382,27 +391,27 @@ public function getApiPaymentMethods($use_cache = true, $filters = []) * Cache the result for a short period * to prevent hammering the API with requests that are likely to fail again */ - if ($use_cache) { + if ($useCache) { set_transient($transient_id, [], 60 * 5); } - $this->logger->debug(__FUNCTION__ . ": Could not load Mollie methods (" . ( $test_mode ? 'test' : 'live' ) . "): " . $e->getMessage() . ' (' . get_class($e) . ')'); + $this->logger->debug(__FUNCTION__ . ": Could not load Mollie methods (" . ( $testMode ? 'test' : 'live' ) . "): " . $e->getMessage() . ' (' . get_class($e) . ')'); return []; } } /** - * @param bool $test_mode + * @param bool $testMode * @param $method * * @return mixed|\Mollie\Api\Resources\Method|null */ public function getPaymentMethod($method) { - $test_mode = $this->isTestModeEnabled(); + $testMode = $this->isTestModeEnabled(); $apiKey = $this->settingsHelper->getApiKey(); - $payment_methods = $this->getAllPaymentMethods($apiKey, $test_mode); + $payment_methods = $this->getAllPaymentMethods($apiKey, $testMode); foreach ($payment_methods as $payment_method) { if ($payment_method['id'] === $method) { @@ -416,15 +425,15 @@ public function getPaymentMethod($method) /** * Get issuers for payment method (e.g. for iDEAL, KBC/CBC payment button, gift cards) * - * @param bool $test_mode (default: false) + * @param bool $testMode (default: false) * @param string|null $methodId * * @return array */ - public function getMethodIssuers($apiKey, $test_mode = false, $methodId = null) + public function getMethodIssuers($apiKey, $testMode = false, $methodId = null) { try { - $transient_id = $this->getTransientId($methodId . '_issuers_' . ($test_mode ? 'test' : 'live')); + $transient_id = $this->getTransientId($methodId . '_issuers_' . ($testMode ? 'test' : 'live')); // When no cache exists $issuers will be `false` $issuers = get_transient($transient_id); @@ -438,7 +447,7 @@ public function getMethodIssuers($apiKey, $test_mode = false, $methodId = null) set_transient($transient_id, $issuers, HOUR_IN_SECONDS); return $issuers; } catch (\Mollie\Api\Exceptions\ApiException $e) { - $this->logger->debug(__FUNCTION__ . ": Could not load " . $methodId . " issuers (" . ( $test_mode ? 'test' : 'live' ) . "): " . $e->getMessage() . ' (' . get_class($e) . ')'); + $this->logger->debug(__FUNCTION__ . ": Could not load " . $methodId . " issuers (" . ( $testMode ? 'test' : 'live' ) . "): " . $e->getMessage() . ' (' . get_class($e) . ')'); } return []; @@ -486,21 +495,21 @@ public function getCachedMethodById(string $methodId) } /** - * @param int $user_id - * @param string|null $customer_id + * @param int $userId + * @param string|null $customerId * * @return $this */ - public function setUserMollieCustomerId($user_id, $customer_id) + public function setUserMollieCustomerId($userId, $customerId) { - if (! empty($customer_id)) { + if (! empty($customerId)) { try { - $customer = new WC_Customer($user_id); - $customer->update_meta_data('mollie_customer_id', $customer_id); + $customer = new WC_Customer($userId); + $customer->update_meta_data('mollie_customer_id', $customerId); $customer->save(); - $this->logger->debug(__FUNCTION__ . ": Stored Mollie customer ID " . $customer_id . " with user " . $user_id); + $this->logger->debug(__FUNCTION__ . ": Stored Mollie customer ID " . $customerId . " with user " . $userId); } catch (Exception $exception) { - $this->logger->debug(__FUNCTION__ . ": Couldn't load (and save) WooCommerce customer based on user ID " . $user_id); + $this->logger->debug(__FUNCTION__ . ": Couldn't load (and save) WooCommerce customer based on user ID " . $userId); } } @@ -509,14 +518,14 @@ public function setUserMollieCustomerId($user_id, $customer_id) /** * @param $orderId - * @param $customer_id + * @param $customerId * @return $this */ - public function setUserMollieCustomerIdAtSubscription($orderId, $customer_id) + public function setUserMollieCustomerIdAtSubscription($orderId, $customerId) { - if (!empty($customer_id)) { + if (!empty($customerId)) { $order = wc_get_order($orderId); - $order->update_meta_data('_mollie_customer_id', $customer_id); + $order->update_meta_data('_mollie_customer_id', $customerId); $order->save(); } @@ -524,53 +533,53 @@ public function setUserMollieCustomerIdAtSubscription($orderId, $customer_id) } /** - * @param int $user_id - * @param bool $test_mode + * @param int $userId + * @param bool $testMode * @return null|string */ - public function getUserMollieCustomerId($user_id, $apiKey) + public function getUserMollieCustomerId($userId, $apiKey) { // Guest users can't buy subscriptions and don't need a Mollie customer ID // https://github.com/mollie/WooCommerce/issues/132 - if (empty($user_id)) { + if (empty($userId)) { return null; } $isTestModeEnabled = $this->isTestModeEnabled(); - $customer = new WC_Customer($user_id); - $customer_id = $customer->get_meta('mollie_customer_id'); + $customer = new WC_Customer($userId); + $customerId = $customer->get_meta('mollie_customer_id'); // If there is no Mollie Customer ID set, check the most recent active subscription - if (empty($customer_id)) { + if (empty($customerId)) { $customer_latest_subscription = wc_get_orders([ 'limit' => 1, - 'customer' => $user_id, + 'customer' => $userId, 'type' => 'shop_subscription', 'status' => 'wc-active', ]); if (! empty($customer_latest_subscription)) { - $customer_id = get_post_meta($customer_latest_subscription[0]->get_id(), '_mollie_customer_id', $single = true); + $customerId = get_post_meta($customer_latest_subscription[0]->get_id(), '_mollie_customer_id', $single = true); // Store this customer ID as user meta too - $this->setUserMollieCustomerId($user_id, $customer_id); + $this->setUserMollieCustomerId($userId, $customerId); } } // If there is a Mollie Customer ID set, check that customer ID is valid for this API key - if (! empty($customer_id)) { + if (! empty($customerId)) { try { - $this->api_helper->getApiClient($apiKey)->customers->get($customer_id); + $this->api_helper->getApiClient($apiKey)->customers->get($customerId); } catch (\Mollie\Api\Exceptions\ApiException $e) { - $this->logger->debug(__FUNCTION__ . sprintf(': Mollie Customer ID (%s) not valid for user %s on this API key, try to create a new one (', $customer_id, $user_id) . ( $isTestModeEnabled ? 'test' : 'live' ) . ")."); - $customer_id = ''; + $this->logger->debug(__FUNCTION__ . sprintf(': Mollie Customer ID (%s) not valid for user %s on this API key, try to create a new one (', $customerId, $userId) . ( $isTestModeEnabled ? 'test' : 'live' ) . ")."); + $customerId = ''; } } // If there is no Mollie Customer ID set, try to create a new Mollie Customer - if (empty($customer_id)) { + if (empty($customerId)) { try { - $userdata = get_userdata($user_id); + $userdata = get_userdata($userId); // Get the best name for use as Mollie Customer name $user_full_name = $userdata->first_name . ' ' . $userdata->last_name; @@ -583,35 +592,35 @@ public function getUserMollieCustomerId($user_id, $apiKey) $customer = $this->api_helper->getApiClient($apiKey)->customers->create([ 'name' => trim($user_full_name), 'email' => trim($userdata->user_email), - 'metadata' => [ 'user_id' => $user_id ], + 'metadata' => [ 'user_id' => $userId ], ]); - $this->setUserMollieCustomerId($user_id, $customer->id); + $this->setUserMollieCustomerId($userId, $customer->id); - $customer_id = $customer->id; + $customerId = $customer->id; - $this->logger->debug(__FUNCTION__ . sprintf(': Created a Mollie Customer (%s) for WordPress user with ID %s (', $customer_id, $user_id) . ( $isTestModeEnabled ? 'test' : 'live' ) . ")."); + $this->logger->debug(__FUNCTION__ . sprintf(': Created a Mollie Customer (%s) for WordPress user with ID %s (', $customerId, $userId) . ( $isTestModeEnabled ? 'test' : 'live' ) . ")."); - return $customer_id; + return $customerId; } catch (\Mollie\Api\Exceptions\ApiException $e) { - $this->logger->debug(__FUNCTION__ . sprintf(': Could not create Mollie Customer for WordPress user with ID %s (', $user_id) . ( $isTestModeEnabled ? 'test' : 'live' ) . "): " . $e->getMessage() . ' (' . get_class($e) . ')'); + $this->logger->debug(__FUNCTION__ . sprintf(': Could not create Mollie Customer for WordPress user with ID %s (', $userId) . ( $isTestModeEnabled ? 'test' : 'live' ) . "): " . $e->getMessage() . ' (' . get_class($e) . ')'); } } else { - $this->logger->debug(__FUNCTION__ . sprintf(': Mollie Customer ID (%s) found and valid for user %s on this API key. (', $customer_id, $user_id) . ( $isTestModeEnabled ? 'test' : 'live' ) . ")."); + $this->logger->debug(__FUNCTION__ . sprintf(': Mollie Customer ID (%s) found and valid for user %s on this API key. (', $customerId, $userId) . ( $isTestModeEnabled ? 'test' : 'live' ) . ")."); } - return $customer_id; + return $customerId; } /** * Get active Mollie payment mode for order * - * @param int $order_id + * @param int $orderId * @return string test or live */ - public function getActiveMolliePaymentMode($order_id) + public function getActiveMolliePaymentMode($orderId) { - $order = wc_get_order($order_id); + $order = wc_get_order($orderId); return $order->get_meta('_mollie_payment_mode', true); } @@ -679,30 +688,28 @@ public function isSubscription($orderId) return apply_filters($this->pluginId . '_is_subscription_payment', $isSubscription, $orderId); } - public function getAllAvailablePaymentMethods($use_cache = true) + public function getAllAvailablePaymentMethods($useCache = true) { $apiKey = $this->settingsHelper->getApiKey(); $methods = false; $locale = $this->getPaymentLocale(); $filters_key = []; $filters_key['locale'] = $locale; + $filters_key['include'] = 'issuers'; $transient_id = $this->getTransientId(md5(http_build_query($filters_key))); - try { - if ($use_cache) { + if ($useCache) { // When no cache exists $methods will be `false` $methods = get_transient($transient_id); } else { delete_transient($transient_id); } - // No cache exists, call the API and cache the result if ($methods === false) { if (!$apiKey) { return []; } $methods = $this->api_helper->getApiClient($apiKey)->methods->allAvailable($filters_key); - $methods_cleaned = []; foreach ($methods as $method) { @@ -714,7 +721,7 @@ public function getAllAvailablePaymentMethods($use_cache = true) $methods = $methods_cleaned; // Set new transients (as cache) - if ($use_cache) { + if ($useCache) { set_transient($transient_id, $methods, HOUR_IN_SECONDS); } } @@ -725,7 +732,7 @@ public function getAllAvailablePaymentMethods($use_cache = true) * Cache the result for a short period * to prevent hammering the API with requests that are likely to fail again */ - if ($use_cache) { + if ($useCache) { set_transient($transient_id, [], 60 * 5); } $this->logger->debug(__FUNCTION__ . ": Could not load Mollie all available methods"); @@ -736,14 +743,14 @@ public function getAllAvailablePaymentMethods($use_cache = true) /** * @param $apiKey - * @param bool $test_mode - * @param bool $use_cache + * @param bool $testMode + * @param bool $useCache * @param $result * @return mixed */ - protected function addRecurringPaymentMethods($apiKey, bool $test_mode, bool $use_cache, $result) + protected function addRecurringPaymentMethods($apiKey, bool $testMode, bool $useCache, $result) { - $recurringPaymentMethods = $this->getRecurringPaymentMethods($apiKey, $test_mode, $use_cache); + $recurringPaymentMethods = $this->getRecurringPaymentMethods($apiKey, $testMode, $useCache); if (!is_array($recurringPaymentMethods)) { $recurringPaymentMethods = unserialize($recurringPaymentMethods); } diff --git a/src/Shared/SharedDataDictionary.php b/src/Shared/SharedDataDictionary.php index 9408c7bad..eb7224391 100644 --- a/src/Shared/SharedDataDictionary.php +++ b/src/Shared/SharedDataDictionary.php @@ -31,6 +31,8 @@ class SharedDataDictionary 'Mollie_WC_Gateway_Paysafecard', 'Mollie_WC_Gateway_Voucher', 'Mollie_WC_Gateway_Directdebit', + 'Mollie_WC_Gateway_Blik', + 'Mollie_WC_Gateway_Twint', ]; public const MOLLIE_OPTIONS_NAMES = [ diff --git a/tests/php/Functional/Payment/PaymentServiceTest.php b/tests/php/Functional/Payment/PaymentServiceTest.php index 3ef13e935..4aef1f340 100644 --- a/tests/php/Functional/Payment/PaymentServiceTest.php +++ b/tests/php/Functional/Payment/PaymentServiceTest.php @@ -112,7 +112,7 @@ public function processPayment_Order_success(){ expect('get_option') ->with('mollie-payments-for-woocommerce_api_switch') ->andReturn(false); - expect('get_transient')->andReturn(['ideal'=>['id'=>'ideal']]); + expect('get_transient')->andReturn(['ideal'=>['id'=>'ideal', 'status'=>'activated']]); $wcOrder->expects($this->any()) ->method('get_billing_company') ->willReturn(''); diff --git a/webpack.config.js b/webpack.config.js index dbd911a67..81a5f614e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -31,7 +31,6 @@ function configJavaScript ({ basePath }) .addEntry('paypalButtonBlockComponent.min', './resources/js/paypalButtonBlockComponent.js') .addEntry('applepayButtonBlockComponent.min', './resources/js/applepayButtonBlockComponent.js') .addEntry('mollieBillie.min', './resources/js/mollieBillie.js') - .addEntry('mollieIn3.min', './resources/js/mollieIn3.js') .enableSourceMaps(!Encore.isProduction()) return extractEncoreConfig('javascript-configuration')