Skip to content

Commit 03094be

Browse files
committed
Apply latest fixes for 4.1.0 release
1 parent 2a0462f commit 03094be

37 files changed

Lines changed: 2376 additions & 128 deletions

modules/ppcp-api-client/src/Authentication/PayPalBearer.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,17 @@ private function newBearer(): Token {
189189
$status_code = is_wp_error( $response ) ? 0 : (int) wp_remote_retrieve_response_code( $response );
190190
$this->rate_limiter->register_failure( self::RATE_LIMIT_SCOPE, $status_code, $response );
191191

192-
$error = new RuntimeException( 'Could not create token.' );
192+
$error = new RuntimeException( 'Could not create token.' );
193+
$log_args = $args;
194+
foreach ( array_keys( $log_args['headers'] ) as $header_name ) {
195+
if ( preg_match( '/authorization|signature/i', $header_name ) ) {
196+
$log_args['headers'][ $header_name ] = '[REDACTED]';
197+
}
198+
}
193199
$this->logger->warning(
194200
$error->getMessage(),
195201
array(
196-
'args' => $args,
202+
'args' => $log_args,
197203
'response' => $response,
198204
)
199205
);

modules/ppcp-api-client/src/Endpoint/LoginSeller.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ private function generate_token_for(
141141
string $seller_nonce
142142
): string {
143143

144-
$url = trailingslashit( $this->host ) . 'v1/oauth2/token/';
145-
$args = array(
144+
$url = trailingslashit( $this->host ) . 'v1/oauth2/token/';
145+
$args = array(
146146
'method' => 'POST',
147147
'headers' => array(
148148
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode

modules/ppcp-api-client/src/Repository/PartnerReferralsData.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,6 @@ public function __construct( DccApplies $dcc_applies, FeaturesDefinition $featur
3030
$this->features_definition = $features_definition;
3131
}
3232

33-
/**
34-
* Returns a nonce.
35-
*
36-
* @return string
37-
*/
38-
public function nonce(): string {
39-
return 'a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG';
40-
}
41-
4233
/**
4334
* Returns the data.
4435
*
@@ -47,13 +38,15 @@ public function nonce(): string {
4738
* @param string $onboarding_token A security token to finalize the onboarding process.
4839
* @param bool $use_subscriptions If the merchant requires subscription features.
4940
* @param bool $use_card_payments If the merchant wants to process credit card payments.
41+
* @param string $seller_nonce The PKCE code_verifier for the OAuth flow.
5042
* @return array
5143
*/
5244
public function data(
5345
array $products = array(),
5446
string $onboarding_token = '',
5547
?bool $use_subscriptions = null,
56-
bool $use_card_payments = true
48+
bool $use_card_payments = true,
49+
string $seller_nonce = ''
5750
): array {
5851
$in_acdc_country = $this->dcc_applies->for_country_currency(); // @phpstan-ignore property.deprecated
5952

@@ -129,7 +122,7 @@ public function data(
129122
'integration_type' => 'FIRST_PARTY',
130123
'first_party_details' => array(
131124
'features' => $first_party_features,
132-
'seller_nonce' => $this->nonce(),
125+
'seller_nonce' => $seller_nonce,
133126
),
134127
),
135128
),

modules/ppcp-axo/src/Gateway/AxoGateway.php

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ public function process_payment( $order_id ) {
296296
}
297297
} catch ( Exception $exception ) {
298298
$this->logger->error( '[AXO] Payment processing failed: ' . $exception->getMessage() );
299+
299300
return array(
300301
'result' => 'failure',
301302
'message' => $this->get_user_friendly_error_message( $exception ),
@@ -330,6 +331,47 @@ protected function process_3ds_return( WC_Order $wc_order, string $token ): arra
330331
);
331332
}
332333

334+
$purchase_units = $paypal_order->purchase_units();
335+
$purchase_unit = $purchase_units[0] ?? null;
336+
337+
if ( null === $purchase_unit ) {
338+
$this->logger->error(
339+
sprintf(
340+
'[AXO] 3DS return validation failed: PayPal order has no purchase units. WC order ID: %s',
341+
$wc_order->get_id(),
342+
)
343+
);
344+
345+
return array(
346+
'result' => 'failure',
347+
'message' => __( 'Payment validation failed. Please try again.', 'woocommerce-paypal-payments' ),
348+
);
349+
}
350+
351+
/*
352+
* If the custom_id === order_id condition is true, we know that the PayPal order
353+
* and our WC order match, and there's no need for other checks.
354+
*
355+
* Specifically, we do not compare the WC_Order total amount against the
356+
* purchase unit's value, as the PU value can deviate from the WC order total, which
357+
* might reject legitimate payments.
358+
*/
359+
if ( $purchase_unit->custom_id() !== (string) $wc_order->get_id() ) {
360+
$this->logger->error(
361+
sprintf(
362+
'[AXO] 3DS return validation failed: custom_id mismatch. Token: %s, WC order ID: %s, PayPal custom_id: %s',
363+
$token,
364+
$wc_order->get_id(),
365+
$purchase_unit->custom_id()
366+
)
367+
);
368+
369+
return array(
370+
'result' => 'failure',
371+
'message' => __( 'Payment validation failed. Please try again.', 'woocommerce-paypal-payments' ),
372+
);
373+
}
374+
333375
/**
334376
* This filter controls if the method 'process()' from OrderProcessor will be called.
335377
* So you can implement your own for example on subscriptions
@@ -344,6 +386,7 @@ protected function process_3ds_return( WC_Order $wc_order, string $token ): arra
344386
}
345387
} catch ( Exception $exception ) {
346388
$this->logger->error( '[AXO] 3DS return processing failed: ' . $exception->getMessage() );
389+
347390
return array(
348391
'result' => 'failure',
349392
'message' => $this->get_user_friendly_error_message( $exception ),
@@ -453,7 +496,7 @@ protected function build_payment_source_properties( string $payment_token ): obj
453496
'single_use_token' => $payment_token,
454497
);
455498

456-
$three_d_secure = $this->settings_model->get_three_d_secure_enum();
499+
$three_d_secure = $this->settings_model->get_three_d_secure_enum();
457500

458501
if ( 'SCA_ALWAYS' === $three_d_secure || 'SCA_WHEN_REQUIRED' === $three_d_secure ) {
459502
$properties['attributes'] = array(
@@ -482,7 +525,7 @@ protected function build_order_data(): array {
482525

483526
$data['experience_context'] = $experience_context->to_array();
484527

485-
$three_d_secure = $this->settings_model->get_three_d_secure_enum();
528+
$three_d_secure = $this->settings_model->get_three_d_secure_enum();
486529

487530
if ( $three_d_secure === 'SCA_ALWAYS' || $three_d_secure === 'SCA_WHEN_REQUIRED' ) {
488531
$data['transaction_context'] = array(

modules/ppcp-blocks/resources/js/paypal-config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ export const createSubscription = async ( data, actions, config ) => {
193193

194194
return actions.subscription.create( {
195195
plan_id: planId,
196+
custom_id: config.scriptData.subscription_custom_id,
196197
} );
197198
};
198199

modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class CartActionHandler {
1414
createSubscription: ( data, actions ) => {
1515
return actions.subscription.create( {
1616
plan_id: subscriptionPlanId,
17+
custom_id: this.config.subscription_custom_id,
1718
} );
1819
},
1920
onApprove: ( data ) => {

modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class CheckoutActionHandler {
2323

2424
return actions.subscription.create( {
2525
plan_id: subscription_plan_id,
26+
custom_id: this.config.subscription_custom_id,
2627
} );
2728
},
2829
onApprove: ( data, actions ) => {

modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class SingleProductActionHandler {
2121
createSubscription: ( data, actions ) => {
2222
return actions.subscription.create( {
2323
plan_id: subscription_plan,
24+
custom_id: this.config.subscription_custom_id,
2425
} );
2526
},
2627
onApprove: ( data, actions ) => {

modules/ppcp-button/src/Assets/SmartButton.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use WC_Product;
1717
use WC_Product_Variable;
1818
use WC_Product_Variation;
19+
use WC_Session_Handler;
1920
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
2021
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
2122
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
@@ -53,6 +54,7 @@
5354
use WooCommerce\PayPalCommerce\Settings\Data\SettingsProvider;
5455
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
5556
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
57+
use WooCommerce\PayPalCommerce\Webhooks\CustomIds;
5658

5759
/**
5860
* Class SmartButton
@@ -995,6 +997,26 @@ private function need_shipping(): bool {
995997
*
996998
* @return array
997999
*/
1000+
/**
1001+
* The session-bound custom_id used to tie a PayPal subscription/order to the
1002+
* current shopper. Mirrors the value stamped on cart orders by
1003+
* PurchaseUnitFactory::from_wc_cart(); both must stay in sync so that
1004+
* ApproveSubscriptionEndpoint can validate ownership on approval.
1005+
*
1006+
* @return string
1007+
*/
1008+
private function subscription_custom_id(): string {
1009+
$session = WC()->session;
1010+
if ( $session instanceof WC_Session_Handler ) {
1011+
$session_id = $session->get_customer_unique_id();
1012+
if ( $session_id ) {
1013+
return CustomIds::CUSTOMER_ID_PREFIX . $session_id;
1014+
}
1015+
}
1016+
1017+
return '';
1018+
}
1019+
9981020
public function script_data(): array {
9991021
$is_free_trial_cart = $this->is_free_trial_cart();
10001022
$is_acdc_enabled = $this->dcc_configuration->is_acdc_enabled();
@@ -1085,6 +1107,7 @@ public function script_data(): array {
10851107
),
10861108
'cart_contains_subscription' => $this->subscription_helper->cart_contains_subscription(),
10871109
'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
1110+
'subscription_custom_id' => $this->subscription_custom_id(),
10881111
'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(),
10891112
'variable_paypal_subscription_variation_from_cart' => $this->subscription_helper->paypal_subscription_variation_from_cart(),
10901113
'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(),

modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
2828
use WooCommerce\PayPalCommerce\Settings\Data\SettingsProvider;
2929
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
30+
use WooCommerce\PayPalCommerce\Webhooks\CustomIds;
3031

3132
/**
3233
* Class ApproveOrderEndpoint
@@ -188,6 +189,24 @@ public function handle_request(): void {
188189

189190
$order = $this->api_endpoint->order( $data['order_id'] );
190191

192+
$purchase_units = $order->purchase_units();
193+
if ( ! empty( $purchase_units ) ) {
194+
$custom_id = $purchase_units[0]->custom_id();
195+
$prefix_len = strlen( CustomIds::CUSTOMER_ID_PREFIX );
196+
if ( strpos( $custom_id, CustomIds::CUSTOMER_ID_PREFIX ) === 0 ) {
197+
$order_session_id = substr( $custom_id, $prefix_len );
198+
$wc_session = WC()->session;
199+
if ( $wc_session instanceof \WC_Session_Handler ) {
200+
$current_session_id = (string) $wc_session->get_customer_unique_id();
201+
if ( $order_session_id !== $current_session_id ) {
202+
throw new RuntimeException(
203+
__( 'Order validation failed.', 'woocommerce-paypal-payments' )
204+
);
205+
}
206+
}
207+
}
208+
}
209+
191210
$payment_source = $order->payment_source();
192211

193212
if ( $payment_source && $payment_source->name() === 'card' ) {

0 commit comments

Comments
 (0)