Skip to content

Feature: add support for 3d secure to PayPal Commerce Gateway #7858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,15 @@ import createSubscriptionPlan from './resources/js/createSubscriptionPlan';
* @unreleased
*/
const cardFieldsOnApproveHandler: PayPalCardFieldsComponentBasics['onApprove'] = async (data) => {
payPalOrderId = data.orderID;
// @ts-ignore
const {orderID, liabilityShift} = data;
payPalOrderId = orderID

if (liabilityShift && !['POSSIBLE', 'YES'].includes(liabilityShift)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonwaldstein I was checking the link you shared in the PR description and couldn't see any response with "YES". Usually, they only return "Y" instead - check the attached screenshot.

Also, I can see in this other link that they said that "YES" is used for the liability_shift property, but as you can see in the screenshot, they API responses are using the "Y" instead.

So, I'm a bit confused about the allowed values here and wondering if we should include the "Y" on this array as well.

Thoughts?

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@glaubersilva good question! I chatted with our PayPal rep about this and they said we can simply use the liability_shift property to check for YES or POSSIBLE. However, as you pointed out there are a lot more responses we could check but that is completely up to what we want to support. To start, i'm happy to accept those simple values and reject the rest 😄

console.log('Liability shift not possible or not accepted.');
throw new Error(__('Card type and issuing bank are not ready to complete a 3D Secure authentication.', 'give'));
}

return;
};

Expand Down
22 changes: 20 additions & 2 deletions src/PaymentGateways/PayPalCommerce/PayPalCommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public function getPaymentMethodLabel(): string
}

/**
* @unreleased updated to include 3D Secure validation
* @since 4.0.0 updated to update and capture payment
* @since 2.19.0
*
Expand All @@ -98,7 +99,9 @@ public function createPayment(Donation $donation, $gatewayData): GatewayCommand

$transactionId = $payPalOrder->purchase_units[0]->payments->captures[0]->id;

} elseif ($payPalOrder->status === 'APPROVED') {
} elseif ($payPalOrder->status === 'APPROVED' || $payPalOrder->status === 'CREATED') {
$this->validate3dSecure($payPalOrder);

if ($this->shouldUpdateOrder($donation, $payPalOrder)){
$payPalOrderRepository->updateApprovedOrder($payPalOrderId, $donation->amount);
}
Expand All @@ -110,7 +113,7 @@ public function createPayment(Donation $donation, $gatewayData): GatewayCommand

$transactionId = $response->purchase_units[0]->payments->captures[0]->id;
} else {
throw new PaymentGatewayException('PayPal Order status is not approved or completed.');
throw new PaymentGatewayException('PayPal Order status is not found.');
}

give()->payment_meta->update_meta(
Expand Down Expand Up @@ -332,5 +335,20 @@ private function validatePayPalOrder(object $payPalOrder): void

throw new PaymentGatewayException($errorMessage);
}

$this->validate3dSecure($payPalOrder);
}

/**
* @unreleased
*
* @throws PaymentGatewayException
*/
private function validate3dSecure(object $payPalOrder): void
{
// Check if the order is not ready for 3D Secure authentication
if (isset($payPalOrder->payment_source->card->authentication_result->liability_shift) && !in_array($payPalOrder->payment_source->card->authentication_result->liability_shift, ['POSSIBLE', 'YES'])) {
throw new PaymentGatewayException('Card type and issuing bank are not ready to complete a 3D Secure authentication.');
}
}
}
10 changes: 8 additions & 2 deletions src/PaymentGateways/PayPalCommerce/Repositories/PayPalOrder.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
use PayPalHttp\HttpException;
use PayPalHttp\IOException;

use stdClass;

use function give_record_gateway_error as logError;

/**
Expand Down Expand Up @@ -99,6 +97,7 @@ public function approveOrder(string $orderId)
*
* @see https://developer.paypal.com/docs/api/orders/v2
*
* @unreleased updated to include 3d secure params for card payments
* @since 3.4.2 Extract the amount parameters to a separate method
* @since 3.1.0 "payer" argument is deprecated, using payment_source/paypal.
* @since 2.9.0
Expand Down Expand Up @@ -142,6 +141,13 @@ public function createOrder(array $array, string $intent = 'CAPTURE'): string
],
"email_address" => $array['payer']['email'],
],
'card' => [
'attributes' => [
'verification' => [
'method' => 'SCA_WHEN_REQUIRED'
]
]
]
],
'purchase_units' => [
$purchaseUnits
Expand Down