From ad523e09731996624d444fd46835643444708e88 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 13 Sep 2022 10:16:15 +0300 Subject: [PATCH 01/26] Check that wc session exists before using it --- modules/ppcp-vaulting/src/VaultingModule.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index 99184f096..d2fb3026c 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -107,7 +107,11 @@ class VaultingModule implements ModuleInterface { add_action( 'woocommerce_created_customer', function( int $customer_id ) use ( $subscription_helper ) { - $guest_customer_id = WC()->session->get( 'ppcp_guest_customer_id' ); + $session = WC()->session; + if ( ! $session ) { + return; + } + $guest_customer_id = $session->get( 'ppcp_guest_customer_id' ); if ( $guest_customer_id && $subscription_helper->cart_contains_subscription() ) { update_user_meta( $customer_id, 'ppcp_guest_customer_id', $guest_customer_id ); } From dee2c66f1445744be315ec7baae1c1419bce92c7 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 17 Oct 2022 15:41:23 +0200 Subject: [PATCH 02/26] Add check for empty brand name and disable gateway if error message --- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 928fac2ef..980cf6516 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -509,13 +509,18 @@ class PayUponInvoice { ) { $error_messages = array(); $pui_gateway = WC()->payment_gateways->payment_gateways()[ PayUponInvoiceGateway::ID ]; + if ( $pui_gateway->get_option( 'brand_name' ) === '' ) { + $error_messages[] = esc_html__( 'Could not enable gateway because "Brand name" field is empty.', 'woocommerce-paypal-payments' ); + } if ( $pui_gateway->get_option( 'logo_url' ) === '' ) { $error_messages[] = esc_html__( 'Could not enable gateway because "Logo URL" field is empty.', 'woocommerce-paypal-payments' ); } if ( $pui_gateway->get_option( 'customer_service_instructions' ) === '' ) { $error_messages[] = esc_html__( 'Could not enable gateway because "Customer service instructions" field is empty.', 'woocommerce-paypal-payments' ); } - if ( count( $error_messages ) > 0 ) { ?> + if ( count( $error_messages ) > 0 ) { + $pui_gateway->update_option( 'enabled', 'no' ); + ?>
Date: Tue, 18 Oct 2022 15:59:11 +0200 Subject: [PATCH 03/26] Add php 8.1 support (WIP) --- .../src/Helper/SubscriptionHelper.php | 8 +++++--- modules/ppcp-subscription/src/SubscriptionModule.php | 2 +- modules/ppcp-vaulting/src/CustomerApprovalListener.php | 6 ++++-- modules/ppcp-vaulting/src/VaultedCreditCardHandler.php | 4 +--- .../ppcp-wc-gateway/src/Assets/SettingsPageAssets.php | 9 ++++----- .../ppcp-wc-gateway/src/Gateway/CardButtonGateway.php | 2 +- .../ppcp-wc-gateway/src/Gateway/CreditCardGateway.php | 2 +- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 4 ++-- modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php | 4 ++-- .../src/Gateway/PayUponInvoice/FraudNetSessionId.php | 2 +- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 10 +++++----- .../Gateway/PayUponInvoice/PayUponInvoiceGateway.php | 6 +++--- .../Gateway/PayUponInvoice/PaymentSourceFactory.php | 2 +- .../src/Helper/PayUponInvoiceHelper.php | 2 +- 14 files changed, 32 insertions(+), 31 deletions(-) diff --git a/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php b/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php index 0d9366a7b..88f191d4c 100644 --- a/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php +++ b/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php @@ -120,8 +120,10 @@ class SubscriptionHelper { * @return bool Whether page is change subscription or not. */ public function is_subscription_change_payment(): bool { - $pay_for_order = filter_input( INPUT_GET, 'pay_for_order', FILTER_SANITIZE_STRING ); - $change_payment_method = filter_input( INPUT_GET, 'change_payment_method', FILTER_SANITIZE_STRING ); - return ( isset( $pay_for_order ) && isset( $change_payment_method ) ); + if ( ! isset( $_GET['pay_for_order'] ) || ! isset( $_GET['change_payment_method'] ) ) { + return false; + } + + return true; } } diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php index 6f152307b..b81ce4697 100644 --- a/modules/ppcp-subscription/src/SubscriptionModule.php +++ b/modules/ppcp-subscription/src/SubscriptionModule.php @@ -100,7 +100,7 @@ class SubscriptionModule implements ModuleInterface { add_filter( 'ppcp_create_order_request_body_data', function( array $data ) use ( $c ) { - $wc_order_action = filter_input( INPUT_POST, 'wc_order_action', FILTER_SANITIZE_STRING ) ?? ''; + $wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) ); if ( $wc_order_action === 'wcs_process_renewal' && isset( $data['payment_source']['token'] ) && $data['payment_source']['token']['type'] === 'PAYMENT_METHOD_TOKEN' diff --git a/modules/ppcp-vaulting/src/CustomerApprovalListener.php b/modules/ppcp-vaulting/src/CustomerApprovalListener.php index 7e2f1406a..11f710bf8 100644 --- a/modules/ppcp-vaulting/src/CustomerApprovalListener.php +++ b/modules/ppcp-vaulting/src/CustomerApprovalListener.php @@ -53,7 +53,7 @@ class CustomerApprovalListener { * @return void */ public function listen(): void { - $token = filter_input( INPUT_GET, 'approval_token_id', FILTER_SANITIZE_STRING ); + $token = wc_clean( wp_unslash( $_GET['approval_token_id'] ?? '' ) ); if ( ! is_string( $token ) ) { return; } @@ -94,7 +94,9 @@ class CustomerApprovalListener { add_action( 'woocommerce_init', function () use ( $message ): void { - wc_add_notice( $message, 'error' ); + if ( function_exists( 'wc_add_notice' ) ) { + wc_add_notice( $message, 'error' ); + } } ); } diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php index 172837633..ec01fc392 100644 --- a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -143,10 +143,8 @@ class VaultedCreditCardHandler { string $saved_credit_card, WC_Order $wc_order ): WC_Order { - - $change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); if ( - $change_payment + isset( $_POST['woocommerce_change_payment'] ) && $this->subscription_helper->has_subscription( $wc_order->get_id() ) && $this->subscription_helper->is_subscription_change_payment() && $saved_credit_card diff --git a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php index 5926580ff..dbb48ae66 100644 --- a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php +++ b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php @@ -83,14 +83,13 @@ class SettingsPageAssets { } $screen = get_current_screen(); - - $tab = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_STRING ); - $section = filter_input( INPUT_GET, 'section', FILTER_SANITIZE_STRING ); - - if ( ! 'woocommerce_page_wc-settings' === $screen->id ) { + if ( $screen->id !== 'woocommerce_page_wc-settings' ) { return false; } + $tab = wc_clean( wp_unslash( $_GET['tab'] ?? '' ) ); + $section = wc_clean( wp_unslash( $_GET['section'] ?? '' ) ); + return 'checkout' === $tab && 'ppcp-gateway' === $section; } diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php index 735093056..616a2ea48 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -275,7 +275,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { * If customer has chosen change Subscription payment. */ if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { - $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); + $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); if ( $saved_paypal_payment ) { update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 07138cce5..e204d52d7 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -360,7 +360,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { /** * If customer has chosen a saved credit card payment. */ - $saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING ); + $saved_credit_card = wc_clean( wp_unslash( $_POST['saved_credit_card'] ?? '' ) ); if ( $saved_credit_card ) { try { $wc_order = $this->vaulted_credit_card_handler->handle_payment( diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 921f3d278..1ca22fc2b 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -138,7 +138,7 @@ class OXXO { 'add_meta_boxes', function( string $post_type ) { if ( $post_type === 'shop_order' ) { - $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_STRING ); + $post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) ); $order = wc_get_order( $post_id ); if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === OXXOGateway::ID ) { $payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' ); @@ -182,7 +182,7 @@ class OXXO { return false; } - $billing_country = filter_input( INPUT_POST, 'country', FILTER_SANITIZE_STRING ) ?? null; + $billing_country = wc_clean( wp_unslash( $_POST['country'] ?? '' ) ); if ( $billing_country && 'MX' !== $billing_country ) { return false; } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 21d8c9b7a..e44b5aeb6 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -400,7 +400,7 @@ class PayPalGateway extends \WC_Payment_Gateway { ); } - $funding_source = filter_input( INPUT_POST, 'ppcp-funding-source', FILTER_SANITIZE_STRING ); + $funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? '' ) ); if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) { $user_id = (int) $wc_order->get_customer_id(); @@ -423,7 +423,7 @@ class PayPalGateway extends \WC_Payment_Gateway { * If customer has chosen change Subscription payment. */ if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { - $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); + $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); if ( $saved_paypal_payment ) { update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php index 358c50958..3bc5839ba 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php @@ -33,7 +33,7 @@ class FraudNetSessionId { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) { - $pui_pay_for_order_session_id = filter_input( INPUT_POST, 'pui_pay_for_order_session_id', FILTER_SANITIZE_STRING ); + $pui_pay_for_order_session_id = wc_clean( wp_unslash( $_POST['pui_pay_for_order_session_id'] ?? '' ) ); if ( $pui_pay_for_order_session_id && '' !== $pui_pay_for_order_session_id ) { return $pui_pay_for_order_session_id; } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 928fac2ef..baf9de13e 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -409,7 +409,7 @@ class PayUponInvoice { add_action( 'woocommerce_after_checkout_validation', function( array $fields, WP_Error $errors ) { - $payment_method = filter_input( INPUT_POST, 'payment_method', FILTER_SANITIZE_STRING ); + $payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ?? '' ) ); if ( PayUponInvoiceGateway::ID !== $payment_method ) { return; } @@ -418,12 +418,12 @@ class PayUponInvoice { $errors->add( 'validation', __( 'Billing country not available.', 'woocommerce-paypal-payments' ) ); } - $birth_date = filter_input( INPUT_POST, 'billing_birth_date', FILTER_SANITIZE_STRING ); + $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); if ( ( $birth_date && ! $this->checkout_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { $errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) ); } - $national_number = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ); + $national_number = wc_clean( wp_unslash( $_POST['billing_phone'] ?? 0 ) ); if ( ! $national_number ) { $errors->add( 'validation', __( 'Phone field cannot be empty.', 'woocommerce-paypal-payments' ) ); } @@ -484,7 +484,7 @@ class PayUponInvoice { add_action( 'woocommerce_update_options_checkout_ppcp-pay-upon-invoice-gateway', function () { - $customer_service_instructions = filter_input( INPUT_POST, 'woocommerce_ppcp-pay-upon-invoice-gateway_customer_service_instructions', FILTER_SANITIZE_STRING ); + $customer_service_instructions = wc_clean( wp_unslash( $_POST['woocommerce_ppcp-pay-upon-invoice-gateway_customer_service_instructions'] ?? '' ) ); if ( '' === $customer_service_instructions ) { $gateway_settings = get_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings' ); $gateway_enabled = $gateway_settings['enabled'] ?? ''; @@ -537,7 +537,7 @@ class PayUponInvoice { 'add_meta_boxes', function( string $post_type ) { if ( $post_type === 'shop_order' ) { - $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_STRING ); + $post_id = wc_clean( wp_unslash( $_GET['post'] ?? 0 ) ); $order = wc_get_order( $post_id ); if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) { $instructions = $order->get_meta( 'ppcp_ratepay_payment_instructions_payment_reference' ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index 7cff41b36..628184b5f 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -203,9 +203,9 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { */ public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); - $birth_date = filter_input( INPUT_POST, 'billing_birth_date', FILTER_SANITIZE_STRING ) ?? ''; + $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); - $pay_for_order = filter_input( INPUT_GET, 'pay_for_order', FILTER_SANITIZE_STRING ); + $pay_for_order = wc_clean( wp_unslash( $_GET['pay_for_order'] ?? '' ) ); if ( 'true' === $pay_for_order ) { if ( ! $this->checkout_helper->validate_birth_date( $birth_date ) ) { wc_add_notice( 'Invalid birth date.', 'error' ); @@ -215,7 +215,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { } } - $phone_number = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ) ?? ''; + $phone_number = wc_clean( wp_unslash( $_POST['billing_phone'] ?? '' ) ); if ( $phone_number ) { $wc_order->set_billing_phone( $phone_number ); $wc_order->save(); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php index 5e13ba0a8..e0047208d 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php @@ -25,7 +25,7 @@ class PaymentSourceFactory { */ public function from_wc_order( WC_Order $order, string $birth_date ) { $address = $order->get_address(); - $phone = filter_input( INPUT_POST, 'billing_phone', FILTER_SANITIZE_STRING ) ?? $address['phone'] ?: ''; + $phone = wc_clean( wp_unslash( $_POST['billing_phone'] ?? '' ) ) ?? $address['phone'] ?? ''; $phone_country_code = WC()->countries->get_country_calling_code( $address['country'] ); $phone_country_code = is_array( $phone_country_code ) && ! empty( $phone_country_code ) ? $phone_country_code[0] : $phone_country_code; if ( is_string( $phone_country_code ) && '' !== $phone_country_code ) { diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php index 6b9af84c8..6bf0d126b 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -54,7 +54,7 @@ class PayUponInvoiceHelper { return false; } - $billing_country = filter_input( INPUT_POST, 'country', FILTER_SANITIZE_STRING ) ?? null; + $billing_country = wc_clean( wp_unslash( $_POST['country'] ?? '' ) ); if ( $billing_country && 'DE' !== $billing_country ) { return false; } From a77078a3728eedfd1f4d37293f98946c09fc6137 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 19 Oct 2022 11:50:15 +0200 Subject: [PATCH 04/26] Fix token check on listener --- modules/ppcp-subscription/src/SubscriptionModule.php | 10 +++++----- modules/ppcp-vaulting/src/CustomerApprovalListener.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php index b81ce4697..cf70a9848 100644 --- a/modules/ppcp-subscription/src/SubscriptionModule.php +++ b/modules/ppcp-subscription/src/SubscriptionModule.php @@ -216,7 +216,7 @@ class SubscriptionModule implements ModuleInterface { && PayPalGateway::ID === $id && $subscription_helper->is_subscription_change_payment() ) { - $tokens = $payment_token_repository->all_for_user_id( get_current_user_id() ); + $tokens = $payment_token_repository->all_for_user_id( get_current_user_id() ); if ( ! $tokens || ! $payment_token_repository->tokens_contains_paypal( $tokens ) ) { return esc_html__( 'No PayPal payments saved, in order to use a saved payment you first need to create it through a purchase.', @@ -224,10 +224,10 @@ class SubscriptionModule implements ModuleInterface { ); } - $output = sprintf( - '

', + esc_html__( 'Select a saved PayPal payment', 'woocommerce-paypal-payments' ) + ); foreach ( $tokens as $token ) { if ( isset( $token->source()->paypal ) ) { $output .= sprintf( diff --git a/modules/ppcp-vaulting/src/CustomerApprovalListener.php b/modules/ppcp-vaulting/src/CustomerApprovalListener.php index 11f710bf8..4ce88691f 100644 --- a/modules/ppcp-vaulting/src/CustomerApprovalListener.php +++ b/modules/ppcp-vaulting/src/CustomerApprovalListener.php @@ -54,7 +54,7 @@ class CustomerApprovalListener { */ public function listen(): void { $token = wc_clean( wp_unslash( $_GET['approval_token_id'] ?? '' ) ); - if ( ! is_string( $token ) ) { + if ( ! $token ) { return; } From 4e248a815e3f56229028a684fa2db827ed79ff11 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 19 Oct 2022 16:52:43 +0200 Subject: [PATCH 05/26] Disable phpcs when nonce check not needed --- .../src/Helper/SubscriptionHelper.php | 1 + .../src/SubscriptionModule.php | 1 + .../src/Assets/SettingsPageAssets.php | 2 ++ .../src/Gateway/CardButtonGateway.php | 1 + .../src/Gateway/CreditCardGateway.php | 1 + .../ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 2 ++ .../src/Gateway/PayPalGateway.php | 3 ++- .../Gateway/PayUponInvoice/PayUponInvoice.php | 18 ++++++------------ .../PayUponInvoice/PayUponInvoiceGateway.php | 3 ++- .../src/Helper/PayUponInvoiceHelper.php | 1 + 10 files changed, 19 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php b/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php index 88f191d4c..cb4521f37 100644 --- a/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php +++ b/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php @@ -120,6 +120,7 @@ class SubscriptionHelper { * @return bool Whether page is change subscription or not. */ public function is_subscription_change_payment(): bool { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! isset( $_GET['pay_for_order'] ) || ! isset( $_GET['change_payment_method'] ) ) { return false; } diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php index cf70a9848..aba7821d4 100644 --- a/modules/ppcp-subscription/src/SubscriptionModule.php +++ b/modules/ppcp-subscription/src/SubscriptionModule.php @@ -100,6 +100,7 @@ class SubscriptionModule implements ModuleInterface { add_filter( 'ppcp_create_order_request_body_data', function( array $data ) use ( $c ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) ); if ( $wc_order_action === 'wcs_process_renewal' diff --git a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php index dbb48ae66..bab40245e 100644 --- a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php +++ b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php @@ -87,8 +87,10 @@ class SettingsPageAssets { return false; } + // phpcs:disable WordPress.Security.NonceVerification.Recommended $tab = wc_clean( wp_unslash( $_GET['tab'] ?? '' ) ); $section = wc_clean( wp_unslash( $_GET['section'] ?? '' ) ); + // phpcs:enable WordPress.Security.NonceVerification.Recommended return 'checkout' === $tab && 'ppcp-gateway' === $section; } diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php index 616a2ea48..8b4412671 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -275,6 +275,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { * If customer has chosen change Subscription payment. */ if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); if ( $saved_paypal_payment ) { update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index e204d52d7..e2c9c7c77 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -360,6 +360,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { /** * If customer has chosen a saved credit card payment. */ + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $saved_credit_card = wc_clean( wp_unslash( $_POST['saved_credit_card'] ?? '' ) ); if ( $saved_credit_card ) { try { diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index 1ca22fc2b..d5ecdd8b0 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -138,6 +138,7 @@ class OXXO { 'add_meta_boxes', function( string $post_type ) { if ( $post_type === 'shop_order' ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) ); $order = wc_get_order( $post_id ); if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === OXXOGateway::ID ) { @@ -182,6 +183,7 @@ class OXXO { return false; } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $billing_country = wc_clean( wp_unslash( $_POST['country'] ?? '' ) ); if ( $billing_country && 'MX' !== $billing_country ) { return false; diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index e44b5aeb6..87c1b88fd 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -400,8 +400,8 @@ class PayPalGateway extends \WC_Payment_Gateway { ); } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? '' ) ); - if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) { $user_id = (int) $wc_order->get_customer_id(); $tokens = $this->payment_token_repository->all_for_user_id( $user_id ); @@ -423,6 +423,7 @@ class PayPalGateway extends \WC_Payment_Gateway { * If customer has chosen change Subscription payment. */ if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); if ( $saved_paypal_payment ) { update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index baf9de13e..fe83b58fe 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -409,6 +409,7 @@ class PayUponInvoice { add_action( 'woocommerce_after_checkout_validation', function( array $fields, WP_Error $errors ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ?? '' ) ); if ( PayUponInvoiceGateway::ID !== $payment_method ) { return; @@ -418,6 +419,7 @@ class PayUponInvoice { $errors->add( 'validation', __( 'Billing country not available.', 'woocommerce-paypal-payments' ) ); } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); if ( ( $birth_date && ! $this->checkout_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { $errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) ); @@ -484,18 +486,9 @@ class PayUponInvoice { add_action( 'woocommerce_update_options_checkout_ppcp-pay-upon-invoice-gateway', function () { - $customer_service_instructions = wc_clean( wp_unslash( $_POST['woocommerce_ppcp-pay-upon-invoice-gateway_customer_service_instructions'] ?? '' ) ); - if ( '' === $customer_service_instructions ) { - $gateway_settings = get_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings' ); - $gateway_enabled = $gateway_settings['enabled'] ?? ''; - if ( 'yes' === $gateway_enabled ) { - $gateway_settings['enabled'] = 'no'; - update_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings', $gateway_settings ); - - $redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-pay-upon-invoice-gateway' ); - wp_safe_redirect( $redirect_url ); - exit; - } + $gateway = WC()->payment_gateways()->payment_gateways()[ PayUponInvoiceGateway::ID ]; + if ( $gateway && $gateway->get_option( 'customer_service_instructions' ) === '' ) { + $gateway->update_option( 'enabled', 'no' ); } } ); @@ -537,6 +530,7 @@ class PayUponInvoice { 'add_meta_boxes', function( string $post_type ) { if ( $post_type === 'shop_order' ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = wc_clean( wp_unslash( $_GET['post'] ?? 0 ) ); $order = wc_get_order( $post_id ); if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) { diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index 628184b5f..2afa61b78 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -203,8 +203,8 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { */ public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); + // phpcs:disable WordPress.Security.NonceVerification.Recommended $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); - $pay_for_order = wc_clean( wp_unslash( $_GET['pay_for_order'] ?? '' ) ); if ( 'true' === $pay_for_order ) { if ( ! $this->checkout_helper->validate_birth_date( $birth_date ) ) { @@ -216,6 +216,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { } $phone_number = wc_clean( wp_unslash( $_POST['billing_phone'] ?? '' ) ); + // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( $phone_number ) { $wc_order->set_billing_phone( $phone_number ); $wc_order->save(); diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php index 6bf0d126b..439ce3ee1 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -54,6 +54,7 @@ class PayUponInvoiceHelper { return false; } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $billing_country = wc_clean( wp_unslash( $_POST['country'] ?? '' ) ); if ( $billing_country && 'DE' !== $billing_country ) { return false; From a18832371e5e64871057b1f301ffc093257ac619 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Wed, 19 Oct 2022 17:22:09 +0200 Subject: [PATCH 06/26] Disable phpcs when nonce check not needed --- modules/ppcp-subscription/src/SubscriptionModule.php | 2 +- modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php | 2 +- modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php | 2 +- modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php | 2 +- modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php | 4 ++-- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 6 +++--- .../src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php | 8 ++++---- .../ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php index aba7821d4..75dc799d5 100644 --- a/modules/ppcp-subscription/src/SubscriptionModule.php +++ b/modules/ppcp-subscription/src/SubscriptionModule.php @@ -100,7 +100,7 @@ class SubscriptionModule implements ModuleInterface { add_filter( 'ppcp_create_order_request_body_data', function( array $data ) use ( $c ) { - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing) $wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) ); if ( $wc_order_action === 'wcs_process_renewal' diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php index 8b4412671..546923ef5 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -275,7 +275,7 @@ class CardButtonGateway extends \WC_Payment_Gateway { * If customer has chosen change Subscription payment. */ if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); if ( $saved_paypal_payment ) { update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index e2c9c7c77..354667aca 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -360,7 +360,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { /** * If customer has chosen a saved credit card payment. */ - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $saved_credit_card = wc_clean( wp_unslash( $_POST['saved_credit_card'] ?? '' ) ); if ( $saved_credit_card ) { try { diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php index d5ecdd8b0..52a2bbadd 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php +++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php @@ -183,7 +183,7 @@ class OXXO { return false; } - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $billing_country = wc_clean( wp_unslash( $_POST['country'] ?? '' ) ); if ( $billing_country && 'MX' !== $billing_country ) { return false; diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 87c1b88fd..d5237b619 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -400,7 +400,7 @@ class PayPalGateway extends \WC_Payment_Gateway { ); } - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? '' ) ); if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) { $user_id = (int) $wc_order->get_customer_id(); @@ -423,7 +423,7 @@ class PayPalGateway extends \WC_Payment_Gateway { * If customer has chosen change Subscription payment. */ if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); if ( $saved_paypal_payment ) { update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index fe83b58fe..a4f89678c 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -409,7 +409,7 @@ class PayUponInvoice { add_action( 'woocommerce_after_checkout_validation', function( array $fields, WP_Error $errors ) { - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ?? '' ) ); if ( PayUponInvoiceGateway::ID !== $payment_method ) { return; @@ -419,7 +419,7 @@ class PayUponInvoice { $errors->add( 'validation', __( 'Billing country not available.', 'woocommerce-paypal-payments' ) ); } - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); if ( ( $birth_date && ! $this->checkout_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { $errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) ); @@ -530,7 +530,7 @@ class PayUponInvoice { 'add_meta_boxes', function( string $post_type ) { if ( $post_type === 'shop_order' ) { - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $post_id = wc_clean( wp_unslash( $_GET['post'] ?? 0 ) ); $order = wc_get_order( $post_id ); if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) { diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index 2afa61b78..1e31fbcad 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -202,9 +202,9 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { * @return array */ public function process_payment( $order_id ) { - $wc_order = wc_get_order( $order_id ); - // phpcs:disable WordPress.Security.NonceVerification.Recommended - $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); + $wc_order = wc_get_order( $order_id ); + // phpcs:disable WordPress.Security.NonceVerification.Missing + $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); $pay_for_order = wc_clean( wp_unslash( $_GET['pay_for_order'] ?? '' ) ); if ( 'true' === $pay_for_order ) { if ( ! $this->checkout_helper->validate_birth_date( $birth_date ) ) { @@ -216,7 +216,7 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { } $phone_number = wc_clean( wp_unslash( $_POST['billing_phone'] ?? '' ) ); - // phpcs:enable WordPress.Security.NonceVerification.Recommended + // phpcs:enable WordPress.Security.NonceVerification.Missing if ( $phone_number ) { $wc_order->set_billing_phone( $phone_number ); $wc_order->save(); diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php index 439ce3ee1..cbbdbf40a 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -54,7 +54,7 @@ class PayUponInvoiceHelper { return false; } - // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing $billing_country = wc_clean( wp_unslash( $_POST['country'] ?? '' ) ); if ( $billing_country && 'DE' !== $billing_country ) { return false; From 2cc0f2378c716d62eb5d42c16bf89fc3621a2705 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 20 Oct 2022 15:48:29 +0200 Subject: [PATCH 07/26] Check nonces --- modules/ppcp-subscription/src/SubscriptionModule.php | 2 +- modules/ppcp-vaulting/src/CustomerApprovalListener.php | 1 + modules/ppcp-vaulting/src/VaultedCreditCardHandler.php | 1 + .../src/Gateway/PayUponInvoice/FraudNetSessionId.php | 1 + .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 3 ++- .../src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php | 3 ++- .../src/Gateway/PayUponInvoice/PaymentSourceFactory.php | 3 ++- 7 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php index 75dc799d5..bd569af50 100644 --- a/modules/ppcp-subscription/src/SubscriptionModule.php +++ b/modules/ppcp-subscription/src/SubscriptionModule.php @@ -100,7 +100,7 @@ class SubscriptionModule implements ModuleInterface { add_filter( 'ppcp_create_order_request_body_data', function( array $data ) use ( $c ) { - // phpcs:ignore WordPress.Security.NonceVerification.Missing) + // phpcs:ignore WordPress.Security.NonceVerification.Missing $wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) ); if ( $wc_order_action === 'wcs_process_renewal' diff --git a/modules/ppcp-vaulting/src/CustomerApprovalListener.php b/modules/ppcp-vaulting/src/CustomerApprovalListener.php index 4ce88691f..009fb451c 100644 --- a/modules/ppcp-vaulting/src/CustomerApprovalListener.php +++ b/modules/ppcp-vaulting/src/CustomerApprovalListener.php @@ -53,6 +53,7 @@ class CustomerApprovalListener { * @return void */ public function listen(): void { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $token = wc_clean( wp_unslash( $_GET['approval_token_id'] ?? '' ) ); if ( ! $token ) { return; diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php index ec01fc392..3cd653db1 100644 --- a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -144,6 +144,7 @@ class VaultedCreditCardHandler { WC_Order $wc_order ): WC_Order { if ( + // phpcs:ignore WordPress.Security.NonceVerification.Missing isset( $_POST['woocommerce_change_payment'] ) && $this->subscription_helper->has_subscription( $wc_order->get_id() ) && $this->subscription_helper->is_subscription_change_payment() diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php index 3bc5839ba..2a13a553b 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/FraudNetSessionId.php @@ -33,6 +33,7 @@ class FraudNetSessionId { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing $pui_pay_for_order_session_id = wc_clean( wp_unslash( $_POST['pui_pay_for_order_session_id'] ?? '' ) ); if ( $pui_pay_for_order_session_id && '' !== $pui_pay_for_order_session_id ) { return $pui_pay_for_order_session_id; diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index a4f89678c..63c4eee9b 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -425,6 +425,7 @@ class PayUponInvoice { $errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) ); } + // phpcs:ignore WordPress.Security.NonceVerification.Missing $national_number = wc_clean( wp_unslash( $_POST['billing_phone'] ?? 0 ) ); if ( ! $national_number ) { $errors->add( 'validation', __( 'Phone field cannot be empty.', 'woocommerce-paypal-payments' ) ); @@ -530,7 +531,7 @@ class PayUponInvoice { 'add_meta_boxes', function( string $post_type ) { if ( $post_type === 'shop_order' ) { - // phpcs:ignore WordPress.Security.NonceVerification.Missing + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = wc_clean( wp_unslash( $_GET['post'] ?? 0 ) ); $order = wc_get_order( $post_id ); if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) { diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index 1e31fbcad..128c99424 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -204,7 +204,8 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); // phpcs:disable WordPress.Security.NonceVerification.Missing - $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); + $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); + // phpcs:disable WordPress.Security.NonceVerification.Recommended $pay_for_order = wc_clean( wp_unslash( $_GET['pay_for_order'] ?? '' ) ); if ( 'true' === $pay_for_order ) { if ( ! $this->checkout_helper->validate_birth_date( $birth_date ) ) { diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php index e0047208d..960a82f87 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php @@ -24,7 +24,8 @@ class PaymentSourceFactory { * @return PaymentSource */ public function from_wc_order( WC_Order $order, string $birth_date ) { - $address = $order->get_address(); + $address = $order->get_address(); + // phpcs:ignore WordPress.Security.NonceVerification.Missing $phone = wc_clean( wp_unslash( $_POST['billing_phone'] ?? '' ) ) ?? $address['phone'] ?? ''; $phone_country_code = WC()->countries->get_country_calling_code( $address['country'] ); $phone_country_code = is_array( $phone_country_code ) && ! empty( $phone_country_code ) ? $phone_country_code[0] : $phone_country_code; From f66c2322fdfe31e7d4bc770aacf5a9bcbb894ebe Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 21 Oct 2022 12:25:22 +0200 Subject: [PATCH 08/26] Fix phpunit --- tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php | 4 +++- tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php | 4 +++- .../Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php | 1 + tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php index 379b00f00..044045c7c 100644 --- a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php +++ b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php @@ -70,7 +70,7 @@ class VaultedCreditCardHandlerTest extends TestCase public function testHandlePaymentChangingPayment() { - when('filter_input')->justReturn(1); + $_POST['woocommerce_change_payment'] = 1; $wcOrder = Mockery::mock(\WC_Order::class); $wcOrder->shouldReceive('get_id')->andReturn(1); $this->subscriptionHelper->shouldReceive('has_subscription')->andReturn(true); @@ -85,6 +85,8 @@ class VaultedCreditCardHandlerTest extends TestCase public function testHandlePayment() { + $_POST['woocommerce_change_payment'] = null; + $wcOrder = Mockery::mock(\WC_Order::class); $wcOrder->shouldReceive('get_id')->andReturn(1); $wcOrder->shouldReceive('get_customer_id')->andReturn(1); diff --git a/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php index e6f9c5697..0dc0b5e4d 100644 --- a/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php @@ -55,6 +55,8 @@ class CreditCardGatewayTest extends TestCase $this->config->shouldReceive('has')->andReturn(true); $this->config->shouldReceive('get')->andReturn(''); + when('wc_clean')->returnArg(); + $this->testee = new CreditCardGateway( $this->settingsRenderer, $this->orderProcessor, @@ -94,7 +96,7 @@ class CreditCardGatewayTest extends TestCase when('wc_get_order')->justReturn($wc_order); $savedCreditCard = 'abc123'; - when('filter_input')->justReturn($savedCreditCard); + $_POST['saved_credit_card'] = $savedCreditCard; $this->vaultedCreditCardHandler ->shouldReceive('handle_payment') diff --git a/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php index add91a083..6ae3fba00 100644 --- a/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php @@ -43,6 +43,7 @@ class PayUponInvoiceGatewayTest extends TestCase $this->checkout_helper = Mockery::mock(CheckoutHelper::class); $this->setInitStubs(); + when('wc_clean')->returnArg(); $this->testee = new PayUponInvoiceGateway( $this->order_endpoint, diff --git a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php index ec901bafa..b62f013d7 100644 --- a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php @@ -46,6 +46,7 @@ class WcGatewayTest extends TestCase expect('is_admin')->andReturnUsing(function () { return $this->isAdmin; }); + when('wc_clean')->returnArg(); $this->settingsRenderer = Mockery::mock(SettingsRenderer::class); $this->orderProcessor = Mockery::mock(OrderProcessor::class); From d509a8080220e4f04be3a38309cee72c281301bf Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 21 Oct 2022 12:45:22 +0200 Subject: [PATCH 09/26] Fix psalm --- modules/ppcp-vaulting/src/CustomerApprovalListener.php | 2 +- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 6 +++--- .../src/Gateway/PayUponInvoice/PaymentSourceFactory.php | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-vaulting/src/CustomerApprovalListener.php b/modules/ppcp-vaulting/src/CustomerApprovalListener.php index 009fb451c..19ea6ace7 100644 --- a/modules/ppcp-vaulting/src/CustomerApprovalListener.php +++ b/modules/ppcp-vaulting/src/CustomerApprovalListener.php @@ -55,7 +55,7 @@ class CustomerApprovalListener { public function listen(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $token = wc_clean( wp_unslash( $_GET['approval_token_id'] ?? '' ) ); - if ( ! $token ) { + if ( ! $token || is_array($token) ) { return; } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 63c4eee9b..fe152e0bd 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -421,12 +421,12 @@ class PayUponInvoice { // phpcs:ignore WordPress.Security.NonceVerification.Missing $birth_date = wc_clean( wp_unslash( $_POST['billing_birth_date'] ?? '' ) ); - if ( ( $birth_date && ! $this->checkout_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { + if ( ( $birth_date && is_string( $birth_date ) && ! $this->checkout_helper->validate_birth_date( $birth_date ) ) || $birth_date === '' ) { $errors->add( 'validation', __( 'Invalid birth date.', 'woocommerce-paypal-payments' ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing - $national_number = wc_clean( wp_unslash( $_POST['billing_phone'] ?? 0 ) ); + $national_number = wc_clean( wp_unslash( $_POST['billing_phone'] ?? '' ) ); if ( ! $national_number ) { $errors->add( 'validation', __( 'Phone field cannot be empty.', 'woocommerce-paypal-payments' ) ); } @@ -532,7 +532,7 @@ class PayUponInvoice { function( string $post_type ) { if ( $post_type === 'shop_order' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $post_id = wc_clean( wp_unslash( $_GET['post'] ?? 0 ) ); + $post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) ); $order = wc_get_order( $post_id ); if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) { $instructions = $order->get_meta( 'ppcp_ratepay_payment_instructions_payment_reference' ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php index 960a82f87..0388132c5 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PaymentSourceFactory.php @@ -26,7 +26,10 @@ class PaymentSourceFactory { public function from_wc_order( WC_Order $order, string $birth_date ) { $address = $order->get_address(); // phpcs:ignore WordPress.Security.NonceVerification.Missing - $phone = wc_clean( wp_unslash( $_POST['billing_phone'] ?? '' ) ) ?? $address['phone'] ?? ''; + $phone = wc_clean( wp_unslash( $_POST['billing_phone'] ?? '' ) ); + if ( ! $phone ) { + $phone = $address['phone'] ?? ''; + } $phone_country_code = WC()->countries->get_country_calling_code( $address['country'] ); $phone_country_code = is_array( $phone_country_code ) && ! empty( $phone_country_code ) ? $phone_country_code[0] : $phone_country_code; if ( is_string( $phone_country_code ) && '' !== $phone_country_code ) { From 8b30b70a44c5c02ff2772e6f6f6f869fb68f4ce1 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Fri, 21 Oct 2022 12:48:34 +0200 Subject: [PATCH 10/26] Fix phpcs --- modules/ppcp-vaulting/src/CustomerApprovalListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-vaulting/src/CustomerApprovalListener.php b/modules/ppcp-vaulting/src/CustomerApprovalListener.php index 19ea6ace7..1b922511b 100644 --- a/modules/ppcp-vaulting/src/CustomerApprovalListener.php +++ b/modules/ppcp-vaulting/src/CustomerApprovalListener.php @@ -55,7 +55,7 @@ class CustomerApprovalListener { public function listen(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $token = wc_clean( wp_unslash( $_GET['approval_token_id'] ?? '' ) ); - if ( ! $token || is_array($token) ) { + if ( ! $token || is_array( $token ) ) { return; } From ab618b200ec39e33a020d45d9d59a3f758aeceac Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 19 Oct 2022 11:38:49 +0300 Subject: [PATCH 11/26] Add button preview in settings --- .../resources/js/modules/Renderer/Renderer.js | 4 +- .../ppcp-onboarding/resources/js/settings.js | 4 + modules/ppcp-wc-gateway/package.json | 1 + .../resources/css/gateway-settings.scss | 17 +++ .../resources/js/gateway-settings.js | 134 ++++++++++++++++++ .../resources/js/helper/debounce.js | 9 ++ modules/ppcp-wc-gateway/services.php | 44 ++++++ .../src/Assets/SettingsPageAssets.php | 53 ++++++- .../ppcp-wc-gateway/src/WCGatewayModule.php | 5 +- modules/ppcp-wc-gateway/webpack.config.js | 14 ++ modules/ppcp-wc-gateway/yarn.lock | 12 ++ package-lock.json | 14 -- .../Assets/SettingsPagesAssetsTest.php | 2 +- 13 files changed, 294 insertions(+), 19 deletions(-) create mode 100644 modules/ppcp-wc-gateway/resources/css/gateway-settings.scss create mode 100644 modules/ppcp-wc-gateway/resources/js/helper/debounce.js delete mode 100644 package-lock.json diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 65e96c0c8..f879f4a1b 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -45,7 +45,9 @@ class Renderer { } } - this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig); + if (this.creditCardRenderer) { + this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig); + } for (const [fundingSource, data] of Object.entries(enabledSeparateGateways)) { this.renderButtons( diff --git a/modules/ppcp-onboarding/resources/js/settings.js b/modules/ppcp-onboarding/resources/js/settings.js index 9440319ce..00e35b1e6 100644 --- a/modules/ppcp-onboarding/resources/js/settings.js +++ b/modules/ppcp-onboarding/resources/js/settings.js @@ -233,6 +233,7 @@ document.addEventListener( '#field-button_label', '#field-button_color', '#field-button_shape', + '#field-button_preview', ] ); @@ -256,6 +257,7 @@ document.addEventListener( '#field-button_product_label', '#field-button_product_color', '#field-button_product_shape', + '#field-button_product_preview', ] ); @@ -280,6 +282,7 @@ document.addEventListener( '#field-button_mini-cart_color', '#field-button_mini-cart_shape', '#field-button_mini-cart_height', + '#field-button_mini-cart_preview', ] ); @@ -291,6 +294,7 @@ document.addEventListener( '#field-button_cart_label', '#field-button_cart_color', '#field-button_cart_shape', + '#field-button_cart_preview', ] ); groupToggle( diff --git a/modules/ppcp-wc-gateway/package.json b/modules/ppcp-wc-gateway/package.json index 36265e6f1..b37863041 100644 --- a/modules/ppcp-wc-gateway/package.json +++ b/modules/ppcp-wc-gateway/package.json @@ -11,6 +11,7 @@ "Edge >= 14" ], "dependencies": { + "@paypal/paypal-js": "^5.1.1", "core-js": "^3.25.0" }, "devDependencies": { diff --git a/modules/ppcp-wc-gateway/resources/css/gateway-settings.scss b/modules/ppcp-wc-gateway/resources/css/gateway-settings.scss new file mode 100644 index 000000000..832642eb0 --- /dev/null +++ b/modules/ppcp-wc-gateway/resources/css/gateway-settings.scss @@ -0,0 +1,17 @@ +.ppcp-button-preview { + width: 350px; + padding: 15px; + border: 1px solid lightgray; + border-radius: 15px; + box-shadow: 0 2px 10px 1px #ddd; + + h4 { + margin-top: 0; + } + + @media (min-width: 1200px) { + position: absolute; + left: 800px; + margin-top: -400px; + } +} diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index cf9ee45bd..7f502628c 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -1,3 +1,7 @@ +import { loadScript } from "@paypal/paypal-js"; +import {debounce} from "./helper/debounce"; +import Renderer from '../../../ppcp-button/resources/js/modules/Renderer/Renderer' + ;document.addEventListener( 'DOMContentLoaded', () => { @@ -65,6 +69,70 @@ atLeastOneChecked(vaultingCheckboxes) ? disablePayLater() : enablePayLater() } + const form = jQuery('#mainform'); + + function createButtonPreview(settingsCallback) { + const render = (settings) => { + const wrapper = document.querySelector(settings.button.wrapper); + wrapper.innerHTML = ''; + + const renderer = new Renderer(null, settings, (data, actions) => actions.reject(), null); + + try { + renderer.render({}); + } catch (err) { + console.error(err); + } + }; + + let oldSettings = settingsCallback(); + + form.on('change', ':input', debounce(() => { + const newSettings = settingsCallback(); + if (JSON.stringify(oldSettings) === JSON.stringify(newSettings)) { + return; + } + + render(newSettings); + + oldSettings = newSettings; + }, 300)); + + jQuery(document).on('ppcp_paypal_script_loaded', () => { + oldSettings = settingsCallback(); + + render(oldSettings); + }); + + render(oldSettings); + } + + function getPaypalScriptSettings() { + const disabledSources = jQuery('[name="ppcp[disable_funding][]"]').val(); + const settings = { + 'client-id': PayPalCommerceGatewaySettings.client_id, + 'currency': PayPalCommerceGatewaySettings.currency, + 'integration-date': PayPalCommerceGatewaySettings.integration_date, + 'components': ['buttons', 'funding-eligibility', 'messages'], + 'enable-funding': ['venmo'], + 'buyer-country': PayPalCommerceGatewaySettings.country, + }; + if (disabledSources.length) { + settings['disable-funding'] = disabledSources; + } + return settings; + } + + function loadPaypalScript(settings, onLoaded = () => {}) { + loadScript(JSON.parse(JSON.stringify(settings))) // clone the object to prevent modification + .then(paypal => { + document.dispatchEvent(new CustomEvent('ppcp_paypal_script_loaded')); + + onLoaded(paypal); + }) + .catch((error) => console.error('failed to load the PayPal JS SDK script', error)); + } + disableAll( disabledCheckboxes ) togglePayLater() @@ -73,5 +141,71 @@ if(PayPalCommerceGatewaySettings.is_subscriptions_plugin_active !== '1') { document.getElementById('field-subscription_behavior_when_vault_fails').style.display = 'none'; } + + let oldScriptSettings = getPaypalScriptSettings(); + + form.on('change', ':input', debounce(() => { + const newSettings = getPaypalScriptSettings(); + if (JSON.stringify(oldScriptSettings) === JSON.stringify(newSettings)) { + return; + } + + loadPaypalScript(newSettings); + + oldScriptSettings = newSettings; + }, 1000)); + + function getButtonSettings(wrapperSelector, fields) { + const layout = jQuery(fields['layout']).val(); + const style = { + 'color': jQuery(fields['color']).val(), + 'shape': jQuery(fields['shape']).val(), + 'label': jQuery(fields['label']).val(), + 'tagline': layout === 'horizontal' && jQuery(fields['tagline']).is(':checked'), + 'layout': layout, + }; + if ('height' in fields) { + style['height'] = parseInt(jQuery(fields['height']).val()); + } + return { + 'button': { + 'wrapper': wrapperSelector, + 'style': style, + }, + 'separate_buttons': {}, + }; + } + + loadPaypalScript(oldScriptSettings, () => { + createButtonPreview(() => getButtonSettings('#ppcpCheckoutButtonPreview', { + 'color': '#ppcp-button_color', + 'shape': '#ppcp-button_shape', + 'label': '#ppcp-button_label', + 'tagline': '#ppcp-button_tagline', + 'layout': '#ppcp-button_layout', + })); + createButtonPreview(() => getButtonSettings('#ppcpProductButtonPreview', { + 'color': '#ppcp-button_product_color', + 'shape': '#ppcp-button_product_shape', + 'label': '#ppcp-button_product_label', + 'tagline': '#ppcp-button_product_tagline', + 'layout': '#ppcp-button_product_layout', + })); + createButtonPreview(() => getButtonSettings('#ppcpCartButtonPreview', { + 'color': '#ppcp-button_cart_color', + 'shape': '#ppcp-button_cart_shape', + 'label': '#ppcp-button_cart_label', + 'tagline': '#ppcp-button_cart_tagline', + 'layout': '#ppcp-button_cart_layout', + })); + createButtonPreview(() => getButtonSettings('#ppcpMiniCartButtonPreview', { + 'color': '#ppcp-button_mini-cart_color', + 'shape': '#ppcp-button_mini-cart_shape', + 'label': '#ppcp-button_mini-cart_label', + 'tagline': '#ppcp-button_mini-cart_tagline', + 'layout': '#ppcp-button_mini-cart_layout', + 'height': '#ppcp-button_mini-cart_height', + })); + }); } ); diff --git a/modules/ppcp-wc-gateway/resources/js/helper/debounce.js b/modules/ppcp-wc-gateway/resources/js/helper/debounce.js new file mode 100644 index 000000000..68a34e771 --- /dev/null +++ b/modules/ppcp-wc-gateway/resources/js/helper/debounce.js @@ -0,0 +1,9 @@ +export const debounce = (callback, delayMs) => { + let timeoutId = null; + return (...args) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + callback.apply(null, args); + }, delayMs); + }; +}; diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 2df151099..386d3b613 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -401,6 +401,14 @@ return array( $onboarding_options_renderer = $container->get( 'onboarding.render-options' ); assert( $onboarding_options_renderer instanceof OnboardingOptionsRenderer ); + $render_preview_element = function ( string $id ): string { + return ' +

+

' . __( 'Preview', 'woocommerce-paypal-payments' ) . '

+
+
'; + }; + $fields = array( 'checkout_settings_heading' => array( 'heading' => __( 'Standard Payments Settings', 'woocommerce-paypal-payments' ), @@ -802,6 +810,15 @@ return array( 'requirements' => array(), 'gateway' => 'paypal', ), + 'button_preview' => array( + 'type' => 'ppcp-text', + 'text' => $render_preview_element( 'ppcpCheckoutButtonPreview' ), + 'screens' => array( + State::STATE_ONBOARDED, + ), + 'requirements' => array(), + 'gateway' => 'paypal', + ), 'message_heading' => array( 'heading' => __( 'Pay Later on Checkout', 'woocommerce-paypal-payments' ), 'type' => 'ppcp-heading', @@ -1107,6 +1124,15 @@ return array( 'requirements' => array(), 'gateway' => 'paypal', ), + 'button_product_preview' => array( + 'type' => 'ppcp-text', + 'text' => $render_preview_element( 'ppcpProductButtonPreview' ), + 'screens' => array( + State::STATE_ONBOARDED, + ), + 'requirements' => array(), + 'gateway' => 'paypal', + ), 'message_product_heading' => array( 'heading' => __( 'Pay Later on Single Product Page', 'woocommerce-paypal-payments' ), @@ -1413,6 +1439,15 @@ return array( 'requirements' => array(), 'gateway' => 'paypal', ), + 'button_cart_preview' => array( + 'type' => 'ppcp-text', + 'text' => $render_preview_element( 'ppcpCartButtonPreview' ), + 'screens' => array( + State::STATE_ONBOARDED, + ), + 'requirements' => array(), + 'gateway' => 'paypal', + ), 'message_cart_heading' => array( 'heading' => __( 'Pay Later on Cart', 'woocommerce-paypal-payments' ), @@ -1732,6 +1767,15 @@ return array( 'requirements' => array(), 'gateway' => 'paypal', ), + 'button_mini-cart_preview' => array( + 'type' => 'ppcp-text', + 'text' => $render_preview_element( 'ppcpMiniCartButtonPreview' ), + 'screens' => array( + State::STATE_ONBOARDED, + ), + 'requirements' => array(), + 'gateway' => 'paypal', + ), 'disable_cards' => array( 'title' => __( 'Disable specific credit cards', 'woocommerce-paypal-payments' ), diff --git a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php index 5926580ff..2fd00b4a5 100644 --- a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php +++ b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php @@ -37,17 +37,51 @@ class SettingsPageAssets { */ protected $subscription_helper; + /** + * The PayPal SDK client ID. + * + * @var string + */ + private $client_id; + + /** + * 3-letter currency code of the shop. + * + * @var string + */ + private $currency; + + /** + * 2-letter country code of the shop. + * + * @var string + */ + private $country; + /** * Assets constructor. * * @param string $module_url The url of this module. * @param string $version The assets version. * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param string $client_id The PayPal SDK client ID. + * @param string $currency 3-letter currency code of the shop. + * @param string $country 2-letter country code of the shop. */ - public function __construct( string $module_url, string $version, SubscriptionHelper $subscription_helper ) { + public function __construct( + string $module_url, + string $version, + SubscriptionHelper $subscription_helper, + string $client_id, + string $currency, + string $country + ) { $this->module_url = $module_url; $this->version = $version; $this->subscription_helper = $subscription_helper; + $this->client_id = $client_id; + $this->currency = $currency; + $this->country = $country; } /** @@ -98,6 +132,13 @@ class SettingsPageAssets { * Register assets for admin pages. */ private function register_admin_assets() { + wp_enqueue_style( + 'ppcp-gateway-settings', + trailingslashit( $this->module_url ) . 'assets/css/gateway-settings.css', + array(), + $this->version + ); + wp_enqueue_script( 'ppcp-gateway-settings', trailingslashit( $this->module_url ) . 'assets/js/gateway-settings.js', @@ -106,12 +147,20 @@ class SettingsPageAssets { true ); - // Intent is configured with Authorize and Capture Virtual-Only Orders is not set. + /** + * Psalm cannot find it for some reason. + * + * @psalm-suppress UndefinedConstant + */ wp_localize_script( 'ppcp-gateway-settings', 'PayPalCommerceGatewaySettings', array( 'is_subscriptions_plugin_active' => $this->subscription_helper->plugin_is_active(), + 'client_id' => $this->client_id, + 'currency' => $this->currency, + 'country' => $this->country, + 'integration_date' => PAYPAL_INTEGRATION_DATE, ) ); } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 7fb9c0b3c..18a39b57e 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -157,7 +157,10 @@ class WCGatewayModule implements ModuleInterface { $assets = new SettingsPageAssets( $c->get( 'wcgateway.url' ), $c->get( 'ppcp.asset-version' ), - $c->get( 'subscription.helper' ) + $c->get( 'subscription.helper' ), + $c->get( 'button.client_id' ), + $c->get( 'api.shop.currency' ), + $c->get( 'api.shop.country' ) ); $assets->register_assets(); } diff --git a/modules/ppcp-wc-gateway/webpack.config.js b/modules/ppcp-wc-gateway/webpack.config.js index 91a99abb7..009952fa6 100644 --- a/modules/ppcp-wc-gateway/webpack.config.js +++ b/modules/ppcp-wc-gateway/webpack.config.js @@ -9,6 +9,7 @@ module.exports = { 'gateway-settings': path.resolve('./resources/js/gateway-settings.js'), 'pay-upon-invoice': path.resolve('./resources/js/pay-upon-invoice.js'), 'oxxo': path.resolve('./resources/js/oxxo.js'), + 'gateway-settings-style': path.resolve('./resources/css/gateway-settings.scss'), }, output: { path: path.resolve(__dirname, 'assets/'), @@ -19,6 +20,19 @@ module.exports = { test: /\.js?$/, exclude: /node_modules/, loader: 'babel-loader', + }, + { + test: /\.scss$/, + exclude: /node_modules/, + use: [ + { + loader: 'file-loader', + options: { + name: 'css/[name].css', + } + }, + {loader:'sass-loader'} + ] }] } }; diff --git a/modules/ppcp-wc-gateway/yarn.lock b/modules/ppcp-wc-gateway/yarn.lock index db1f0b716..9c070466e 100644 --- a/modules/ppcp-wc-gateway/yarn.lock +++ b/modules/ppcp-wc-gateway/yarn.lock @@ -1005,6 +1005,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@paypal/paypal-js@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-5.1.1.tgz#81ab1f78dd2001061a2472f561d20df687a1d295" + integrity sha512-MMQ8TA048gTB43pzEOMzod8WY8hfzy+ahd7w29LtMvXduqzp7/29WxrTlsy4k6ARG6WGJ/uGqpc4+la4UZEQgw== + dependencies: + promise-polyfill "^8.2.3" + "@types/eslint-scope@^3.7.3": version "3.7.4" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" @@ -1933,6 +1940,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +promise-polyfill@^8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.3.tgz#2edc7e4b81aff781c88a0d577e5fe9da822107c6" + integrity sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg== + punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index b07da92da..000000000 --- a/package-lock.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "woocommerce-paypal-payments", - "version": "1.7.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "woocommerce-paypal-payments", - "version": "1.7.0", - "license": "GPL-2.0", - "devDependencies": {} - } - } -} diff --git a/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php b/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php index 23f32d100..3f94739bf 100644 --- a/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php +++ b/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php @@ -16,7 +16,7 @@ class SettingsPagesAssetsTest extends TestCase $modulePath = '/var/www/html/wp-content/plugins/woocommerce-paypal-payments/modules/ppcp-wc-gateway'; $subscriptionsHelper = Mockery::mock(SubscriptionHelper::class); - $testee = new SettingsPageAssets($moduleUrl, $modulePath, $subscriptionsHelper); + $testee = new SettingsPageAssets($moduleUrl, $modulePath, $subscriptionsHelper, '123', 'EUR', 'DE'); when('is_admin') ->justReturn(true); From 5a5933ce91bceede6798e6792da2125fe320d87d Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Thu, 20 Oct 2022 14:20:11 -0300 Subject: [PATCH 12/26] Fix button positioning for products with one page checkout enabled --- modules/ppcp-button/src/Assets/SmartButton.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 89c15b90b..44f37ebfe 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -370,6 +370,7 @@ class SmartButton implements SmartButtonInterface { if ( ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) && ! $not_enabled_on_product_page + && ! is_checkout() ) { add_action( $this->single_product_renderer_hook(), @@ -420,6 +421,7 @@ class SmartButton implements SmartButtonInterface { // TODO: it seems like there is no easy way to properly handle vaulted PayPal free trial, // so disable the buttons for now everywhere except checkout for free trial. && ! $this->is_free_trial_product() + && ! is_checkout() ) { add_action( $this->single_product_renderer_hook(), From 37d5cae9246e42b677bc4ed6021c8c820440c47b Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 24 Oct 2022 12:20:45 +0200 Subject: [PATCH 13/26] Add WC HPOS support --- woocommerce-paypal-payments.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 71be57a0e..a9aa6b7bc 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -183,6 +183,15 @@ define( 'PPCP_FLAG_SEPARATE_APM_BUTTONS', apply_filters( 'woocommerce_paypal_pay 2 ); + add_action( + 'before_woocommerce_init', + function() { + if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) ) { + \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); + } + } + ); + /** * Check if WooCommerce is active. * From 5e42704dbc637a0aec7ed6c8d057d23a469a5da1 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 24 Oct 2022 12:35:31 +0200 Subject: [PATCH 14/26] Replace wp update post meta to wc update meta --- .../src/Endpoint/OrderTrackingEndpoint.php | 12 ++++++++++-- modules/ppcp-subscription/src/SubscriptionModule.php | 4 ++-- .../ppcp-vaulting/src/VaultedCreditCardHandler.php | 4 +++- .../src/Gateway/CardButtonGateway.php | 3 ++- .../ppcp-wc-gateway/src/Gateway/PayPalGateway.php | 3 ++- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-order-tracking/src/Endpoint/OrderTrackingEndpoint.php b/modules/ppcp-order-tracking/src/Endpoint/OrderTrackingEndpoint.php index b09fcdeae..fb281ecef 100644 --- a/modules/ppcp-order-tracking/src/Endpoint/OrderTrackingEndpoint.php +++ b/modules/ppcp-order-tracking/src/Endpoint/OrderTrackingEndpoint.php @@ -171,7 +171,11 @@ class OrderTrackingEndpoint { throw $error; } - update_post_meta( $order_id, '_ppcp_paypal_tracking_number', $data['tracking_number'] ?? '' ); + $wc_order = wc_get_order( $order_id ); + if ( is_a( $wc_order, WC_Order::class ) ) { + $wc_order->update_meta_data( '_ppcp_paypal_tracking_number', $data['tracking_number'] ?? '' ); + $wc_order->save(); + } do_action( 'woocommerce_paypal_payments_after_tracking_is_added', $order_id, $response ); } @@ -300,7 +304,11 @@ class OrderTrackingEndpoint { throw $error; } - update_post_meta( $order_id, '_ppcp_paypal_tracking_number', $data['tracking_number'] ?? '' ); + $wc_order = wc_get_order( $order_id ); + if ( is_a( $wc_order, WC_Order::class ) ) { + $wc_order->update_meta_data( '_ppcp_paypal_tracking_number', $data['tracking_number'] ?? '' ); + $wc_order->save(); + } do_action( 'woocommerce_paypal_payments_after_tracking_is_updated', $order_id, $response ); } diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php index 6f152307b..89c2eec8b 100644 --- a/modules/ppcp-subscription/src/SubscriptionModule.php +++ b/modules/ppcp-subscription/src/SubscriptionModule.php @@ -175,9 +175,9 @@ class SubscriptionModule implements ModuleInterface { try { $tokens = $payment_token_repository->all_for_user_id( $subscription->get_customer_id() ); if ( $tokens ) { - $subscription_id = $subscription->get_id(); $latest_token_id = end( $tokens )->id() ? end( $tokens )->id() : ''; - update_post_meta( $subscription_id, 'payment_token_id', $latest_token_id, true ); + $subscription->update_meta_data( 'payment_token_id', $latest_token_id ); + $subscription->save(); } } catch ( RuntimeException $error ) { $message = sprintf( diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php index 172837633..7fd16c5bb 100644 --- a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -151,7 +151,9 @@ class VaultedCreditCardHandler { && $this->subscription_helper->is_subscription_change_payment() && $saved_credit_card ) { - update_post_meta( $wc_order->get_id(), 'payment_token_id', $saved_credit_card ); + $wc_order->update_meta_data( 'payment_token_id', $saved_credit_card ); + $wc_order->save(); + return $wc_order; } diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php index 735093056..bc2a8d553 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -277,7 +277,8 @@ class CardButtonGateway extends \WC_Payment_Gateway { if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); if ( $saved_paypal_payment ) { - update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); + $wc_order->update_meta_data( 'payment_token_id', $saved_paypal_payment ); + $wc_order->save(); return $this->handle_payment_success( $wc_order ); } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 21d8c9b7a..9c6a9ad97 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -425,7 +425,8 @@ class PayPalGateway extends \WC_Payment_Gateway { if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); if ( $saved_paypal_payment ) { - update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); + $wc_order->update_meta_data( 'payment_token_id', $saved_paypal_payment ); + $wc_order->save(); return $this->handle_payment_success( $wc_order ); } From 6d2c08b46acd19b5efba59ee2a47106fafd46df9 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Mon, 24 Oct 2022 12:44:34 +0200 Subject: [PATCH 15/26] Fix phpunit --- tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php index 379b00f00..11065e83e 100644 --- a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php +++ b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php @@ -73,9 +73,12 @@ class VaultedCreditCardHandlerTest extends TestCase when('filter_input')->justReturn(1); $wcOrder = Mockery::mock(\WC_Order::class); $wcOrder->shouldReceive('get_id')->andReturn(1); + $wcOrder->shouldReceive('update_meta_data') + ->with('payment_token_id', 'abc123') + ->andReturn(1); + $wcOrder->shouldReceive('save')->andReturn(1); $this->subscriptionHelper->shouldReceive('has_subscription')->andReturn(true); $this->subscriptionHelper->shouldReceive('is_subscription_change_payment')->andReturn(true); - expect('update_post_meta')->with(1, 'payment_token_id', 'abc123'); $customer = Mockery::mock(WC_Customer::class); From 7b60f339aa66230eb86fdebfd4d2f598ef528809 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 25 Oct 2022 09:37:08 +0300 Subject: [PATCH 16/26] Handle synced sub without upfront payment like free trial --- .psalm/wcs.php | 507 ++++++++++++++++++ .../src/FreeTrialHandlerTrait.php | 34 +- 2 files changed, 526 insertions(+), 15 deletions(-) diff --git a/.psalm/wcs.php b/.psalm/wcs.php index 16a62a9e3..2106378e2 100644 --- a/.psalm/wcs.php +++ b/.psalm/wcs.php @@ -904,6 +904,513 @@ class WC_Subscriptions_Admin public static $option_prefix = 'woocommerce_subscriptions'; } +/** + * Allow for payment dates to be synchronised to a specific day of the week, month or year. + */ +class WC_Subscriptions_Synchroniser { + + public static $setting_id; + public static $setting_id_proration; + public static $setting_id_days_no_fee; + + public static $post_meta_key = '_subscription_payment_sync_date'; + public static $post_meta_key_day = '_subscription_payment_sync_date_day'; + public static $post_meta_key_month = '_subscription_payment_sync_date_month'; + + public static $sync_field_label; + public static $sync_description; + public static $sync_description_year; + + public static $billing_period_ranges; + + /** + * Bootstraps the class and hooks required actions & filters. + */ + public static function init() + { + } + + /** + * Set default value of 'no' for our options. + * + * This only sets the default + * + * @param mixed $default The default value for the option. + * @param string $option The option name. + * @param bool $passed_default Whether get_option() was passed a default value. + * + * @return mixed The default option value. + */ + public static function option_default( $default, $option, $passed_default = null ) + { + } + + /** + * Sanitize our options when they are saved in the admin area. + * + * @param mixed $value The value being saved. + * @param array $option The option data array. + * + * @return mixed The sanitized option value. + */ + public static function sanitize_option( $value, $option ) + { + } + + /** + * Check if payment syncing is enabled on the store. + * + * @since 1.5 + */ + public static function is_syncing_enabled() + { + } + + /** + * Check if payments can be prorated on the store. + * + * @since 1.5 + */ + public static function is_sync_proration_enabled() + { + } + + /** + * Add sync settings to the Subscription's settings page. + * + * @since 1.5 + */ + public static function add_settings( $settings ) + { + } + + /** + * Add the sync setting fields to the Edit Product screen + * + * @since 1.5 + */ + public static function subscription_product_fields() + { + } + + /** + * Add the sync setting fields to the variation section of the Edit Product screen + * + * @since 1.5 + */ + public static function variable_subscription_product_fields( $loop, $variation_data, $variation ) + { + } + + /** + * Save sync options when a subscription product is saved + * + * @since 1.5 + */ + public static function save_subscription_meta( $post_id ) + { + } + + /** + * Save sync options when a variable subscription product is saved + * + * @since 1.5 + */ + public static function process_product_meta_variable_subscription( $post_id ) + { + } + + /** + * Save sync options when a variable subscription product is saved + * + * @since 1.5 + */ + public static function save_product_variation( $variation_id, $index ) + { + } + + /** + * Add translated syncing options for our client side script + * + * @since 1.5 + */ + public static function admin_script_parameters( $script_parameters ) + { + } + + /** + * Determine whether a product, specified with $product, needs to have its first payment processed on a + * specific day (instead of at the time of sign-up). + * + * @return (bool) True is the product's first payment will be synced to a certain day. + * @since 1.5 + */ + public static function is_product_synced( $product ) + { + } + + /** + * Determine whether a product, specified with $product, should have its first payment processed on a + * at the time of sign-up but prorated to the sync day. + * + * @since 1.5.10 + * + * @param WC_Product $product + * + * @return bool + */ + public static function is_product_prorated( $product ) + { + } + + /** + * Determine whether the payment for a subscription should be the full price upfront. + * + * This method is particularly concerned with synchronized subscriptions. It will only return + * true when the following conditions are met: + * + * - There is no free trial + * - The subscription is synchronized + * - The store owner has determined that new subscribers need to pay for their subscription upfront. + * + * Additionally, if the store owner sets a number of days prior to the synchronization day that do not + * require an upfront payment, this method will check to see whether the current date falls within that + * period for the given product. + * + * @param WC_Product $product The product to check. + * @param string $from_date Optional. A MySQL formatted date/time string from which to calculate from. The default is an empty string which is today's date/time. + * + * @return bool Whether an upfront payment is required for the product. + */ + public static function is_payment_upfront( $product, $from_date = '' ) + { + } + + /** + * Get the day of the week, month or year on which a subscription's payments should be + * synchronised to. + * + * @return int The day the products payments should be processed, or 0 if the payments should not be sync'd to a specific day. + * @since 1.5 + */ + public static function get_products_payment_day( $product ) + { + } + + /** + * Calculate the first payment date for a synced subscription. + * + * The date is calculated in UTC timezone. + * + * @param WC_Product $product A subscription product. + * @param string $type (optional) The format to return the first payment date in, either 'mysql' or 'timestamp'. Default 'mysql'. + * @param string $from_date (optional) The date to calculate the first payment from in GMT/UTC timzeone. If not set, it will use the current date. This should not include any trial period on the product. + * @since 1.5 + */ + public static function calculate_first_payment_date( $product, $type = 'mysql', $from_date = '' ) + { + } + + /** + * Return an i18n'ified associative array of sync options for 'year' as billing period + * + * @since 3.0.0 + */ + public static function get_year_sync_options() + { + } + + /** + * Return an i18n'ified associative array of all possible subscription periods. + * + * @since 1.5 + */ + public static function get_billing_period_ranges( $billing_period = '' ) + { + } + + /** + * Add the first payment date to a products summary section + * + * @since 1.5 + */ + public static function products_first_payment_date( $echo = false ) + { + } + + /** + * Return a string explaining when the first payment will be completed for the subscription. + * + * @since 1.5 + */ + public static function get_products_first_payment_date( $product ) + { + } + + /** + * If a product is synchronised to a date in the future, make sure that is set as the product's first payment date + * + * @since 2.0 + */ + public static function products_first_renewal_payment_time( $first_renewal_timestamp, $product_id, $from_date, $timezone ) + { + } + + /** + * Make sure a synchronised subscription's price includes a free trial, unless it's first payment is today. + * + * @since 1.5 + */ + public static function maybe_set_free_trial( $total = '' ) + { + } + + /** + * Make sure a synchronised subscription's price includes a free trial, unless it's first payment is today. + * + * @since 1.5 + */ + public static function maybe_unset_free_trial( $total = '' ) + { + } + + /** + * Check if the cart includes a subscription that needs to be synced. + * + * @return bool Returns true if any item in the cart is a subscription sync request, otherwise, false. + * @since 1.5 + */ + public static function cart_contains_synced_subscription( $cart = null ) + { + } + + /** + * Maybe set the time of a product's trial expiration to be the same as the synced first payment date for products where the first + * renewal payment date falls on the same day as the trial expiration date, but the trial expiration time is later in the day. + * + * When making sure the first payment is after the trial expiration in @see self::calculate_first_payment_date() we only check + * whether the first payment day comes after the trial expiration day, because we don't want to pushing the first payment date + * a month or year in the future because of a few hours difference between it and the trial expiration. However, this means we + * could still end up with a trial end time after the first payment time, even though they are both on the same day because the + * trial end time is normally calculated from the start time, which can be any time of day, but the first renewal time is always + * set to be 3am in the site's timezone. For example, the first payment date might be calculate to be 3:00 on the 21st April 2017, + * while the trial end date is on the same day at 3:01 (or any time after that on the same day). So we need to check both the time and day. We also don't want to make the first payment date/time skip a year because of a few hours difference. That means we need to either modify the trial end time to be 3:00am or make the first payment time occur at the same time as the trial end time. The former is pretty hard to change, but the later will sync'd payments will be at a different times if there is a free trial ending on the same day, which could be confusing. o_0 + * + * Fixes #1328 + * + * @param mixed $trial_expiration_date MySQL formatted date on which the subscription's trial will end, or 0 if it has no trial + * @param mixed $product_id The product object or post ID of the subscription product + * @return mixed MySQL formatted date on which the subscription's trial is set to end, or 0 if it has no trial + * @since 2.0.13 + */ + public static function recalculate_product_trial_expiration_date( $trial_expiration_date, $product_id ) + { + } + + /** + * Make sure the expiration date is calculated from the synced start date for products where the start date + * will be synced. + * + * @param string $expiration_date MySQL formatted date on which the subscription is set to expire + * @param mixed $product_id The product/post ID of the subscription + * @param mixed $from_date A MySQL formatted date/time string from which to calculate the expiration date, or empty (default), which will use today's date/time. + * @since 1.5 + */ + public static function recalculate_product_expiration_date( $expiration_date, $product_id, $from_date ) + { + } + + /** + * Check if a given timestamp (in the UTC timezone) is equivalent to today in the site's time. + * + * @param int $timestamp A time in UTC timezone to compare to today. + */ + public static function is_today( $timestamp ) + { + } + + /** + * Filters WC_Subscriptions_Order::get_sign_up_fee() to make sure the sign-up fee for a subscription product + * that is synchronised is returned correctly. + * + * @param float The initial sign-up fee charged when the subscription product in the order was first purchased, if any. + * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in. + * @param int $product_id The post ID of the subscription WC_Product object purchased in the order. Defaults to the ID of the first product purchased in the order. + * @return float The initial sign-up fee charged when the subscription product in the order was first purchased, if any. + * @since 2.0 + */ + public static function get_synced_sign_up_fee( $sign_up_fee, $subscription, $product_id ) + { + } + + /** + * Removes the "set_subscription_prices_for_calculation" filter from the WC Product's woocommerce_get_price hook once + * + * @since 1.5.10 + * + * @param int $price The current price. + * @param WC_Product $product The product object. + * + * @return int + */ + public static function set_prorated_price_for_calculation( $price, $product ) + { + } + + /** + * Retrieve the full translated weekday word. + * + * Week starts on translated Monday and can be fetched + * by using 1 (one). So the week starts with 1 (one) + * and ends on Sunday with is fetched by using 7 (seven). + * + * @since 1.5.8 + * @access public + * + * @param int $weekday_number 1 for Monday through 7 Sunday + * @return string Full translated weekday + */ + public static function get_weekday( $weekday_number ) + { + } + + /** + * Override quantities used to lower stock levels by when using synced subscriptions. If it's a synced product + * that does not have proration enabled and the payment date is not today, do not lower stock levels. + * + * @param integer $qty the original quantity that would be taken out of the stock level + * @param array $order order data + * @param array $item item data for each item in the order + * + * @return int + */ + public static function maybe_do_not_reduce_stock( $qty, $order, $order_item ) + { + } + + /** + * Add subscription meta for subscription that contains a synced product. + * + * @param WC_Order Parent order for the subscription + * @param WC_Subscription new subscription + * @since 2.0 + */ + public static function maybe_add_subscription_meta( $post_id ) + { + } + + /** + * When adding an item to an order/subscription via the Add/Edit Subscription administration interface, check if we should be setting + * the sync meta on the subscription. + * + * @param int The order item ID of an item that was just added to the order + * @param array The order item details + * @since 2.0 + */ + public static function ajax_maybe_add_meta_for_item( $item_id, $item ) + { + } + + /** + * When adding a product to an order/subscription via the WC_Subscription::add_product() method, check if we should be setting + * the sync meta on the subscription. + * + * @param int The post ID of a WC_Order or child object + * @param int The order item ID of an item that was just added to the order + * @param object The WC_Product for which an item was just added + * @since 2.0 + */ + public static function maybe_add_meta_for_new_product( $subscription_id, $item_id, $product ) + { + } + + /** + * Check if a given subscription is synced to a certain day. + * + * @param int|WC_Subscription Accepts either a subscription object of post id + * @return bool + * @since 2.0 + */ + public static function subscription_contains_synced_product( $subscription_id ) + { + } + + /** + * If the cart item is synced, add a '_synced' string to the recurring cart key. + * + * @since 2.0 + */ + public static function add_to_recurring_cart_key( $cart_key, $cart_item ) + { + } + + /** + * When adding a product line item to an order/subscription via the WC_Abstract_Order::add_product() method, check if we should be setting + * the sync meta on the subscription. + * + * Attached to WC 3.0+ hooks and uses WC 3.0 methods. + * + * @param int The new line item id + * @param WC_Order_Item + * @param int The post ID of a WC_Subscription + * @since 2.2.3 + */ + public static function maybe_add_meta_for_new_line_item( $item_id, $item, $subscription_id ) + { + } + + /** + * Store a synced product's signup fee on the line item on the subscription and order. + * + * When calculating prorated sign up fees during switches it's necessary to get the sign-up fee paid. + * For synced product purchases we cannot rely on the order line item price as that might include a prorated recurring price or no recurring price all. + * + * Attached to WC 3.0+ hooks and uses WC 3.0 methods. + * + * @param WC_Order_Item_Product $item The order item object. + * @param string $cart_item_key The hash used to identify the item in the cart + * @param array $cart_item The cart item's data. + * @since 2.3.0 + */ + public static function maybe_add_line_item_meta( $item, $cart_item_key, $cart_item ) + { + } + + /** + * Store a synced product's signup fee on the line item on the subscription and order. + * + * This function is a pre WooCommerce 3.0 version of @see WC_Subscriptions_Synchroniser::maybe_add_line_item_meta() + * + * @param int $item_id The order item ID. + * @param array $cart_item The cart item's data. + * @since 2.3.0 + */ + public static function maybe_add_order_item_meta( $item_id, $cart_item ) + { + } + + /** + * Hides synced subscription meta on the edit order and subscription screen on non-debug sites. + * + * @since 2.6.2 + * @param array $hidden_meta_keys the list of meta keys hidden on the edit order and subscription screen. + * @return array $hidden_meta_keys + */ + public static function hide_order_itemmeta( $hidden_meta_keys ) + { + } + + /** + * Gets the number of sign-up grace period days. + * + * @since 3.0.6 + * @return int The number of days in the grace period. 0 will be returned if the stroe isn't charging the full recurring price on sign-up -- a prerequiste for setting a grace period. + */ + private static function get_number_of_grace_period_days() + { + } +} + /** * Check if a given object is a WC_Subscription (or child class of WC_Subscription), or if a given ID * belongs to a post with the subscription post type ('shop_subscription') diff --git a/modules/ppcp-subscription/src/FreeTrialHandlerTrait.php b/modules/ppcp-subscription/src/FreeTrialHandlerTrait.php index 2fe9f7f49..8e309f4ce 100644 --- a/modules/ppcp-subscription/src/FreeTrialHandlerTrait.php +++ b/modules/ppcp-subscription/src/FreeTrialHandlerTrait.php @@ -13,6 +13,7 @@ use WC_Order; use WC_Product; use WC_Subscription; use WC_Subscriptions_Product; +use WC_Subscriptions_Synchroniser; /** * Class FreeTrialHandlerTrait @@ -37,10 +38,7 @@ trait FreeTrialHandlerTrait { foreach ( $cart->get_cart() as $item ) { $product = $item['data'] ?? null; - if ( ! $product instanceof WC_Product ) { - continue; - } - if ( WC_Subscriptions_Product::get_trial_length( $product ) > 0 ) { + if ( $product && WC_Subscriptions_Product::is_subscription( $product ) ) { return true; } } @@ -60,9 +58,22 @@ trait FreeTrialHandlerTrait { $product = wc_get_product(); - return $product - && WC_Subscriptions_Product::is_subscription( $product ) - && WC_Subscriptions_Product::get_trial_length( $product ) > 0; + if ( ! $product || ! WC_Subscriptions_Product::is_subscription( $product ) ) { + return false; + } + + if ( WC_Subscriptions_Product::get_trial_length( $product ) > 0 ) { + return true; + } + + if ( WC_Subscriptions_Synchroniser::is_product_synced( $product ) && ! WC_Subscriptions_Synchroniser::is_payment_upfront( $product ) ) { + $date = WC_Subscriptions_Synchroniser::calculate_first_payment_date( $product, 'timestamp' ); + if ( ! WC_Subscriptions_Synchroniser::is_today( $date ) ) { + return true; + } + } + + return false; } /** @@ -82,13 +93,6 @@ trait FreeTrialHandlerTrait { $subs = wcs_get_subscriptions_for_order( $wc_order ); - return ! empty( - array_filter( - $subs, - function ( WC_Subscription $sub ): bool { - return (float) $sub->get_total_initial_payment() <= 0; - } - ) - ); + return ! empty( $subs ); } } From 6517f15457820f24896ee92bc5a00693089e39e4 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 25 Oct 2022 11:57:22 +0200 Subject: [PATCH 17/26] Add refund support for pui --- modules/ppcp-wc-gateway/services.php | 4 +- .../PayUponInvoice/PayUponInvoiceGateway.php | 44 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 1e4d2bc78..30608fa96 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -1994,7 +1994,9 @@ return array( $container->get( 'wcgateway.transaction-url-provider' ), $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'wcgateway.pay-upon-invoice-helper' ), - $container->get( 'wcgateway.checkout-helper' ) + $container->get( 'wcgateway.checkout-helper' ), + $container->get( 'onboarding.state' ), + $container->get( 'wcgateway.processor.refunds' ) ); }, 'wcgateway.pay-upon-invoice-fraudnet-session-id' => static function ( ContainerInterface $container ): FraudNetSessionId { diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php index 7cff41b36..95209866b 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoiceGateway.php @@ -17,10 +17,12 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; +use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait; +use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; /** * Class PayUponInvoiceGateway. @@ -87,6 +89,20 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { */ protected $checkout_helper; + /** + * The onboarding state. + * + * @var State + */ + protected $state; + + /** + * The refund processor. + * + * @var RefundProcessor + */ + protected $refund_processor; + /** * PayUponInvoiceGateway constructor. * @@ -98,6 +114,8 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { * @param LoggerInterface $logger The logger. * @param PayUponInvoiceHelper $pui_helper The PUI helper. * @param CheckoutHelper $checkout_helper The checkout helper. + * @param State $state The onboarding state. + * @param RefundProcessor $refund_processor The refund processor. */ public function __construct( PayUponInvoiceOrderEndpoint $order_endpoint, @@ -107,7 +125,9 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { TransactionUrlProvider $transaction_url_provider, LoggerInterface $logger, PayUponInvoiceHelper $pui_helper, - CheckoutHelper $checkout_helper + CheckoutHelper $checkout_helper, + State $state, + RefundProcessor $refund_processor ) { $this->id = self::ID; @@ -137,6 +157,12 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { $this->transaction_url_provider = $transaction_url_provider; $this->pui_helper = $pui_helper; $this->checkout_helper = $checkout_helper; + + $this->state = $state; + if ( $state->current_state() === State::STATE_ONBOARDED ) { + $this->supports = array( 'refunds' ); + } + $this->refund_processor = $refund_processor; } /** @@ -274,6 +300,22 @@ class PayUponInvoiceGateway extends WC_Payment_Gateway { } } + /** + * Process refund. + * + * @param int $order_id Order ID. + * @param float $amount Refund amount. + * @param string $reason Refund reason. + * @return boolean True or false based on success, or a WP_Error object. + */ + public function process_refund( $order_id, $amount = null, $reason = '' ) { + $order = wc_get_order( $order_id ); + if ( ! is_a( $order, \WC_Order::class ) ) { + return false; + } + return $this->refund_processor->process( $order, (float) $amount, (string) $reason ); + } + /** * Return transaction url for this gateway and given order. * From 37b20fa896ff3215cb85f960d2190aa2cd91221c Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 25 Oct 2022 12:07:01 +0200 Subject: [PATCH 18/26] Fix phpunit --- .../PayUponInvoice/PayUponInvoiceGatewayTest.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php index add91a083..91e447774 100644 --- a/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/PayUponInvoice/PayUponInvoiceGatewayTest.php @@ -11,10 +11,12 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; +use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\TestCase; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper; +use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use function Brain\Monkey\Functions\when; class PayUponInvoiceGatewayTest extends TestCase @@ -28,6 +30,8 @@ class PayUponInvoiceGatewayTest extends TestCase private $testee; private $pui_helper; private $checkout_helper; + private $state; + private $refund_processor; public function setUp(): void { @@ -42,6 +46,11 @@ class PayUponInvoiceGatewayTest extends TestCase $this->pui_helper = Mockery::mock(PayUponInvoiceHelper::class); $this->checkout_helper = Mockery::mock(CheckoutHelper::class); + $this->state = Mockery::mock(State::class); + $this->state->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED); + + $this->refund_processor = Mockery::mock(RefundProcessor::class); + $this->setInitStubs(); $this->testee = new PayUponInvoiceGateway( @@ -52,7 +61,9 @@ class PayUponInvoiceGatewayTest extends TestCase $this->transaction_url_provider, $this->logger, $this->pui_helper, - $this->checkout_helper + $this->checkout_helper, + $this->state, + $this->refund_processor ); } From f688ccdff066abbfbc3035a994440ac87e320c75 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 25 Oct 2022 16:16:48 +0200 Subject: [PATCH 19/26] Fix php8 warnings --- .../src/Entity/PaymentToken.php | 3 +- .../src/Factory/PaymentTokenFactory.php | 7 +++-- .../src/PPEC/SubscriptionsHandler.php | 3 +- .../Gateway/PayUponInvoice/PayUponInvoice.php | 28 ++++++++++++------- .../Vaulting/PaymentTokenRepositoryTest.php | 2 +- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-api-client/src/Entity/PaymentToken.php b/modules/ppcp-api-client/src/Entity/PaymentToken.php index 686813817..1854167c5 100644 --- a/modules/ppcp-api-client/src/Entity/PaymentToken.php +++ b/modules/ppcp-api-client/src/Entity/PaymentToken.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Entity; +use stdClass; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; /** @@ -47,7 +48,7 @@ class PaymentToken { * @param \stdClass $source The source. * @throws RuntimeException When the type is not valid. */ - public function __construct( string $id, string $type = self::TYPE_PAYMENT_METHOD_TOKEN, \stdClass $source ) { + public function __construct( string $id, stdClass $source, string $type = self::TYPE_PAYMENT_METHOD_TOKEN ) { if ( ! in_array( $type, self::get_valid_types(), true ) ) { throw new RuntimeException( __( 'Not a valid payment source type.', 'woocommerce-paypal-payments' ) diff --git a/modules/ppcp-api-client/src/Factory/PaymentTokenFactory.php b/modules/ppcp-api-client/src/Factory/PaymentTokenFactory.php index 1c2f6de39..50b21a78f 100644 --- a/modules/ppcp-api-client/src/Factory/PaymentTokenFactory.php +++ b/modules/ppcp-api-client/src/Factory/PaymentTokenFactory.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Factory; +use stdClass; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; @@ -25,7 +26,7 @@ class PaymentTokenFactory { * @return PaymentToken * @throws RuntimeException When JSON object is malformed. */ - public function from_paypal_response( \stdClass $data ): PaymentToken { + public function from_paypal_response( stdClass $data ): PaymentToken { if ( ! isset( $data->id ) ) { throw new RuntimeException( __( 'No id for payment token given', 'woocommerce-paypal-payments' ) @@ -34,8 +35,8 @@ class PaymentTokenFactory { return new PaymentToken( $data->id, - ( isset( $data->type ) ) ? $data->type : PaymentToken::TYPE_PAYMENT_METHOD_TOKEN, - $data->source + $data->source, + ( isset( $data->type ) ) ? $data->type : PaymentToken::TYPE_PAYMENT_METHOD_TOKEN ); } diff --git a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php index 0881c40a5..8db9808a0 100644 --- a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php +++ b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat\PPEC; +use stdClass; use WooCommerce\PayPalCommerce\Subscription\RenewalHandler; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; @@ -125,7 +126,7 @@ class SubscriptionsHandler { $billing_agreement_id = $order->get_meta( '_ppec_billing_agreement_id', true ); if ( $billing_agreement_id ) { - $token = new PaymentToken( $billing_agreement_id, 'BILLING_AGREEMENT', new \stdClass() ); + $token = new PaymentToken( $billing_agreement_id, new stdClass(), 'BILLING_AGREEMENT' ); } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 928fac2ef..6afd9cfa5 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -251,16 +251,24 @@ class PayUponInvoice { $order = $this->pui_order_endpoint->order( $order_id ); - $payment_instructions = array( - $order->payment_source->pay_upon_invoice->payment_reference, - $order->payment_source->pay_upon_invoice->deposit_bank_details, - ); - $wc_order->update_meta_data( - 'ppcp_ratepay_payment_instructions_payment_reference', - $payment_instructions - ); - $wc_order->save_meta_data(); - $this->logger->info( "Ratepay payment instructions added to order #{$wc_order->get_id()}." ); + if ( + property_exists( $order, 'payment_source' ) + && property_exists( $order->payment_source, 'pay_upon_invoice' ) + && property_exists( $order->payment_source->pay_upon_invoice, 'payment_reference' ) + && property_exists( $order->payment_source->pay_upon_invoice, 'deposit_bank_details' ) + ) { + + $payment_instructions = array( + $order->payment_source->pay_upon_invoice->payment_reference, + $order->payment_source->pay_upon_invoice->deposit_bank_details, + ); + $wc_order->update_meta_data( + 'ppcp_ratepay_payment_instructions_payment_reference', + $payment_instructions + ); + $wc_order->save_meta_data(); + $this->logger->info( "Ratepay payment instructions added to order #{$wc_order->get_id()}." ); + } $capture = $this->capture_factory->from_paypal_response( $order->purchase_units[0]->payments->captures[0] ); $breakdown = $capture->seller_receivable_breakdown(); diff --git a/tests/PHPUnit/Vaulting/PaymentTokenRepositoryTest.php b/tests/PHPUnit/Vaulting/PaymentTokenRepositoryTest.php index 1a94249c9..5cf5cde21 100644 --- a/tests/PHPUnit/Vaulting/PaymentTokenRepositoryTest.php +++ b/tests/PHPUnit/Vaulting/PaymentTokenRepositoryTest.php @@ -52,7 +52,7 @@ class PaymentTokenRepositoryTest extends TestCase { $id = 1; $source = new \stdClass(); - $paymentToken = new PaymentToken('foo', 'PAYMENT_METHOD_TOKEN', $source); + $paymentToken = new PaymentToken('foo', $source, 'PAYMENT_METHOD_TOKEN'); when('get_user_meta')->justReturn([]); $this->endpoint->shouldReceive('for_user') From 914c79e5fca533a62c496955b64dac9338e53165 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Tue, 25 Oct 2022 16:22:06 +0200 Subject: [PATCH 20/26] Fix phpcs --- modules/ppcp-api-client/src/Entity/PaymentToken.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Entity/PaymentToken.php b/modules/ppcp-api-client/src/Entity/PaymentToken.php index 1854167c5..51b43e57b 100644 --- a/modules/ppcp-api-client/src/Entity/PaymentToken.php +++ b/modules/ppcp-api-client/src/Entity/PaymentToken.php @@ -43,9 +43,9 @@ class PaymentToken { /** * PaymentToken constructor. * - * @param string $id The Id. - * @param string $type The type. - * @param \stdClass $source The source. + * @param string $id The Id. + * @param stdClass $source The source. + * @param string $type The type. * @throws RuntimeException When the type is not valid. */ public function __construct( string $id, stdClass $source, string $type = self::TYPE_PAYMENT_METHOD_TOKEN ) { From 9020c9b797102e6bb2eb210a6f1a58f34fc1693c Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Oct 2022 09:09:45 +0300 Subject: [PATCH 21/26] Use the same errorHandler Otherwise old messages may get stuck --- modules/ppcp-button/resources/js/button.js | 11 ++++++++--- .../js/modules/ContextBootstrap/CartBootstap.js | 5 +++-- .../js/modules/ContextBootstrap/CheckoutBootstap.js | 5 +++-- .../js/modules/ContextBootstrap/MiniCartBootstap.js | 5 +++-- .../js/modules/ContextBootstrap/PayNowBootstrap.js | 4 ++-- .../modules/ContextBootstrap/SingleProductBootstap.js | 5 +++-- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index b1f19bbac..0e3fae7b0 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -100,7 +100,8 @@ const bootstrap = () => { if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') { const miniCartBootstrap = new MiniCartBootstap( PayPalCommerceGateway, - renderer + renderer, + errorHandler, ); miniCartBootstrap.init(); @@ -112,6 +113,7 @@ const bootstrap = () => { PayPalCommerceGateway, renderer, messageRenderer, + errorHandler, ); singleProductBootstrap.init(); @@ -121,6 +123,7 @@ const bootstrap = () => { const cartBootstrap = new CartBootstrap( PayPalCommerceGateway, renderer, + errorHandler, ); cartBootstrap.init(); @@ -131,7 +134,8 @@ const bootstrap = () => { PayPalCommerceGateway, renderer, messageRenderer, - spinner + spinner, + errorHandler, ); checkoutBootstap.init(); @@ -142,7 +146,8 @@ const bootstrap = () => { PayPalCommerceGateway, renderer, messageRenderer, - spinner + spinner, + errorHandler, ); payNowBootstrap.init(); } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js index d32ca440f..3c5d14b9d 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js @@ -2,9 +2,10 @@ import CartActionHandler from '../ActionHandler/CartActionHandler'; import ErrorHandler from '../ErrorHandler'; class CartBootstrap { - constructor(gateway, renderer) { + constructor(gateway, renderer, errorHandler) { this.gateway = gateway; this.renderer = renderer; + this.errorHandler = errorHandler; } init() { @@ -28,7 +29,7 @@ class CartBootstrap { render() { const actionHandler = new CartActionHandler( PayPalCommerceGateway, - new ErrorHandler(this.gateway.labels.error.generic), + this.errorHandler, ); this.renderer.render( diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index b491bcb1e..a513e9c76 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -8,11 +8,12 @@ import { } from "../Helper/CheckoutMethodState"; class CheckoutBootstap { - constructor(gateway, renderer, messages, spinner) { + constructor(gateway, renderer, messages, spinner, errorHandler) { this.gateway = gateway; this.renderer = renderer; this.messages = messages; this.spinner = spinner; + this.errorHandler = errorHandler; this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR; } @@ -60,7 +61,7 @@ class CheckoutBootstap { } const actionHandler = new CheckoutActionHandler( PayPalCommerceGateway, - new ErrorHandler(this.gateway.labels.error.generic), + this.errorHandler, this.spinner ); diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js index 35465c12e..96e7abb8e 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js @@ -2,9 +2,10 @@ import ErrorHandler from '../ErrorHandler'; import CartActionHandler from '../ActionHandler/CartActionHandler'; class MiniCartBootstap { - constructor(gateway, renderer) { + constructor(gateway, renderer, errorHandler) { this.gateway = gateway; this.renderer = renderer; + this.errorHandler = errorHandler; this.actionHandler = null; } @@ -12,7 +13,7 @@ class MiniCartBootstap { this.actionHandler = new CartActionHandler( PayPalCommerceGateway, - new ErrorHandler(this.gateway.labels.error.generic), + this.errorHandler, ); this.render(); diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js index 4b2583c98..4d69532eb 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js @@ -2,8 +2,8 @@ import CheckoutBootstap from './CheckoutBootstap' import {isChangePaymentPage} from "../Helper/Subscriptions"; class PayNowBootstrap extends CheckoutBootstap { - constructor(gateway, renderer, messages, spinner) { - super(gateway, renderer, messages, spinner) + constructor(gateway, renderer, messages, spinner, errorHandler) { + super(gateway, renderer, messages, spinner, errorHandler) } updateUi() { diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index ecb44eabe..d4634ce1f 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -3,10 +3,11 @@ import UpdateCart from "../Helper/UpdateCart"; import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler"; class SingleProductBootstap { - constructor(gateway, renderer, messages) { + constructor(gateway, renderer, messages, errorHandler) { this.gateway = gateway; this.renderer = renderer; this.messages = messages; + this.errorHandler = errorHandler; } @@ -81,7 +82,7 @@ class SingleProductBootstap { this.messages.hideMessages(); }, document.querySelector('form.cart'), - new ErrorHandler(this.gateway.labels.error.generic), + this.errorHandler, ); this.renderer.render( From 3dde305cb4ff57ef7844f4972df1dd7344f3cb5e Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Oct 2022 09:11:35 +0300 Subject: [PATCH 22/26] Fix order creation error message handling in js --- .../modules/ActionHandler/CheckoutActionHandler.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js index 98e14e134..127490a1f 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js @@ -66,7 +66,7 @@ class CheckoutActionHandler { } } - throw new Error(data.data.message); + throw {type: 'create-order-error', data: data.data}; } const input = document.createElement('input'); input.setAttribute('type', 'hidden'); @@ -82,9 +82,15 @@ class CheckoutActionHandler { onCancel: () => { spinner.unblock(); }, - onError: () => { - this.errorHandler.genericError(); + onError: (err) => { + console.error(err); spinner.unblock(); + + if (err && err.type === 'create-order-error') { + return; + } + + this.errorHandler.genericError(); } } } From 91636a9dccdc0b3f4ec75b807a9e7ac9310b7fd7 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Oct 2022 09:30:40 +0300 Subject: [PATCH 23/26] Execute server-side WC validation when clicking button (order creation) --- .../ActionHandler/CheckoutActionHandler.js | 4 +- modules/ppcp-button/services.php | 43 +++++++++------ .../src/Endpoint/CreateOrderEndpoint.php | 55 +++++++++++++++++-- .../src/Exception/ValidationException.php | 47 ++++++++++++++++ .../src/Validation/CheckoutFormValidator.php | 43 +++++++++++++++ .../Endpoint/CreateOrderEndpointTest.php | 1 + 6 files changed, 169 insertions(+), 24 deletions(-) create mode 100644 modules/ppcp-button/src/Exception/ValidationException.php create mode 100644 modules/ppcp-button/src/Validation/CheckoutFormValidator.php diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js index 127490a1f..aa456a064 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js @@ -59,7 +59,9 @@ class CheckoutActionHandler { ); } else { errorHandler.clear(); - if (data.data.details.length > 0) { + if (data.data.errors.length > 0) { + errorHandler.messages(data.data.errors); + } else if (data.data.details.length > 0) { errorHandler.message(data.data.details.map(d => `${d.issue} ${d.description}`).join('
'), true); } else { errorHandler.message(data.data.message, true); diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index cd30058cf..aa01ff1bc 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -27,7 +27,7 @@ use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\State; return array( - 'button.client_id' => static function ( ContainerInterface $container ): string { + 'button.client_id' => static function ( ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); $client_id = $settings->has( 'client_id' ) ? $settings->get( 'client_id' ) : ''; @@ -45,7 +45,7 @@ return array( return $env->current_environment_is( Environment::SANDBOX ) ? CONNECT_WOO_SANDBOX_CLIENT_ID : CONNECT_WOO_CLIENT_ID; }, - 'button.smart-button' => static function ( ContainerInterface $container ): SmartButtonInterface { + 'button.smart-button' => static function ( ContainerInterface $container ): SmartButtonInterface { $state = $container->get( 'onboarding.state' ); /** @@ -92,16 +92,16 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'button.url' => static function ( ContainerInterface $container ): string { + 'button.url' => static function ( ContainerInterface $container ): string { return plugins_url( '/modules/ppcp-button/', dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ); }, - 'button.request-data' => static function ( ContainerInterface $container ): RequestData { + 'button.request-data' => static function ( ContainerInterface $container ): RequestData { return new RequestData(); }, - 'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint { + 'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint { if ( ! \WC()->cart ) { throw new RuntimeException( 'cant initialize endpoint at this moment' ); } @@ -113,7 +113,7 @@ return array( $logger = $container->get( 'woocommerce.logger.woocommerce' ); return new ChangeCartEndpoint( $cart, $shipping, $request_data, $purchase_unit_factory, $data_store, $logger ); }, - 'button.endpoint.create-order' => static function ( ContainerInterface $container ): CreateOrderEndpoint { + 'button.endpoint.create-order' => static function ( ContainerInterface $container ): CreateOrderEndpoint { $request_data = $container->get( 'button.request-data' ); $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); $order_endpoint = $container->get( 'api.endpoint.order' ); @@ -134,10 +134,11 @@ return array( $early_order_handler, $registration_needed, $container->get( 'wcgateway.settings.card_billing_data_mode' ), + $container->get( 'button.early-wc-checkout-validation-enabled' ), $logger ); }, - 'button.helper.early-order-handler' => static function ( ContainerInterface $container ) : EarlyOrderHandler { + 'button.helper.early-order-handler' => static function ( ContainerInterface $container ) : EarlyOrderHandler { $state = $container->get( 'onboarding.state' ); $order_processor = $container->get( 'wcgateway.order-processor' ); @@ -145,7 +146,7 @@ return array( $prefix = $container->get( 'api.prefix' ); return new EarlyOrderHandler( $state, $order_processor, $session_handler, $prefix ); }, - 'button.endpoint.approve-order' => static function ( ContainerInterface $container ): ApproveOrderEndpoint { + 'button.endpoint.approve-order' => static function ( ContainerInterface $container ): ApproveOrderEndpoint { $request_data = $container->get( 'button.request-data' ); $order_endpoint = $container->get( 'api.endpoint.order' ); $session_handler = $container->get( 'session.handler' ); @@ -165,7 +166,7 @@ return array( $logger ); }, - 'button.endpoint.data-client-id' => static function( ContainerInterface $container ) : DataClientIdEndpoint { + 'button.endpoint.data-client-id' => static function( ContainerInterface $container ) : DataClientIdEndpoint { $request_data = $container->get( 'button.request-data' ); $identity_token = $container->get( 'api.endpoint.identity-token' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); @@ -175,39 +176,47 @@ return array( $logger ); }, - 'button.endpoint.vault-paypal' => static function( ContainerInterface $container ) : StartPayPalVaultingEndpoint { + 'button.endpoint.vault-paypal' => static function( ContainerInterface $container ) : StartPayPalVaultingEndpoint { return new StartPayPalVaultingEndpoint( $container->get( 'button.request-data' ), $container->get( 'api.endpoint.payment-token' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'button.helper.three-d-secure' => static function ( ContainerInterface $container ): ThreeDSecure { + 'button.helper.three-d-secure' => static function ( ContainerInterface $container ): ThreeDSecure { $logger = $container->get( 'woocommerce.logger.woocommerce' ); return new ThreeDSecure( $logger ); }, - 'button.helper.messages-apply' => static function ( ContainerInterface $container ): MessagesApply { + 'button.helper.messages-apply' => static function ( ContainerInterface $container ): MessagesApply { return new MessagesApply( $container->get( 'api.shop.country' ) ); }, - 'button.is-logged-in' => static function ( ContainerInterface $container ): bool { + 'button.is-logged-in' => static function ( ContainerInterface $container ): bool { return is_user_logged_in(); }, - 'button.registration-required' => static function ( ContainerInterface $container ): bool { + 'button.registration-required' => static function ( ContainerInterface $container ): bool { return WC()->checkout()->is_registration_required(); }, - 'button.current-user-must-register' => static function ( ContainerInterface $container ): bool { + 'button.current-user-must-register' => static function ( ContainerInterface $container ): bool { return ! $container->get( 'button.is-logged-in' ) && $container->get( 'button.registration-required' ); }, - 'button.basic-checkout-validation-enabled' => static function ( ContainerInterface $container ): bool { + 'button.basic-checkout-validation-enabled' => static function ( ContainerInterface $container ): bool { /** * The filter allowing to disable the basic client-side validation of the checkout form * when the PayPal button is clicked. */ - return (bool) apply_filters( 'woocommerce_paypal_payments_basic_checkout_validation_enabled', true ); + return (bool) apply_filters( 'woocommerce_paypal_payments_basic_checkout_validation_enabled', false ); + }, + 'button.early-wc-checkout-validation-enabled' => static function ( ContainerInterface $container ): bool { + /** + * The filter allowing to disable the WC validation of the checkout form + * when the PayPal button is clicked. + * The validation is triggered in a non-standard way and may cause issues on some sites. + */ + return (bool) apply_filters( 'woocommerce_paypal_payments_early_wc_checkout_validation_enabled', true ); }, ); diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 3e6e5cb9d..b22b851b9 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use Exception; use Psr\Log\LoggerInterface; use stdClass; +use Throwable; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount; use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; @@ -25,6 +26,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; +use WooCommerce\PayPalCommerce\Button\Exception\ValidationException; +use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; @@ -128,6 +131,13 @@ class CreateOrderEndpoint implements EndpointInterface { */ protected $card_billing_data_mode; + /** + * Whether to execute WC validation of the checkout form. + * + * @var bool + */ + protected $early_validation_enabled; + /** * The logger. * @@ -148,6 +158,7 @@ class CreateOrderEndpoint implements EndpointInterface { * @param EarlyOrderHandler $early_order_handler The EarlyOrderHandler object. * @param bool $registration_needed Whether a new user must be registered during checkout. * @param string $card_billing_data_mode The value of card_billing_data_mode from the settings. + * @param bool $early_validation_enabled Whether to execute WC validation of the checkout form. * @param LoggerInterface $logger The logger. */ public function __construct( @@ -161,6 +172,7 @@ class CreateOrderEndpoint implements EndpointInterface { EarlyOrderHandler $early_order_handler, bool $registration_needed, string $card_billing_data_mode, + bool $early_validation_enabled, LoggerInterface $logger ) { @@ -174,6 +186,7 @@ class CreateOrderEndpoint implements EndpointInterface { $this->early_order_handler = $early_order_handler; $this->registration_needed = $registration_needed; $this->card_billing_data_mode = $card_billing_data_mode; + $this->early_validation_enabled = $early_validation_enabled; $this->logger = $logger; } @@ -233,8 +246,14 @@ class CreateOrderEndpoint implements EndpointInterface { $this->set_bn_code( $data ); - if ( 'pay-now' === $data['context'] && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) { - $this->validate_paynow_form( $data['form'] ); + $form_fields = $data['form'] ?? null; + + if ( $this->early_validation_enabled && is_array( $form_fields ) ) { + $this->validate_form( $form_fields ); + } + + if ( 'pay-now' === $data['context'] && is_array( $form_fields ) && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) { + $this->validate_paynow_form( $form_fields ); } try { @@ -264,6 +283,13 @@ class CreateOrderEndpoint implements EndpointInterface { wp_send_json_success( $order->to_array() ); return true; + } catch ( ValidationException $error ) { + wp_send_json_error( + array( + 'message' => $error->getMessage(), + 'errors' => $error->errors(), + ) + ); } catch ( \RuntimeException $error ) { $this->logger->error( 'Order creation failed: ' . $error->getMessage() ); @@ -481,16 +507,33 @@ class CreateOrderEndpoint implements EndpointInterface { return $payment_method; } + /** + * Checks whether the form fields are valid. + * + * @param array $form_fields The form fields. + * @throws ValidationException When fields are not valid. + */ + private function validate_form( array $form_fields ): void { + try { + $v = new CheckoutFormValidator(); + $v->validate( $form_fields ); + } catch ( ValidationException $exception ) { + throw $exception; + } catch ( Throwable $exception ) { + $this->logger->error( "Form validation execution failed. {$exception->getMessage()} {$exception->getFile()}:{$exception->getLine()}" ); + } + } + /** * Checks whether the terms input field is checked. * * @param array $form_fields The form fields. - * @throws \RuntimeException When field is not checked. + * @throws ValidationException When field is not checked. */ - private function validate_paynow_form( array $form_fields ) { + private function validate_paynow_form( array $form_fields ): void { if ( isset( $form_fields['terms-field'] ) && ! isset( $form_fields['terms'] ) ) { - throw new \RuntimeException( - __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce-paypal-payments' ) + throw new ValidationException( + array( __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce-paypal-payments' ) ) ); } } diff --git a/modules/ppcp-button/src/Exception/ValidationException.php b/modules/ppcp-button/src/Exception/ValidationException.php new file mode 100644 index 000000000..f8aedf291 --- /dev/null +++ b/modules/ppcp-button/src/Exception/ValidationException.php @@ -0,0 +1,47 @@ +errors = $errors; + + if ( ! $message ) { + $message = implode( ' ', $errors ); + } + + parent::__construct( $message ); + } + + /** + * The error messages. + * + * @return string[] + */ + public function errors(): array { + return $this->errors; + } +} diff --git a/modules/ppcp-button/src/Validation/CheckoutFormValidator.php b/modules/ppcp-button/src/Validation/CheckoutFormValidator.php new file mode 100644 index 000000000..729125638 --- /dev/null +++ b/modules/ppcp-button/src/Validation/CheckoutFormValidator.php @@ -0,0 +1,43 @@ +validate_checkout( $data, $errors ); + + if ( $errors->has_errors() ) { + throw new ValidationException( $errors->get_error_messages() ); + } + } +} diff --git a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php index d185f37ea..4e6d82a6c 100644 --- a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php @@ -166,6 +166,7 @@ class CreateOrderEndpointTest extends TestCase $early_order_handler, false, CardBillingMode::MINIMAL_INPUT, + false, new NullLogger() ); return array($payer_factory, $testee); From c99418d0bcd2c9c32729400a70e92c86606235de Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Oct 2022 12:14:39 +0300 Subject: [PATCH 24/26] Remove unused imports --- .../resources/js/modules/ContextBootstrap/CartBootstap.js | 1 - .../resources/js/modules/ContextBootstrap/CheckoutBootstap.js | 1 - .../resources/js/modules/ContextBootstrap/MiniCartBootstap.js | 1 - .../js/modules/ContextBootstrap/SingleProductBootstap.js | 1 - 4 files changed, 4 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js index 3c5d14b9d..ebc00bfdc 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js @@ -1,5 +1,4 @@ import CartActionHandler from '../ActionHandler/CartActionHandler'; -import ErrorHandler from '../ErrorHandler'; class CartBootstrap { constructor(gateway, renderer, errorHandler) { diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index a513e9c76..af04b6811 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -1,4 +1,3 @@ -import ErrorHandler from '../ErrorHandler'; import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; import {setVisible, setVisibleByClass} from '../Helper/Hiding'; import { diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js index 96e7abb8e..443c9afe4 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js @@ -1,4 +1,3 @@ -import ErrorHandler from '../ErrorHandler'; import CartActionHandler from '../ActionHandler/CartActionHandler'; class MiniCartBootstap { diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index d4634ce1f..7bb515df4 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -1,4 +1,3 @@ -import ErrorHandler from '../ErrorHandler'; import UpdateCart from "../Helper/UpdateCart"; import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler"; From 90cd342a1075927633d98b44ef403bdd56485074 Mon Sep 17 00:00:00 2001 From: dinamiko Date: Thu, 27 Oct 2022 12:51:47 +0200 Subject: [PATCH 25/26] Hide PUI gateway if shipping address is not Germany --- modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php index 6b9af84c8..98bf0fbe3 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceHelper.php @@ -59,6 +59,12 @@ class PayUponInvoiceHelper { return false; } + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $shipping_country = wc_clean( wp_unslash( $_POST['s_country'] ?? '' ) ); + if ( $shipping_country && 'DE' !== $shipping_country ) { + return false; + } + if ( ! $this->is_valid_currency() ) { return false; } From 349f606ced4233239e15b342d37520df726f65a9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 31 Oct 2022 15:29:58 +0200 Subject: [PATCH 26/26] Fix preview error when missing elements --- modules/ppcp-wc-gateway/resources/js/gateway-settings.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 7f502628c..dea91a6db 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -74,6 +74,9 @@ import Renderer from '../../../ppcp-button/resources/js/modules/Renderer/Rendere function createButtonPreview(settingsCallback) { const render = (settings) => { const wrapper = document.querySelector(settings.button.wrapper); + if (!wrapper) { + return; + } wrapper.innerHTML = ''; const renderer = new Renderer(null, settings, (data, actions) => actions.reject(), null); @@ -117,7 +120,7 @@ import Renderer from '../../../ppcp-button/resources/js/modules/Renderer/Rendere 'enable-funding': ['venmo'], 'buyer-country': PayPalCommerceGatewaySettings.country, }; - if (disabledSources.length) { + if (disabledSources?.length) { settings['disable-funding'] = disabledSources; } return settings;