Merge branch 'trunk' into modularity-module-migration

# Conflicts:
#	modules/ppcp-wc-gateway/src/WCGatewayModule.php
This commit is contained in:
Pedro Silva 2024-04-19 11:59:28 +01:00
commit 161e933d39
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
96 changed files with 6599 additions and 223 deletions

View file

@ -137,7 +137,12 @@ const bootstrap = () => {
}
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
if (isFreeTrial && data.fundingSource !== 'card' && ! PayPalCommerceGateway.subscription_plan_id) {
if (
isFreeTrial
&& data.fundingSource !== 'card'
&& ! PayPalCommerceGateway.subscription_plan_id
&& ! PayPalCommerceGateway.vault_v3_enabled
) {
freeTrialHandler.handle();
return actions.reject();
}

View file

@ -9,11 +9,11 @@ class CartActionHandler {
this.errorHandler = errorHandler;
}
subscriptionsConfiguration() {
subscriptionsConfiguration(subscription_plan_id) {
return {
createSubscription: (data, actions) => {
return actions.subscription.create({
'plan_id': this.config.subscription_plan_id
'plan_id': subscription_plan_id
});
},
onApprove: (data, actions) => {

View file

@ -12,7 +12,7 @@ class CheckoutActionHandler {
this.spinner = spinner;
}
subscriptionsConfiguration() {
subscriptionsConfiguration(subscription_plan_id) {
return {
createSubscription: async (data, actions) => {
try {
@ -22,7 +22,7 @@ class CheckoutActionHandler {
}
return actions.subscription.create({
'plan_id': this.config.subscription_plan_id
'plan_id': subscription_plan_id
});
},
onApprove: (data, actions) => {
@ -144,6 +144,54 @@ class CheckoutActionHandler {
}
}
}
addPaymentMethodConfiguration() {
return {
createVaultSetupToken: async () => {
const response = await fetch(this.config.ajax.create_setup_token.endpoint, {
method: "POST",
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonce: this.config.ajax.create_setup_token.nonce,
})
});
const result = await response.json()
if (result.data.id) {
return result.data.id
}
console.error(result)
},
onApprove: async ({vaultSetupToken}) => {
const response = await fetch(this.config.ajax.create_payment_token_for_guest.endpoint, {
method: "POST",
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonce: this.config.ajax.create_payment_token_for_guest.nonce,
vault_setup_token: vaultSetupToken,
})
})
const result = await response.json();
if (result.success === true) {
document.querySelector('#place_order').click()
return;
}
console.error(result)
},
onError: (error) => {
console.error(error)
}
}
}
}
export default CheckoutActionHandler;

View file

@ -90,7 +90,12 @@ class CartBootstrap {
PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) {
this.renderer.render(actionHandler.subscriptionsConfiguration());
let subscription_plan_id = PayPalCommerceGateway.subscription_plan_id
if(PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== '') {
subscription_plan_id = PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart
}
this.renderer.render(actionHandler.subscriptionsConfiguration(subscription_plan_id));
if(!PayPalCommerceGateway.subscription_product_allowed) {
this.gateway.button.is_disabled = true;

View file

@ -106,7 +106,11 @@ class CheckoutBootstap {
PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
) {
this.renderer.render(actionHandler.subscriptionsConfiguration(), {}, actionHandler.configuration());
let subscription_plan_id = PayPalCommerceGateway.subscription_plan_id
if(PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart !== '') {
subscription_plan_id = PayPalCommerceGateway.variable_paypal_subscription_variation_from_cart
}
this.renderer.render(actionHandler.subscriptionsConfiguration(subscription_plan_id), {}, actionHandler.configuration());
if(!PayPalCommerceGateway.subscription_product_allowed) {
this.gateway.button.is_disabled = true;
@ -116,6 +120,14 @@ class CheckoutBootstap {
return;
}
if(
PayPalCommerceGateway.is_free_trial_cart
&& PayPalCommerceGateway.vault_v3_enabled
) {
this.renderer.render(actionHandler.addPaymentMethodConfiguration(), {}, actionHandler.configuration());
return;
}
this.renderer.render(actionHandler.configuration(), {}, actionHandler.configuration());
}

View file

@ -60,6 +60,13 @@ export const loadPaypalScript = (config, onLoaded, onError = null) => {
scriptOptions = merge(scriptOptions, config.script_attributes);
}
// Axo SDK options
const sdkClientToken = config?.axo?.sdk_client_token;
if(sdkClientToken) {
scriptOptions['data-sdk-client-token'] = sdkClientToken;
scriptOptions['data-client-metadata-id'] = 'ppcp-cm-id';
}
// Load PayPal script for special case with data-client-token
if (config.data_client_id?.set_attribute) {
dataClientIdAttributeHandler(scriptOptions, config.data_client_id, callback, errorCallback);

View file

@ -145,6 +145,8 @@ return array(
$container->get( 'button.early-wc-checkout-validation-enabled' ),
$container->get( 'button.pay-now-contexts' ),
$container->get( 'wcgateway.funding-sources-without-redirect' ),
$container->get( 'vaulting.vault-v3-enabled' ),
$container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},

View file

@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Product;
use WC_Product_Variation;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
@ -33,6 +34,9 @@ use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\PayLaterBlock\PayLaterBlockModule;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentTokenForGuest;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
@ -184,13 +188,6 @@ class SmartButton implements SmartButtonInterface {
*/
private $funding_sources_without_redirect;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* Session handler.
*
@ -198,6 +195,27 @@ class SmartButton implements SmartButtonInterface {
*/
private $session_handler;
/**
* Whether Vault v3 module is enabled.
*
* @var bool
*/
private $vault_v3_enabled;
/**
* Payment tokens endpoint.
*
* @var PaymentTokensEndpoint
*/
private $payment_tokens_endpoint;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* SmartButton constructor.
*
@ -220,6 +238,8 @@ class SmartButton implements SmartButtonInterface {
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
* @param array $pay_now_contexts The contexts that should have the Pay Now button.
* @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
* @param bool $vault_v3_enabled Whether Vault v3 module is enabled.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@ -242,6 +262,8 @@ class SmartButton implements SmartButtonInterface {
bool $early_validation_enabled,
array $pay_now_contexts,
array $funding_sources_without_redirect,
bool $vault_v3_enabled,
PaymentTokensEndpoint $payment_tokens_endpoint,
LoggerInterface $logger
) {
@ -264,7 +286,9 @@ class SmartButton implements SmartButtonInterface {
$this->early_validation_enabled = $early_validation_enabled;
$this->pay_now_contexts = $pay_now_contexts;
$this->funding_sources_without_redirect = $funding_sources_without_redirect;
$this->vault_v3_enabled = $vault_v3_enabled;
$this->logger = $logger;
$this->payment_tokens_endpoint = $payment_tokens_endpoint;
}
/**
@ -990,11 +1014,21 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
if ( $this->settings->has( '3d_secure_contingency' ) ) {
$value = $this->settings->get( '3d_secure_contingency' );
if ( $value ) {
return $value;
return $this->return_3ds_contingency( $value );
}
}
return 'SCA_WHEN_REQUIRED';
return $this->return_3ds_contingency( 'SCA_WHEN_REQUIRED' );
}
/**
* Processes and returns the 3D Secure contingency.
*
* @param string $contingency The ThreeD secure contingency.
* @return string
*/
private function return_3ds_contingency( string $contingency ): string {
return apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $contingency );
}
/**
@ -1025,45 +1059,59 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
'redirect' => wc_get_checkout_url(),
'context' => $this->context(),
'ajax' => array(
'simulate_cart' => array(
'simulate_cart' => array(
'endpoint' => \WC_AJAX::get_endpoint( SimulateCartEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( SimulateCartEndpoint::nonce() ),
),
'change_cart' => array(
'change_cart' => array(
'endpoint' => \WC_AJAX::get_endpoint( ChangeCartEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ChangeCartEndpoint::nonce() ),
),
'create_order' => array(
'create_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreateOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( CreateOrderEndpoint::nonce() ),
),
'approve_order' => array(
'approve_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( ApproveOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ),
),
'approve_subscription' => array(
'approve_subscription' => array(
'endpoint' => \WC_AJAX::get_endpoint( ApproveSubscriptionEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ApproveSubscriptionEndpoint::nonce() ),
),
'vault_paypal' => array(
'vault_paypal' => array(
'endpoint' => \WC_AJAX::get_endpoint( StartPayPalVaultingEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( StartPayPalVaultingEndpoint::nonce() ),
),
'save_checkout_form' => array(
'save_checkout_form' => array(
'endpoint' => \WC_AJAX::get_endpoint( SaveCheckoutFormEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( SaveCheckoutFormEndpoint::nonce() ),
),
'validate_checkout' => array(
'validate_checkout' => array(
'endpoint' => \WC_AJAX::get_endpoint( ValidateCheckoutEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ValidateCheckoutEndpoint::nonce() ),
),
'cart_script_params' => array(
'cart_script_params' => array(
'endpoint' => \WC_AJAX::get_endpoint( CartScriptParamsEndpoint::ENDPOINT ),
),
'create_setup_token' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ),
'nonce' => wp_create_nonce( CreateSetupToken::nonce() ),
),
'create_payment_token' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentToken::ENDPOINT ),
'nonce' => wp_create_nonce( CreatePaymentToken::nonce() ),
),
'create_payment_token_for_guest' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentTokenForGuest::ENDPOINT ),
'nonce' => wp_create_nonce( CreatePaymentTokenForGuest::nonce() ),
),
),
'cart_contains_subscription' => $this->subscription_helper->cart_contains_subscription(),
'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
'vault_v3_enabled' => $this->vault_v3_enabled,
'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(),
'variable_paypal_subscription_variation_from_cart' => $this->subscription_helper->paypal_subscription_variation_from_cart(),
'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(),
'locations_with_subscription_product' => $this->subscription_helper->locations_with_subscription_product(),
'enforce_vault' => $this->has_subscriptions(),
@ -1318,7 +1366,7 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
$disable_funding,
array_diff(
array_keys( $this->all_funding_sources ),
array( 'venmo', 'paylater' )
array( 'venmo', 'paylater', 'paypal' )
)
);
}
@ -1339,6 +1387,20 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
$disable_funding[] = 'paylater';
}
$disable_funding = array_filter(
$disable_funding,
/**
* Make sure paypal is not sent in disable funding.
*
* @param string $funding_source The funding_source.
*
* @psalm-suppress MissingClosureParamType
*/
function( $funding_source ) {
return $funding_source !== 'paypal';
}
);
if ( count( $disable_funding ) > 0 ) {
$params['disable-funding'] = implode( ',', array_unique( $disable_funding ) );
}
@ -1880,8 +1942,18 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
*/
private function get_vaulted_paypal_email(): string {
try {
$tokens = $this->get_payment_tokens();
$customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
if ( $customer_id ) {
$customer_tokens = $this->payment_tokens_endpoint->payment_tokens_for_customer( $customer_id );
foreach ( $customer_tokens as $token ) {
$email_address = $token['payment_source']->properties()->email_address ?? '';
if ( $email_address ) {
return $email_address;
}
}
}
$tokens = $this->get_payment_tokens();
foreach ( $tokens as $token ) {
if ( isset( $token->source()->paypal ) ) {
return $token->source()->paypal->payer->email_address;
@ -1890,6 +1962,7 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
} catch ( Exception $exception ) {
$this->logger->error( 'Failed to get PayPal vaulted email. ' . $exception->getMessage() );
}
return '';
}

View file

@ -329,6 +329,21 @@ class CreateOrderEndpoint implements EndpointInterface {
if ( 'pay-now' === $data['context'] && is_a( $wc_order, \WC_Order::class ) ) {
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$payment_source = $order->payment_source();
$payment_source_name = $payment_source ? $payment_source->name() : null;
$payer = $order->payer();
if (
$payer
&& $payment_source_name
&& in_array( $payment_source_name, PayPalGateway::PAYMENT_SOURCES_WITH_PAYER_EMAIL, true )
) {
$payer_email = $payer->email_address();
if ( $payer_email ) {
$wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
}
}
$wc_order->save_meta_data();
do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );

View file

@ -159,6 +159,22 @@ class EarlyOrderHandler {
$wc_order = wc_get_order( $order_id );
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$payment_source = $order->payment_source();
$payment_source_name = $payment_source ? $payment_source->name() : null;
$payer = $order->payer();
if (
$payer
&& $payment_source_name
&& in_array( $payment_source_name, PayPalGateway::PAYMENT_SOURCES_WITH_PAYER_EMAIL, true )
&& $wc_order instanceof \WC_Order
) {
$payer_email = $payer->email_address();
if ( $payer_email ) {
$wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
}
}
$wc_order->save_meta_data();
/**

View file

@ -57,21 +57,24 @@ class ThreeDSecure {
*
* @link https://developer.paypal.com/docs/business/checkout/add-capabilities/3d-secure/#authenticationresult
*
* @param Order $order The order for which the decission is needed.
* @param Order $order The order for which the decision is needed.
*
* @return int
*/
public function proceed_with_order( Order $order ): int {
do_action( 'woocommerce_paypal_payments_three_d_secure_before_check', $order );
$payment_source = $order->payment_source();
if ( ! $payment_source ) {
return self::NO_DECISION;
return $this->return_decision( self::NO_DECISION, $order );
}
if ( ! ( $payment_source->properties()->brand ?? '' ) ) {
return self::NO_DECISION;
return $this->return_decision( self::NO_DECISION, $order );
}
if ( ! ( $payment_source->properties()->authentication_result ?? '' ) ) {
return self::NO_DECISION;
return $this->return_decision( self::NO_DECISION, $order );
}
$authentication_result = $payment_source->properties()->authentication_result ?? null;
@ -81,18 +84,31 @@ class ThreeDSecure {
$this->logger->info( '3DS Authentication Result: ' . wc_print_r( $result->to_array(), true ) );
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_POSSIBLE ) {
return self::PROCCEED;
return $this->return_decision( self::PROCCEED, $order );
}
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_UNKNOWN ) {
return self::RETRY;
return $this->return_decision( self::RETRY, $order );
}
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_NO ) {
return $this->no_liability_shift( $result );
return $this->return_decision( $this->no_liability_shift( $result ), $order );
}
}
return self::NO_DECISION;
return $this->return_decision( self::NO_DECISION, $order );
}
/**
* Processes and returns a ThreeD secure decision.
*
* @param int $decision The ThreeD secure decision.
* @param Order $order The PayPal Order object.
* @return int
*/
public function return_decision( int $decision, Order $order ) {
$decision = apply_filters( 'woocommerce_paypal_payments_three_d_secure_decision', $decision, $order );
do_action( 'woocommerce_paypal_payments_three_d_secure_after_check', $order, $decision );
return $decision;
}
/**