From 3c710995b2a89ac05a461f1d4c8e7a156308b063 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Tue, 17 Dec 2024 17:59:00 +0100 Subject: [PATCH] Add missing decorators --- src/Payment/MollieObject.php | 444 ------------------ src/Payment/MolliePayment.php | 17 - .../AddCustomRequestFieldsDecorator.php | 39 ++ .../Request/Decorators/AddressDecorator.php | 246 ++++++++++ .../Decorators/ApplePaytokenDecorator.php | 13 +- .../Request/Decorators/CardTokenDecorator.php | 6 +- .../Decorators/OrderLinesDecorator.php | 28 ++ .../PaymentDescriptionDecorator.php | 112 +++++ .../Decorators/SelectedIssuerDecorator.php | 37 ++ .../Decorators/StoreCustomerDecorator.php | 11 +- .../Request/Decorators/UrlDecorator.php | 158 +++++++ .../Strategies/OrderRequestStrategy.php | 43 +- .../Strategies/PaymentRequestStrategy.php | 35 +- src/Payment/inc/services.php | 46 +- src/PaymentMethods/Alma.php | 3 +- 15 files changed, 693 insertions(+), 545 deletions(-) create mode 100644 src/Payment/Request/Decorators/AddCustomRequestFieldsDecorator.php create mode 100644 src/Payment/Request/Decorators/AddressDecorator.php create mode 100644 src/Payment/Request/Decorators/OrderLinesDecorator.php create mode 100644 src/Payment/Request/Decorators/PaymentDescriptionDecorator.php create mode 100644 src/Payment/Request/Decorators/SelectedIssuerDecorator.php create mode 100644 src/Payment/Request/Decorators/UrlDecorator.php diff --git a/src/Payment/MollieObject.php b/src/Payment/MollieObject.php index 6a507b09..cfee6569 100644 --- a/src/Payment/MollieObject.php +++ b/src/Payment/MollieObject.php @@ -161,16 +161,6 @@ public function getPaymentObjectOrder($payment_id, $test_mode = false, $use_cach protected function getPaymentRequestData($order, $customerId, $voucherDefaultCategory = Voucher::NO_CATEGORY) { } - /** - * @return string|NULL - */ - public function getSelectedIssuer($gatewayId): ?string - { - $issuer_id = $this->pluginId . '_issuer_' . $gatewayId; - //phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - $postedIssuer = wc_clean(wp_unslash($_POST[$issuer_id] ?? '')); - return !empty($postedIssuer) ? $postedIssuer : null; - } /** * @param \WC_Order $order @@ -821,438 +811,4 @@ protected function addPaypalTransactionIdToOrder( $order->save(); } } - /** - * Get the url to return to on Mollie return - * saves the return redirect and failed redirect, so we save the page language in case there is one set - * For example 'http://mollie-wc.docker.myhost/wc-api/mollie_return/?order_id=89&key=wc_order_eFZyH8jki6fge' - * - * @param WC_Order $order The order processed - * - * @return string The url with order id and key as params - */ - public function getReturnUrl($order, $returnUrl) - { - $returnUrl = untrailingslashit($returnUrl); - $returnUrl = $this->asciiDomainName($returnUrl); - $orderId = $order->get_id(); - $orderKey = $order->get_order_key(); - - $onMollieReturn = 'onMollieReturn'; - $returnUrl = $this->appendOrderArgumentsToUrl( - $orderId, - $orderKey, - $returnUrl, - $onMollieReturn - ); - $returnUrl = untrailingslashit($returnUrl); - $this->logger->debug(" Order {$orderId} returnUrl: {$returnUrl}", [true]); - - return apply_filters($this->pluginId . '_return_url', $returnUrl, $order); - } - /** - * Get the webhook url - * For example 'http://mollie-wc.docker.myhost/wc-api/mollie_return/mollie_wc_gateway_bancontact/?order_id=89&key=wc_order_eFZyH8jki6fge' - * - * @param WC_Order $order The order processed - * - * @return string The url with gateway and order id and key as params - */ - public function getWebhookUrl($order, $gatewayId) - { - $webhookUrl = WC()->api_request_url($gatewayId); - $webhookUrl = untrailingslashit($webhookUrl); - $webhookUrl = $this->asciiDomainName($webhookUrl); - $orderId = $order->get_id(); - $orderKey = $order->get_order_key(); - $webhookUrl = $this->appendOrderArgumentsToUrl( - $orderId, - $orderKey, - $webhookUrl - ); - $webhookUrl = untrailingslashit($webhookUrl); - - $this->logger->debug(" Order {$orderId} webhookUrl: {$webhookUrl}", [true]); - - return apply_filters($this->pluginId . '_webhook_url', $webhookUrl, $order); - } - /** - * @param $url - * - * @return string - */ - protected function asciiDomainName($url): string - { - $parsed = wp_parse_url($url); - $scheme = isset($parsed['scheme']) ? $parsed['scheme'] : ''; - $domain = isset($parsed['host']) ? $parsed['host'] : false; - $query = isset($parsed['query']) ? $parsed['query'] : ''; - $path = isset($parsed['path']) ? $parsed['path'] : ''; - if (!$domain) { - return $url; - } - - if (function_exists('idn_to_ascii')) { - $domain = $this->idnEncodeDomain($domain); - $url = $scheme . "://" . $domain . $path . '?' . $query; - } - - return $url; - } - /** - * @param $order_id - * @param $order_key - * @param $webhook_url - * @param string $filterFlag - * - * @return string - */ - protected function appendOrderArgumentsToUrl($order_id, $order_key, $webhook_url, $filterFlag = '') - { - $webhook_url = add_query_arg( - [ - 'order_id' => $order_id, - 'key' => $order_key, - 'filter_flag' => $filterFlag, - ], - $webhook_url - ); - return $webhook_url; - } - - /** - * @param $domain - * @return false|mixed|string - */ - protected function idnEncodeDomain($domain) - { - if ( - defined('IDNA_NONTRANSITIONAL_TO_ASCII') - && defined( - 'INTL_IDNA_VARIANT_UTS46' - ) - ) { - $domain = idn_to_ascii( - $domain, - IDNA_NONTRANSITIONAL_TO_ASCII, - INTL_IDNA_VARIANT_UTS46 - ) ? idn_to_ascii( - $domain, - IDNA_NONTRANSITIONAL_TO_ASCII, - INTL_IDNA_VARIANT_UTS46 - ) : $domain; - } else { - $domain = idn_to_ascii($domain) ? idn_to_ascii($domain) : $domain; - } - return $domain; - } - - protected function getPaymentDescription($order, $option) - { - $description = !$option ? '' : trim($option); - $description = !$description ? '{orderNumber}' : $description; - - switch ($description) { - // Support for old deprecated options. - // TODO: remove when deprecated - case '{orderNumber}': - $description = - /* translators: do not translate between {} */ - _x( - 'Order {orderNumber}', - 'Payment description for {orderNumber}', - 'mollie-payments-for-woocommerce' - ); - $description = $this->replaceTagsDescription($order, $description); - break; - case '{storeName}': - $description = - /* translators: do not translate between {} */ - _x( - 'StoreName {storeName}', - 'Payment description for {storeName}', - 'mollie-payments-for-woocommerce' - ); - $description = $this->replaceTagsDescription($order, $description); - break; - case '{customer.firstname}': - $description = - /* translators: do not translate between {} */ - _x( - 'Customer Firstname {customer.firstname}', - 'Payment description for {customer.firstname}', - 'mollie-payments-for-woocommerce' - ); - $description = $this->replaceTagsDescription($order, $description); - break; - case '{customer.lastname}': - $description = - /* translators: do not translate between {} */ - _x( - 'Customer Lastname {customer.lastname}', - 'Payment description for {customer.lastname}', - 'mollie-payments-for-woocommerce' - ); - $description = $this->replaceTagsDescription($order, $description); - break; - case '{customer.company}': - $description = - /* translators: do not translate between {} */ - _x( - 'Customer Company {customer.company}', - 'Payment description for {customer.company}', - 'mollie-payments-for-woocommerce' - ); - $description = $this->replaceTagsDescription($order, $description); - break; - // Support for custom string with interpolation. - default: - // Replace available description tags. - $description = $this->replaceTagsDescription($order, $description); - break; - } - - // Fall back on default if description turns out empty. - return !$description ? __('Order', 'woocommerce') . ' ' . $order->get_order_number() : $description; - } - - /** - * @param $order - * @param $description - * @return array|string|string[] - */ - protected function replaceTagsDescription($order, $description) - { - $replacement_tags = [ - '{orderNumber}' => $order->get_order_number(), - '{storeName}' => get_bloginfo('name'), - '{customer.firstname}' => $order->get_billing_first_name(), - '{customer.lastname}' => $order->get_billing_last_name(), - '{customer.company}' => $order->get_billing_company(), - ]; - foreach ($replacement_tags as $tag => $replacement) { - $description = str_replace($tag, $replacement, $description); - } - return $description; - } - - /** - * @param $order - * @return stdClass - */ - protected function createBillingAddress($order) - { - // Setup billing and shipping objects - $billingAddress = new stdClass(); - - // Get user details - $billingAddress->givenName = (ctype_space( - $order->get_billing_first_name() - )) ? null : $order->get_billing_first_name(); - $billingAddress->familyName = (ctype_space( - $order->get_billing_last_name() - )) ? null : $order->get_billing_last_name(); - $billingAddress->email = (ctype_space($order->get_billing_email())) - ? null : $order->get_billing_email(); - // Create billingAddress object - $billingAddress->streetAndNumber = (ctype_space( - $order->get_billing_address_1() - )) - ? null - : $this->maximalFieldLengths( - $order->get_billing_address_1(), - self::MAXIMAL_LENGHT_ADDRESS - ); - $billingAddress->streetAdditional = (ctype_space( - $order->get_billing_address_2() - )) - ? null - : $this->maximalFieldLengths( - $order->get_billing_address_2(), - self::MAXIMAL_LENGHT_ADDRESS - ); - $billingAddress->postalCode = (ctype_space( - $order->get_billing_postcode() - )) - ? null - : $this->maximalFieldLengths( - $order->get_billing_postcode(), - self::MAXIMAL_LENGHT_POSTALCODE - ); - $billingAddress->city = (ctype_space($order->get_billing_city())) - ? null - : $this->maximalFieldLengths( - $order->get_billing_city(), - self::MAXIMAL_LENGHT_CITY - ); - $billingAddress->region = (ctype_space($order->get_billing_state())) - ? null - : $this->maximalFieldLengths( - $order->get_billing_state(), - self::MAXIMAL_LENGHT_REGION - ); - $billingAddress->country = (ctype_space($order->get_billing_country())) - ? null - : $this->maximalFieldLengths( - $order->get_billing_country(), - self::MAXIMAL_LENGHT_REGION - ); - $billingAddress->organizationName = $this->billingCompanyField($order); - $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; - } - - protected function getFormatedPhoneNumber(string $phone) - { - //remove whitespaces and all non numerical characters except + - $phone = preg_replace('/[^0-9+]+/', '', $phone); - if (!is_string($phone)) { - return null; - } - //check if phone starts with 06 and replace with +316 - $phone = transformPhoneToNLFormat($phone); - - //check that $phone is in E164 format or can be changed by api - if (is_string($phone) && preg_match('/^\+[1-9]\d{10,13}$|^[1-9]\d{9,13}$/', $phone)) { - return $phone; - } - return null; - } - - /** - * @param $order - * @return string|null - */ - public function billingCompanyField($order): ?string - { - if (!trim($order->get_billing_company())) { - return $this->checkBillieCompanyField($order); - } - return $this->maximalFieldLengths( - $order->get_billing_company(), - self::MAXIMAL_LENGHT_ADDRESS - ); - } - - private function checkBillieCompanyField($order) - { - $gateway = wc_get_payment_gateway_by_order($order); - if (!$gateway || !$gateway->id) { - return null; - } - $isBillieMethodId = $gateway->id === 'mollie_wc_gateway_billie'; - if ($isBillieMethodId) { - //phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - $fieldPosted = wc_clean(wp_unslash($_POST["billing_company"] ?? '')); - if ($fieldPosted === '' || !is_string($fieldPosted)) { - return null; - } - return $this->maximalFieldLengths( - $fieldPosted, - self::MAXIMAL_LENGHT_ADDRESS - ); - } - return null; - } - - /** - * @param $order - * @return stdClass - */ - protected function createShippingAddress($order) - { - $shippingAddress = new stdClass(); - // Get user details - $shippingAddress->givenName = (ctype_space( - $order->get_shipping_first_name() - )) ? null : $order->get_shipping_first_name(); - $shippingAddress->familyName = (ctype_space( - $order->get_shipping_last_name() - )) ? null : $order->get_shipping_last_name(); - $shippingAddress->email = (ctype_space($order->get_billing_email())) - ? null - : $order->get_billing_email(); // WooCommerce doesn't have a shipping email - - - // Create shippingAddress object - $shippingAddress->streetAndNumber = (ctype_space( - $order->get_shipping_address_1() - )) - ? null - : $this->maximalFieldLengths( - $order->get_shipping_address_1(), - self::MAXIMAL_LENGHT_ADDRESS - ); - $shippingAddress->streetAdditional = (ctype_space( - $order->get_shipping_address_2() - )) - ? null - : $this->maximalFieldLengths( - $order->get_shipping_address_2(), - self::MAXIMAL_LENGHT_ADDRESS - ); - $shippingAddress->postalCode = (ctype_space( - $order->get_shipping_postcode() - )) - ? null - : $this->maximalFieldLengths( - $order->get_shipping_postcode(), - self::MAXIMAL_LENGHT_POSTALCODE - ); - $shippingAddress->city = (ctype_space($order->get_shipping_city())) - ? null - : $this->maximalFieldLengths( - $order->get_shipping_city(), - self::MAXIMAL_LENGHT_CITY - ); - $shippingAddress->region = (ctype_space($order->get_shipping_state())) - ? null - : $this->maximalFieldLengths( - $order->get_shipping_state(), - self::MAXIMAL_LENGHT_REGION - ); - $shippingAddress->country = (ctype_space( - $order->get_shipping_country() - )) - ? null - : $this->maximalFieldLengths( - $order->get_shipping_country(), - self::MAXIMAL_LENGHT_REGION - ); - return $shippingAddress; - } - - /** - * Method that shortens the field to a certain length - * - * @param string $field - * @param int $maximalLength - * - * @return null|string - */ - protected function maximalFieldLengths($field, $maximalLength) - { - if (!is_string($field)) { - return null; - } - if (is_int($maximalLength) && strlen($field) > $maximalLength) { - $field = substr($field, 0, $maximalLength); - $field = !$field ? null : $field; - } - - return $field; - } } diff --git a/src/Payment/MolliePayment.php b/src/Payment/MolliePayment.php index c6a7f808..22e22743 100644 --- a/src/Payment/MolliePayment.php +++ b/src/Payment/MolliePayment.php @@ -469,21 +469,4 @@ protected function maybeUpdateStatus( } $this->updateOrderStatus($order, $newOrderStatus); } - - protected function addCustomRequestFields($order, array $paymentRequestData) - { - if ($this->paymentMethod->hasProperty('paymentAPIfields')) { - $paymentAPIfields = $this->paymentMethod->getProperty('paymentAPIfields'); - foreach ($paymentAPIfields as $field) { - if (!method_exists($this, 'create' . ucfirst($field))) { - continue; - } - $value = $this->{'create' . ucfirst($field)}($order); - if ($value) { - $paymentRequestData[$field] = $value; - } - } - } - return $paymentRequestData; - } } diff --git a/src/Payment/Request/Decorators/AddCustomRequestFieldsDecorator.php b/src/Payment/Request/Decorators/AddCustomRequestFieldsDecorator.php new file mode 100644 index 00000000..225e692c --- /dev/null +++ b/src/Payment/Request/Decorators/AddCustomRequestFieldsDecorator.php @@ -0,0 +1,39 @@ +paymentMethod = $paymentMethod; + $this->container = $container; + } + + public function decorate(array $requestData, WC_Order $order): array + { + if (property_exists($this->paymentMethod, 'paymentAPIfields')) { + $paymentAPIfields = $this->paymentMethod->paymentAPIfields; + foreach ($paymentAPIfields as $field) { + $decoratorClass = 'Mollie\\WooCommerce\\Payment\\Decorator\\' . $field; + if (class_exists($decoratorClass)) { + $decorator = $this->container->get($decoratorClass); + if ($decorator instanceof RequestDecoratorInterface) { + $requestData = $decorator->decorate($requestData, $order); + } + } + } + } + return $requestData; + } +} diff --git a/src/Payment/Request/Decorators/AddressDecorator.php b/src/Payment/Request/Decorators/AddressDecorator.php new file mode 100644 index 00000000..94f578e3 --- /dev/null +++ b/src/Payment/Request/Decorators/AddressDecorator.php @@ -0,0 +1,246 @@ +get_meta('_mollie_payment_method_button') === 'PayPalButton'; + $billingAddress = null; + if (!$isPayPalExpressOrder) { + $billingAddress = $this->createBillingAddress($order); + $shippingAddress = $this->createShippingAddress($order); + } + $requestData['billingAddress'] = $billingAddress; + // Only add shippingAddress if all required fields are set + if ( + !empty($shippingAddress->streetAndNumber) + && !empty($shippingAddress->postalCode) + && !empty($shippingAddress->city) + && !empty($shippingAddress->country) + ) { + $requestData['shippingAddress'] = $shippingAddress; + } + + return $requestData; + } + + private function createBillingAddress(WC_Order $order) + { + // Setup billing and shipping objects + $billingAddress = new stdClass(); + + // Get user details + $billingAddress->givenName = (ctype_space( + $order->get_billing_first_name() + )) ? null : $order->get_billing_first_name(); + $billingAddress->familyName = (ctype_space( + $order->get_billing_last_name() + )) ? null : $order->get_billing_last_name(); + $billingAddress->email = (ctype_space($order->get_billing_email())) + ? null : $order->get_billing_email(); + // Create billingAddress object + $billingAddress->streetAndNumber = (ctype_space( + $order->get_billing_address_1() + )) + ? null + : $this->maximalFieldLengths( + $order->get_billing_address_1(), + self::MAXIMAL_LENGHT_ADDRESS + ); + $billingAddress->streetAdditional = (ctype_space( + $order->get_billing_address_2() + )) + ? null + : $this->maximalFieldLengths( + $order->get_billing_address_2(), + self::MAXIMAL_LENGHT_ADDRESS + ); + $billingAddress->postalCode = (ctype_space( + $order->get_billing_postcode() + )) + ? null + : $this->maximalFieldLengths( + $order->get_billing_postcode(), + self::MAXIMAL_LENGHT_POSTALCODE + ); + $billingAddress->city = (ctype_space($order->get_billing_city())) + ? null + : $this->maximalFieldLengths( + $order->get_billing_city(), + self::MAXIMAL_LENGHT_CITY + ); + $billingAddress->region = (ctype_space($order->get_billing_state())) + ? null + : $this->maximalFieldLengths( + $order->get_billing_state(), + self::MAXIMAL_LENGHT_REGION + ); + $billingAddress->country = (ctype_space($order->get_billing_country())) + ? null + : $this->maximalFieldLengths( + $order->get_billing_country(), + self::MAXIMAL_LENGHT_REGION + ); + $billingAddress->organizationName = $this->billingCompanyField($order); + $phone = $this->getPhoneNumber($order); + $billingAddress->phone = (ctype_space($phone)) + ? null + : $this->getFormatedPhoneNumber($phone); + return $billingAddress; + } + + private function createShippingAddress(WC_Order $order) + { + $shippingAddress = new stdClass(); + // Get user details + $shippingAddress->givenName = (ctype_space( + $order->get_shipping_first_name() + )) ? null : $order->get_shipping_first_name(); + $shippingAddress->familyName = (ctype_space( + $order->get_shipping_last_name() + )) ? null : $order->get_shipping_last_name(); + $shippingAddress->email = (ctype_space($order->get_billing_email())) + ? null + : $order->get_billing_email(); // WooCommerce doesn't have a shipping email + + + // Create shippingAddress object + $shippingAddress->streetAndNumber = (ctype_space( + $order->get_shipping_address_1() + )) + ? null + : $this->maximalFieldLengths( + $order->get_shipping_address_1(), + self::MAXIMAL_LENGHT_ADDRESS + ); + $shippingAddress->streetAdditional = (ctype_space( + $order->get_shipping_address_2() + )) + ? null + : $this->maximalFieldLengths( + $order->get_shipping_address_2(), + self::MAXIMAL_LENGHT_ADDRESS + ); + $shippingAddress->postalCode = (ctype_space( + $order->get_shipping_postcode() + )) + ? null + : $this->maximalFieldLengths( + $order->get_shipping_postcode(), + self::MAXIMAL_LENGHT_POSTALCODE + ); + $shippingAddress->city = (ctype_space($order->get_shipping_city())) + ? null + : $this->maximalFieldLengths( + $order->get_shipping_city(), + self::MAXIMAL_LENGHT_CITY + ); + $shippingAddress->region = (ctype_space($order->get_shipping_state())) + ? null + : $this->maximalFieldLengths( + $order->get_shipping_state(), + self::MAXIMAL_LENGHT_REGION + ); + $shippingAddress->country = (ctype_space( + $order->get_shipping_country() + )) + ? null + : $this->maximalFieldLengths( + $order->get_shipping_country(), + self::MAXIMAL_LENGHT_REGION + ); + return $shippingAddress; + } + + 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; + } + + protected function getFormatedPhoneNumber(string $phone) + { + //remove whitespaces and all non numerical characters except + + $phone = preg_replace('/[^0-9+]+/', '', $phone); + if (!is_string($phone)) { + return null; + } + //check if phone starts with 06 and replace with +316 + $phone = transformPhoneToNLFormat($phone); + + //check that $phone is in E164 format or can be changed by api + if (is_string($phone) && preg_match('/^\+[1-9]\d{10,13}$|^[1-9]\d{9,13}$/', $phone)) { + return $phone; + } + return null; + } + + /** + * @param $order + * @return string|null + */ + public function billingCompanyField($order): ?string + { + if (!trim($order->get_billing_company())) { + return $this->checkBillieCompanyField($order); + } + return $this->maximalFieldLengths( + $order->get_billing_company(), + self::MAXIMAL_LENGHT_ADDRESS + ); + } + + private function checkBillieCompanyField($order) + { + $gateway = wc_get_payment_gateway_by_order($order); + if (!$gateway || !$gateway->id) { + return null; + } + $isBillieMethodId = $gateway->id === 'mollie_wc_gateway_billie'; + if ($isBillieMethodId) { + //phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $fieldPosted = wc_clean(wp_unslash($_POST["billing_company"] ?? '')); + if ($fieldPosted === '' || !is_string($fieldPosted)) { + return null; + } + return $this->maximalFieldLengths( + $fieldPosted, + self::MAXIMAL_LENGHT_ADDRESS + ); + } + return null; + } + + /** + * Method that shortens the field to a certain length + * + * @param string $field + * @param int $maximalLength + * + * @return null|string + */ + protected function maximalFieldLengths($field, $maximalLength) + { + if (!is_string($field)) { + return null; + } + if (is_int($maximalLength) && strlen($field) > $maximalLength) { + $field = substr($field, 0, $maximalLength); + $field = !$field ? null : $field; + } + + return $field; + } +} diff --git a/src/Payment/Request/Decorators/ApplePaytokenDecorator.php b/src/Payment/Request/Decorators/ApplePaytokenDecorator.php index d13b14c5..4a4c4586 100644 --- a/src/Payment/Request/Decorators/ApplePaytokenDecorator.php +++ b/src/Payment/Request/Decorators/ApplePaytokenDecorator.php @@ -9,13 +9,18 @@ class ApplePayTokenDecorator implements RequestDecoratorInterface { - public function decorate(array $requestData, WC_Order $order): array + public function decorate(array $requestData, WC_Order $order, $context): array { // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $applePayToken = wc_clean(wp_unslash($_POST["token"] ?? '')); - if ($applePayToken && isset($requestData['payment'])) { - $encodedApplePayToken = wp_json_encode($applePayToken); - $requestData['payment']['applePayPaymentToken'] = $encodedApplePayToken; + if (!$applePayToken) { + return $requestData; + } + $encodedApplePayToken = wp_json_encode($applePayToken); + if($context === 'order') { + $requestData['payment']['applePayToken'] = $encodedApplePayToken; + } elseif ($context === 'payment') { + $requestData['applePayToken'] = $encodedApplePayToken; } return $requestData; } diff --git a/src/Payment/Request/Decorators/CardTokenDecorator.php b/src/Payment/Request/Decorators/CardTokenDecorator.php index 97a1113a..12163d89 100644 --- a/src/Payment/Request/Decorators/CardTokenDecorator.php +++ b/src/Payment/Request/Decorators/CardTokenDecorator.php @@ -9,11 +9,13 @@ class CardTokenDecorator implements RequestDecoratorInterface { - public function decorate(array $requestData, WC_Order $order): array + public function decorate(array $requestData, WC_Order $order, $context): array { $cardToken = mollieWooCommerceCardToken(); - if ($cardToken && isset($requestData['payment'])) { + if ($cardToken && isset($requestData['payment']) && $context === 'order') { $requestData['payment']['cardToken'] = $cardToken; + }elseif ($cardToken && isset($requestData['payment']) && $context === 'payment') { + $requestData['cardToken'] = $cardToken; } return $requestData; } diff --git a/src/Payment/Request/Decorators/OrderLinesDecorator.php b/src/Payment/Request/Decorators/OrderLinesDecorator.php new file mode 100644 index 00000000..7c1ff689 --- /dev/null +++ b/src/Payment/Request/Decorators/OrderLinesDecorator.php @@ -0,0 +1,28 @@ +orderLines = $orderLines; + $this->voucherDefaultCategory = $voucherDefaultCategory; + } + + public function decorate(array $requestData, WC_Order $order): array + { + $orderLines = $this->orderLines->order_lines($order, $this->voucherDefaultCategory); + $requestData['lines'] = $orderLines['lines']; + return $requestData; + } +} diff --git a/src/Payment/Request/Decorators/PaymentDescriptionDecorator.php b/src/Payment/Request/Decorators/PaymentDescriptionDecorator.php new file mode 100644 index 00000000..fc5a8e14 --- /dev/null +++ b/src/Payment/Request/Decorators/PaymentDescriptionDecorator.php @@ -0,0 +1,112 @@ +dataHelper = $dataHelper; + } + + public function decorate(array $requestData, WC_Order $order, string $context = null): array + { + $optionName = $this->dataHelper->getPluginId() . '_' . 'api_payment_description'; + $option = get_option($optionName); + $paymentDescription = $this->getPaymentDescription($order, $option); + + $requestData['description'] = $paymentDescription; + return $requestData; + } + + private function getPaymentDescription(WC_Order $order, $option): string + { + $description = !$option ? '' : trim($option); + $description = !$description ? '{orderNumber}' : $description; + + switch ($description) { + // Support for old deprecated options. + // TODO: remove when deprecated + case '{orderNumber}': + $description = + /* translators: do not translate between {} */ + _x( + 'Order {orderNumber}', + 'Payment description for {orderNumber}', + 'mollie-payments-for-woocommerce' + ); + $description = $this->replaceTagsDescription($order, $description); + break; + case '{storeName}': + $description = + /* translators: do not translate between {} */ + _x( + 'StoreName {storeName}', + 'Payment description for {storeName}', + 'mollie-payments-for-woocommerce' + ); + $description = $this->replaceTagsDescription($order, $description); + break; + case '{customer.firstname}': + $description = + /* translators: do not translate between {} */ + _x( + 'Customer Firstname {customer.firstname}', + 'Payment description for {customer.firstname}', + 'mollie-payments-for-woocommerce' + ); + $description = $this->replaceTagsDescription($order, $description); + break; + case '{customer.lastname}': + $description = + /* translators: do not translate between {} */ + _x( + 'Customer Lastname {customer.lastname}', + 'Payment description for {customer.lastname}', + 'mollie-payments-for-woocommerce' + ); + $description = $this->replaceTagsDescription($order, $description); + break; + case '{customer.company}': + $description = + /* translators: do not translate between {} */ + _x( + 'Customer Company {customer.company}', + 'Payment description for {customer.company}', + 'mollie-payments-for-woocommerce' + ); + $description = $this->replaceTagsDescription($order, $description); + break; + // Support for custom string with interpolation. + default: + // Replace available description tags. + $description = $this->replaceTagsDescription($order, $description); + break; + } + + // Fall back on default if description turns out empty. + return !$description ? __('Order', 'woocommerce') . ' ' . $order->get_order_number() : $description; + } + + private function replaceTagsDescription(WC_Order $order, string $description): string + { + $replacement_tags = [ + '{orderNumber}' => $order->get_order_number(), + '{storeName}' => get_bloginfo('name'), + '{customer.firstname}' => $order->get_billing_first_name(), + '{customer.lastname}' => $order->get_billing_last_name(), + '{customer.company}' => $order->get_billing_company(), + ]; + foreach ($replacement_tags as $tag => $replacement) { + $description = str_replace($tag, $replacement, $description); + } + return $description; + } +} diff --git a/src/Payment/Request/Decorators/SelectedIssuerDecorator.php b/src/Payment/Request/Decorators/SelectedIssuerDecorator.php new file mode 100644 index 00000000..d8029785 --- /dev/null +++ b/src/Payment/Request/Decorators/SelectedIssuerDecorator.php @@ -0,0 +1,37 @@ +pluginId = $pluginId; + } + + public function decorate(array $requestData, WC_Order $order): array + { + $gateway = wc_get_payment_gateway_by_order($order); + if ($gateway) { + $gatewayId = $gateway->id; + $selectedIssuer = $this->getSelectedIssuer($gatewayId); + $requestData['payment']['issuer'] = $selectedIssuer; + } + return $requestData; + } + + private function getSelectedIssuer(string $gatewayId): string + { + $issuer_id = $this->pluginId . '_issuer_' . $gatewayId; + //phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $postedIssuer = wc_clean(wp_unslash($_POST[$issuer_id] ?? '')); + return !empty($postedIssuer) ? $postedIssuer : ''; + } +} diff --git a/src/Payment/Request/Decorators/StoreCustomerDecorator.php b/src/Payment/Request/Decorators/StoreCustomerDecorator.php index 9c045d93..77a1b1d2 100644 --- a/src/Payment/Request/Decorators/StoreCustomerDecorator.php +++ b/src/Payment/Request/Decorators/StoreCustomerDecorator.php @@ -17,14 +17,17 @@ public function __construct(Settings $settingsHelper) } - public function decorate(array $requestData, WC_Order $order): array + public function decorate(array $requestData, WC_Order $order, $context): array { - $storeCustomer = $this->settingsHelper->shouldStoreCustomer(); $customerId = $order->get_meta('_mollie_customer_id'); - - if ($storeCustomer && $customerId) { + if(!$storeCustomer || !$customerId) { + return $requestData; + } + if($context === 'order') { $requestData['payment']['customerId'] = $customerId; + } elseif ($context === 'payment') { + $requestData['customerId'] = $customerId; } return $requestData; diff --git a/src/Payment/Request/Decorators/UrlDecorator.php b/src/Payment/Request/Decorators/UrlDecorator.php new file mode 100644 index 00000000..c5a0bc54 --- /dev/null +++ b/src/Payment/Request/Decorators/UrlDecorator.php @@ -0,0 +1,158 @@ +pluginId = $pluginId; + } + + public function decorate(array $requestData, WC_Order $order): array + { + $gateway = wc_get_payment_gateway_by_order($order); + if ($gateway) { + $returnUrl = $gateway->get_return_url($order); + $returnUrl = $this->getReturnUrl($order, $returnUrl); + $webhookUrl = $this->getWebhookUrl($order, $gateway->id); + + $requestData['redirectUrl'] = $returnUrl; + $requestData['webhookUrl'] = $webhookUrl; + } + return $requestData; + } + + /** + * Get the url to return to on Mollie return + * saves the return redirect and failed redirect, so we save the page language in case there is one set + * For example 'http://mollie-wc.docker.myhost/wc-api/mollie_return/?order_id=89&key=wc_order_eFZyH8jki6fge' + * + * @param WC_Order $order The order processed + * + * @return string The url with order id and key as params + */ + private function getReturnUrl($order, $returnUrl) + { + $returnUrl = untrailingslashit($returnUrl); + $returnUrl = $this->asciiDomainName($returnUrl); + $orderId = $order->get_id(); + $orderKey = $order->get_order_key(); + + $onMollieReturn = 'onMollieReturn'; + $returnUrl = $this->appendOrderArgumentsToUrl( + $orderId, + $orderKey, + $returnUrl, + $onMollieReturn + ); + $returnUrl = untrailingslashit($returnUrl); + $this->logger->debug(" Order {$orderId} returnUrl: {$returnUrl}", [true]); + + return apply_filters($this->pluginId . '_return_url', $returnUrl, $order); + } + + /** + * Get the webhook url + * For example 'http://mollie-wc.docker.myhost/wc-api/mollie_return/mollie_wc_gateway_bancontact/?order_id=89&key=wc_order_eFZyH8jki6fge' + * + * @param WC_Order $order The order processed + * + * @return string The url with gateway and order id and key as params + */ + public function getWebhookUrl($order, $gatewayId) + { + $webhookUrl = WC()->api_request_url($gatewayId); + $webhookUrl = untrailingslashit($webhookUrl); + $webhookUrl = $this->asciiDomainName($webhookUrl); + $orderId = $order->get_id(); + $orderKey = $order->get_order_key(); + $webhookUrl = $this->appendOrderArgumentsToUrl( + $orderId, + $orderKey, + $webhookUrl + ); + $webhookUrl = untrailingslashit($webhookUrl); + + $this->logger->debug(" Order {$orderId} webhookUrl: {$webhookUrl}", [true]); + + return apply_filters($this->pluginId . '_webhook_url', $webhookUrl, $order); + } + + /** + * @param $url + * + * @return string + */ + protected function asciiDomainName($url): string + { + $parsed = wp_parse_url($url); + $scheme = isset($parsed['scheme']) ? $parsed['scheme'] : ''; + $domain = isset($parsed['host']) ? $parsed['host'] : false; + $query = isset($parsed['query']) ? $parsed['query'] : ''; + $path = isset($parsed['path']) ? $parsed['path'] : ''; + if (!$domain) { + return $url; + } + + if (function_exists('idn_to_ascii')) { + $domain = $this->idnEncodeDomain($domain); + $url = $scheme . "://" . $domain . $path . '?' . $query; + } + + return $url; + } + /** + * @param $order_id + * @param $order_key + * @param $webhook_url + * @param string $filterFlag + * + * @return string + */ + protected function appendOrderArgumentsToUrl($order_id, $order_key, $webhook_url, $filterFlag = '') + { + $webhook_url = add_query_arg( + [ + 'order_id' => $order_id, + 'key' => $order_key, + 'filter_flag' => $filterFlag, + ], + $webhook_url + ); + return $webhook_url; + } + /** + * @param $domain + * @return false|mixed|string + */ + protected function idnEncodeDomain($domain) + { + if ( + defined('IDNA_NONTRANSITIONAL_TO_ASCII') + && defined( + 'INTL_IDNA_VARIANT_UTS46' + ) + ) { + $domain = idn_to_ascii( + $domain, + IDNA_NONTRANSITIONAL_TO_ASCII, + INTL_IDNA_VARIANT_UTS46 + ) ? idn_to_ascii( + $domain, + IDNA_NONTRANSITIONAL_TO_ASCII, + INTL_IDNA_VARIANT_UTS46 + ) : $domain; + } else { + $domain = idn_to_ascii($domain) ? idn_to_ascii($domain) : $domain; + } + return $domain; + } +} diff --git a/src/Payment/Request/Strategies/OrderRequestStrategy.php b/src/Payment/Request/Strategies/OrderRequestStrategy.php index 6f0f55b3..7d7619ea 100644 --- a/src/Payment/Request/Strategies/OrderRequestStrategy.php +++ b/src/Payment/Request/Strategies/OrderRequestStrategy.php @@ -3,13 +3,14 @@ namespace Mollie\WooCommerce\Payment\Request; use Inpsyde\PaymentGateway\PaymentGateway; -use Mollie\WooCommerce\Payment\Request\RequestStrategyInterface; +use Mollie\WooCommerce\Settings\Settings; +use Mollie\WooCommerce\Shared\Data; use WC_Order; class OrderRequestStrategy implements RequestStrategyInterface { - private $dataHelper; - private $settingsHelper; + private Data $dataHelper; + private Settings $settingsHelper; private array $decorators; public function __construct($dataHelper, $settingsHelper, array $decorators) { @@ -27,33 +28,8 @@ public function createRequest(WC_Order $order, string $customerId): array return [ 'result' => 'failure' ]; } - $gatewayId = $gateway->id; - $paymentLocale = $settingsHelper->getPaymentLocale(); - $selectedIssuer = $this->getSelectedIssuer($gatewayId); - $returnUrl = $gateway->get_return_url($order); - $returnUrl = $this->getReturnUrl($order, $returnUrl); - $webhookUrl = $this->getWebhookUrl($order, $gatewayId); - $isPayPalExpressOrder = $order->get_meta('_mollie_payment_method_button') === 'PayPalButton'; - $billingAddress = null; - if (!$isPayPalExpressOrder) { - $billingAddress = $this->createBillingAddress($order); - $shippingAddress = $this->createShippingAddress($order); - } - // Only add shippingAddress if all required fields are set - if ( - !empty($shippingAddress->streetAndNumber) - && !empty($shippingAddress->postalCode) - && !empty($shippingAddress->city) - && !empty($shippingAddress->country) - ) { - $requestData['shippingAddress'] = $shippingAddress; - } - - // Generate order lines for Mollie Orders - $orderLinesHelper = $this->orderLines; - $orderLines = $orderLinesHelper->order_lines($order, $voucherDefaultCategory); $methodId = substr($gateway->id, strrpos($gateway->id, '_') + 1); - + $paymentLocale = $settingsHelper->getPaymentLocale(); // Build the Mollie order data $requestData = [ 'amount' => [ @@ -63,22 +39,15 @@ public function createRequest(WC_Order $order, string $customerId): array $this->dataHelper->getOrderCurrency($order) ), ], - 'redirectUrl' => $returnUrl, - 'webhookUrl' => $webhookUrl, 'method' => $methodId, - 'payment' => [ - 'issuer' => $selectedIssuer, - ], 'locale' => $paymentLocale, - 'billingAddress' => $billingAddress, 'metadata' => apply_filters( - $this->pluginId . '_payment_object_metadata', + $this->dataHelper->getPluginId() . '_payment_object_metadata', [ 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), ] ), - 'lines' => $orderLines['lines'], 'orderNumber' => $order->get_order_number(), ]; diff --git a/src/Payment/Request/Strategies/PaymentRequestStrategy.php b/src/Payment/Request/Strategies/PaymentRequestStrategy.php index 52fd8675..dea47552 100644 --- a/src/Payment/Request/Strategies/PaymentRequestStrategy.php +++ b/src/Payment/Request/Strategies/PaymentRequestStrategy.php @@ -26,18 +26,8 @@ public function createRequest(WC_Order $order, string $customerId): array return ['result' => 'failure']; } $settingsHelper = $this->settingsHelper; - $optionName = $this->pluginId . '_' . 'api_payment_description'; - $option = get_option($optionName); - $paymentDescription = $this->getPaymentDescription($order, $option); + $methodId = substr($gateway->id, strrpos($gateway->id, '_') + 1); $paymentLocale = $settingsHelper->getPaymentLocale(); - $storeCustomer = $settingsHelper->shouldStoreCustomer(); - - $gatewayId = $gateway->id; - $selectedIssuer = $this->getSelectedIssuer($gatewayId); - $returnUrl = $gateway->get_return_url($order); - $returnUrl = $this->getReturnUrl($order, $returnUrl); - $webhookUrl = $this->getWebhookUrl($order, $gatewayId); - $orderId = $order->get_id(); $paymentRequestData = [ 'amount' => [ @@ -52,35 +42,16 @@ public function createRequest(WC_Order $order, string $customerId): array ), ], 'description' => $paymentDescription, - 'redirectUrl' => $returnUrl, - 'webhookUrl' => $webhookUrl, - 'method' => $this->paymentMethod->getProperty('id'), - 'issuer' => $selectedIssuer, + 'method' => $methodId, 'locale' => $paymentLocale, 'metadata' => apply_filters( - $this->pluginId . '_payment_object_metadata', + $this->dataHelper->getPluginId() . '_payment_object_metadata', [ 'order_id' => $order->get_id(), ] ), ]; - $paymentRequestData = $this->addSequenceTypeForSubscriptionsFirstPayments($order->get_id(), $gateway, $paymentRequestData); - - if ($storeCustomer) { - $paymentRequestData['customerId'] = $customerId; - } - - $cardToken = mollieWooCommerceCardToken(); - if ($cardToken) { - $paymentRequestData['cardToken'] = $cardToken; - } - //phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - $applePayToken = wc_clean(wp_unslash($_POST["token"] ?? '')); - if ($applePayToken) { - $encodedApplePayToken = wp_json_encode($applePayToken); - $paymentRequestData['applePayPaymentToken'] = $encodedApplePayToken; - } $paymentRequestData = $this->addCustomRequestFields($order, $paymentRequestData); $context = 'payment'; foreach ($this->decorators as $decorator) { diff --git a/src/Payment/inc/services.php b/src/Payment/inc/services.php index 5003d0ae..2bc1bea8 100644 --- a/src/Payment/inc/services.php +++ b/src/Payment/inc/services.php @@ -3,10 +3,16 @@ declare(strict_types=1); +use Mollie\WooCommerce\Payment\Decorator\AddCustomRequestFieldsDecorator; +use Mollie\WooCommerce\Payment\Decorator\AddressDecorator; use Mollie\WooCommerce\Payment\Decorator\AddSequenceTypeForSubscriptionsDecorator; use Mollie\WooCommerce\Payment\Decorator\ApplePayTokenDecorator; use Mollie\WooCommerce\Payment\Decorator\CardTokenDecorator; +use Mollie\WooCommerce\Payment\Decorator\OrderLinesDecorator; +use Mollie\WooCommerce\Payment\Decorator\PaymentDescriptionDecorator; +use Mollie\WooCommerce\Payment\Decorator\SelectedIssuerDecorator; use Mollie\WooCommerce\Payment\Decorator\StoreCustomerDecorator; +use Mollie\WooCommerce\Payment\Decorator\UrlDecorator; use Mollie\WooCommerce\Payment\MollieObject; use Mollie\WooCommerce\Payment\OrderLines; use Mollie\WooCommerce\Payment\PaymentFactory; @@ -61,7 +67,30 @@ $pluginId = $container->get('shared.plugin_id'); return new AddSequenceTypeForSubscriptionsDecorator($dataHelper, $pluginId); }, - + OrderLinesDecorator::class => static function (ContainerInterface $container): OrderLinesDecorator { + $orderLines = $container->get(OrderLines::class); + $voucherDefaultCategory = $container->get('voucher.defaultCategory'); + return new OrderLinesDecorator($orderLines, $voucherDefaultCategory); + }, + AddressDecorator::class => static function (): AddressDecorator { + return new AddressDecorator(); + }, + UrlDecorator::class => static function (ContainerInterface $container): UrlDecorator { + $pluginId = $container->get('shared.plugin_id'); + return new UrlDecorator($pluginId); + }, + SelectedIssuerDecorator::class => static function (ContainerInterface $container): SelectedIssuerDecorator { + $pluginId = $container->get('shared.plugin_id'); + return new SelectedIssuerDecorator($pluginId); + }, + PaymentDescriptionDecorator::class => static function (ContainerInterface $container): PaymentDescriptionDecorator { + $dataHelper = $container->get('settings.data_helper'); + return new PaymentDescriptionDecorator($dataHelper); + }, + AddCustomRequestFieldsDecorator::class => static function (ContainerInterface $container): AddCustomRequestFieldsDecorator { + $paymentMethod = $container->get('payment.method'); + return new AddCustomRequestFieldsDecorator($paymentMethod, $container); + }, 'request.strategy.order' => static function (ContainerInterface $container): RequestStrategyInterface { $dataHelper = $container->get('settings.data_helper'); $settingsHelper = $container->get('settings.settings_helper'); @@ -73,7 +102,11 @@ $container->get(ApplePayTokenDecorator::class), $container->get(CardTokenDecorator::class), $container->get(StoreCustomerDecorator::class), - + $container->get(AddSequenceTypeForSubscriptionsDecorator::class), + $container->get(OrderLinesDecorator::class), + $container->get(AddressDecorator::class), + $container->get(UrlDecorator::class), + $container->get(SelectedIssuerDecorator::class), ] ); }, @@ -81,7 +114,14 @@ $dataHelper = $container->get('settings.data_helper'); $settingsHelper = $container->get('settings.settings_helper'); $decorators = [ - + $container->get(SelectedIssuerDecorator::class), + $container->get(UrlDecorator::class), + $container->get(AddSequenceTypeForSubscriptionsDecorator::class), + $container->get(ApplePayTokenDecorator::class), + $container->get(CardTokenDecorator::class), + $container->get(StoreCustomerDecorator::class), + $container->get(PaymentDescriptionDecorator::class), + $container->get(AddCustomRequestFieldsDecorator::class), ]; return new PaymentRequestStrategy($dataHelper, $settingsHelper, $decorators); }, diff --git a/src/PaymentMethods/Alma.php b/src/PaymentMethods/Alma.php index d127a986..2fb07abf 100644 --- a/src/PaymentMethods/Alma.php +++ b/src/PaymentMethods/Alma.php @@ -23,8 +23,7 @@ protected function getConfig(): array 'confirmationDelayed' => false, 'SEPA' => false, 'paymentAPIfields' => [ - 'billingAddress', - 'shippingAddress', + 'AddressDecorator', ], 'docs' => 'https://www.mollie.com/gb/payments/alma', ];