diff --git a/modules/ppcp-api-client/src/Factory/AmountFactory.php b/modules/ppcp-api-client/src/Factory/AmountFactory.php index 258ca08c2..bf9637c32 100644 --- a/modules/ppcp-api-client/src/Factory/AmountFactory.php +++ b/modules/ppcp-api-client/src/Factory/AmountFactory.php @@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; /** * Class AmountFactory @@ -124,7 +125,12 @@ class AmountFactory { $items = $this->item_factory->from_wc_order( $order ); $total_value = (float) $order->get_total(); - if ( CreditCardGateway::ID === $order->get_payment_method() && $this->is_free_trial_order( $order ) ) { + if ( ( + CreditCardGateway::ID === $order->get_payment_method() + || ( PayPalGateway::ID === $order->get_payment_method() && 'card' === $order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE ) ) + ) + && $this->is_free_trial_order( $order ) + ) { $total_value = 1.0; } $total = new Money( $total_value, $currency ); diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index 0a0e9c073..1ecda5a95 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -31,8 +31,17 @@ const bootstrap = () => { const onSmartButtonClick = (data, actions) => { window.ppcpFundingSource = data.fundingSource; + const form = document.querySelector('form.woocommerce-checkout'); + if (form) { + jQuery('#ppcp-funding-source-form-input').remove(); + form.insertAdjacentHTML( + 'beforeend', + `` + ) + } + const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; - if (isFreeTrial) { + if (isFreeTrial && data.fundingSource !== 'card') { freeTrialHandler.handle(); return actions.reject(); } diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js index 28d53a1f1..d9004f0fc 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js @@ -20,6 +20,7 @@ class CartActionHandler { nonce: this.config.ajax.create_order.nonce, purchase_units: [], payment_method: PaymentMethods.PAYPAL, + funding_source: window.ppcpFundingSource, bn_code:bnCode, payer, context:this.config.context diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js index 73252caa1..625fd369b 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js @@ -33,6 +33,7 @@ class CheckoutActionHandler { context:this.config.context, order_id:this.config.order_id, payment_method: getCurrentPaymentMethod(), + funding_source: window.ppcpFundingSource, form:formValues, createaccount: createaccount }) diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js index 0446e1f0c..3bfbd46b4 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js @@ -86,6 +86,7 @@ class SingleProductActionHandler { payer, bn_code:bnCode, payment_method: PaymentMethods.PAYPAL, + funding_source: window.ppcpFundingSource, context:this.config.context }) }).then(function (res) { diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index fac23b531..89c5faa54 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** @@ -182,6 +183,7 @@ class CreateOrderEndpoint implements EndpointInterface { $data = $this->request_data->read_request( $this->nonce() ); $this->parsed_request_data = $data; $payment_method = $data['payment_method'] ?? ''; + $funding_source = $data['funding_source'] ?? ''; $wc_order = null; if ( 'pay-now' === $data['context'] ) { $wc_order = wc_get_order( (int) $data['order_id'] ); @@ -200,7 +202,12 @@ class CreateOrderEndpoint implements EndpointInterface { $this->purchase_units = $this->cart_repository->all(); // The cart does not have any info about payment method, so we must handle free trial here. - if ( CreditCardGateway::ID === $payment_method && $this->is_free_trial_cart() ) { + if ( ( + CreditCardGateway::ID === $payment_method + || ( PayPalGateway::ID === $payment_method && 'card' === $funding_source ) + ) + && $this->is_free_trial_cart() + ) { $this->purchase_units[0]->set_amount( new Amount( new Money( 1.0, $this->purchase_units[0]->amount()->currency_code() ), diff --git a/modules/ppcp-vaulting/src/PaymentTokenChecker.php b/modules/ppcp-vaulting/src/PaymentTokenChecker.php index d8d6d8020..45cdae20c 100644 --- a/modules/ppcp-vaulting/src/PaymentTokenChecker.php +++ b/modules/ppcp-vaulting/src/PaymentTokenChecker.php @@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -117,7 +118,9 @@ class PaymentTokenChecker { if ( $tokens ) { try { if ( $this->is_free_trial_order( $wc_order ) ) { - if ( CreditCardGateway::ID === $wc_order->get_payment_method() ) { + if ( CreditCardGateway::ID === $wc_order->get_payment_method() + || ( PayPalGateway::ID === $wc_order->get_payment_method() && 'card' === $wc_order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE ) ) + ) { $order = $this->order_repository->for_wc_order( $wc_order ); $this->authorized_payments_processor->void_authorizations( $order ); $wc_order->payment_complete(); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index c0604e598..5c959d388 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -37,6 +37,7 @@ class PayPalGateway extends \WC_Payment_Gateway { const INTENT_META_KEY = '_ppcp_paypal_intent'; const ORDER_ID_META_KEY = '_ppcp_paypal_order_id'; const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode'; + const ORDER_PAYMENT_SOURCE = '_ppcp_paypal_payment_source'; const FEES_META_KEY = '_ppcp_paypal_fees'; /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index 4768e1f9a..fa425c6f3 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -55,6 +55,7 @@ trait ProcessPaymentTrait { } $payment_method = filter_input( INPUT_POST, 'payment_method', FILTER_SANITIZE_STRING ); + $funding_source = filter_input( INPUT_POST, 'ppcp-funding-source', FILTER_SANITIZE_STRING ); /** * If customer has chosen a saved credit card payment. @@ -135,7 +136,7 @@ trait ProcessPaymentTrait { } } - if ( PayPalGateway::ID === $payment_method && $this->is_free_trial_order( $wc_order ) ) { + if ( PayPalGateway::ID === $payment_method && '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 ); if ( ! array_filter( diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php index 8aba3d7fa..4aad635a4 100644 --- a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php +++ b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php @@ -37,5 +37,29 @@ trait OrderMetaTrait { PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, $environment->current_environment_is( Environment::SANDBOX ) ? 'sandbox' : 'live' ); + $payment_source = $this->get_payment_source( $order ); + if ( $payment_source ) { + $wc_order->update_meta_data( PayPalGateway::ORDER_PAYMENT_SOURCE, $payment_source ); + } + } + + /** + * Returns the payment source type or null, + * + * @param Order $order The PayPal order. + * @return string|null + */ + private function get_payment_source( Order $order ): ?string { + $source = $order->payment_source(); + if ( $source ) { + if ( $source->card() ) { + return 'card'; + } + if ( $source->wallet() ) { + return 'wallet'; + } + } + + return null; } } diff --git a/tests/PHPUnit/ApiClient/Factory/AmountFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/AmountFactoryTest.php index 0aa0bfe38..9001c97ea 100644 --- a/tests/PHPUnit/ApiClient/Factory/AmountFactoryTest.php +++ b/tests/PHPUnit/ApiClient/Factory/AmountFactoryTest.php @@ -162,6 +162,9 @@ class AmountFactoryTest extends TestCase ->shouldReceive('get_total_discount') ->with(false) ->andReturn(3); + $order + ->shouldReceive('get_meta') + ->andReturn(null); $result = $this->testee->from_wc_order($order); $this->assertEquals((float) 3, $result->breakdown()->discount()->value()); @@ -222,6 +225,9 @@ class AmountFactoryTest extends TestCase ->shouldReceive('get_total_discount') ->with(false) ->andReturn(0); + $order + ->shouldReceive('get_meta') + ->andReturn(null); $result = $this->testee->from_wc_order($order); $this->assertNull($result->breakdown()->discount()); diff --git a/tests/PHPUnit/Subscription/RenewalHandlerTest.php b/tests/PHPUnit/Subscription/RenewalHandlerTest.php index fc92fcf9f..9fa160612 100644 --- a/tests/PHPUnit/Subscription/RenewalHandlerTest.php +++ b/tests/PHPUnit/Subscription/RenewalHandlerTest.php @@ -98,6 +98,9 @@ class RenewalHandlerTest extends TestCase $order ->shouldReceive('purchase_units') ->andReturn([$purchaseUnit]); + $order + ->shouldReceive('payment_source') + ->andReturn(null); $wcOrder ->shouldReceive('get_id') diff --git a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php index e3f1f5d6f..e379a4fc1 100644 --- a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php +++ b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php @@ -85,6 +85,9 @@ class OrderProcessorTest extends TestCase ->andReturn($orderStatus); $currentOrder->shouldReceive('purchase_units') ->andReturn([$purchaseUnit]); + $currentOrder + ->shouldReceive('payment_source') + ->andReturn(null); $sessionHandler = Mockery::mock(SessionHandler::class); $sessionHandler @@ -202,6 +205,9 @@ class OrderProcessorTest extends TestCase $currentOrder ->shouldReceive('purchase_units') ->andReturn([$purchaseUnit]); + $currentOrder + ->shouldReceive('payment_source') + ->andReturn(null); $sessionHandler = Mockery::mock(SessionHandler::class); $sessionHandler ->expects('order')