Skip to content

Commit

Permalink
Add support for adapted extensions in Direct Checkout (#8849)
Browse files Browse the repository at this point in the history
  • Loading branch information
cesarcosta99 committed Jun 14, 2024
1 parent 0948bf9 commit 25db37e
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 10 deletions.
4 changes: 4 additions & 0 deletions changelog/add-2648-adapted-extensions-compatibility
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Enable adapted extensions compatibility with Direct Checkout.
16 changes: 16 additions & 0 deletions client/checkout/woopay/connect/user-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class WooPayUserConnect extends WoopayConnect {
this.listeners = {
...this.listeners,
getIsUserLoggedInCallback: () => {},
getEncryptedDataCallback: () => {},
};
}

Expand All @@ -26,6 +27,18 @@ class WooPayUserConnect extends WoopayConnect {
);
}

/**
* Retrieves encrypted data from WooPay.
*
* @return {Promise<Object>} Resolves to an object with encrypted data.
*/
async getEncryptedData() {
return await this.sendMessageAndListenWith(
{ action: 'getEncryptedData' },
'getEncryptedDataCallback'
);
}

/**
* Handles the callback from the WooPayConnectIframe.
*
Expand All @@ -38,6 +51,9 @@ class WooPayUserConnect extends WoopayConnect {
case 'get_is_user_logged_in_success':
this.listeners.getIsUserLoggedInCallback( data.value );
break;
case 'get_encrypted_data_success':
this.listeners.getEncryptedDataCallback( data.value );
break;
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion client/checkout/woopay/connect/woopay-connect-iframe.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ export const WooPayConnectIframe = () => {
const fetchConfigAndSetIframeUrl = async () => {
const testMode = getConfig( 'testMode' );
const woopayHost = getConfig( 'woopayHost' );
const blogId = getConfig( 'woopayMerchantId' );
const urlParams = new URLSearchParams( {
testMode,
source_url: window.location.href,
source_url: window.location.href, // TODO: refactor this to camel case.
blogId,
} );

const tracksUserId = await getTracksIdentity();
Expand Down
13 changes: 12 additions & 1 deletion client/checkout/woopay/direct-checkout/woopay-direct-checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,21 @@ class WooPayDirectCheckout {
/**
* Checks if the user is logged in.
*
* @return {Promise<*>} Resolves to true if the user is logged in.
* @return {Promise<bool>} Resolves to true if the user is logged in.
*/
static async isUserLoggedIn() {
return this.getUserConnect().isUserLoggedIn();
}

/**
* Retrieves encrypted data from WooPay.
*
* @return {Promise<Object>} Resolves to an object with encrypted data.
*/
static async getEncryptedData() {
return this.getUserConnect().getEncryptedData();
}

/**
* Checks if third-party cookies are enabled.
*
Expand Down Expand Up @@ -368,10 +377,12 @@ class WooPayDirectCheckout {
* @return {Promise<Promise<*>|*>} Resolves to the WooPay session response.
*/
static async getEncryptedSessionData() {
const encryptedData = await this.getEncryptedData();
return request(
buildAjaxURL( getConfig( 'wcAjaxUrl' ), 'get_woopay_session' ),
{
_ajax_nonce: getConfig( 'woopaySessionNonce' ),
...( encryptedData && { encrypted_data: encryptedData } ),
}
);
}
Expand Down
7 changes: 7 additions & 0 deletions includes/admin/class-wc-rest-woopay-session-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ public function register_routes() {
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_session_data' ],
'permission_callback' => [ $this, 'check_permission' ],
'args' => [
'email' => [
'type' => 'string',
'format' => 'email',
'required' => true,
],
],
]
);
}
Expand Down
2 changes: 1 addition & 1 deletion includes/class-wc-payments-features.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public static function is_woopay_direct_checkout_enabled() {
$is_direct_checkout_eligible = is_array( $account_cache ) && ( $account_cache['platform_direct_checkout_eligible'] ?? false );
$is_direct_checkout_flag_enabled = '1' === get_option( self::WOOPAY_DIRECT_CHECKOUT_FLAG_NAME, '1' );

return $is_direct_checkout_eligible && $is_direct_checkout_flag_enabled && self::is_woopay_first_party_auth_enabled();
return $is_direct_checkout_eligible && $is_direct_checkout_flag_enabled && self::is_woopay_eligible();
}

/**
Expand Down
1 change: 1 addition & 0 deletions includes/class-wc-payments.php
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,7 @@ public static function enqueue_woopay_common_config_script() {
'testMode' => $is_test_mode,
'wcAjaxUrl' => WC_AJAX::get_endpoint( '%%endpoint%%' ),
'woopaySessionNonce' => wp_create_nonce( 'woopay_session_nonce' ),
'woopayMerchantId' => Jetpack_Options::get_option( 'id' ),
'isWooPayDirectCheckoutEnabled' => WC_Payments_Features::is_woopay_direct_checkout_enabled(),
'platformTrackerNonce' => wp_create_nonce( 'platform_tracks_nonce' ),
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
Expand Down
35 changes: 33 additions & 2 deletions includes/woopay/class-woopay-session.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,38 @@ private static function get_checkout_data( $woopay_request ) {
return $checkout_data;
}

/**
* Retrieves the user email from the current session.
*
* @param \WP_User $user The user object.
* @return string The user email.
*/
private static function get_user_email( $user ) {
if ( ! empty( $_POST['email'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return sanitize_email( wp_unslash( $_POST['email'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
}

if ( ! empty( $_GET['email'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return sanitize_email( wp_unslash( $_GET['email'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
}

if ( ! empty( $_POST['encrypted_data'] ) && is_array( $_POST['encrypted_data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
// phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$decrypted_data = WooPay_Utilities::decrypt_signed_data( $_POST['encrypted_data'] );

if ( ! empty( $decrypted_data['user_email'] ) ) {
return sanitize_email( wp_unslash( $decrypted_data['user_email'] ) );
}
}

// As a last resort, we try to get the email from the customer logged in the store.
if ( $user->exists() ) {
return $user->user_email;
}

return '';
}

/**
* Returns the initial session request data.
*
Expand Down Expand Up @@ -424,13 +456,12 @@ public static function get_init_session_request( $order_id = null, $key = null,

$cart_data = self::get_cart_data( $is_pay_for_order, $order_id, $key, $billing_email, $woopay_request );
$checkout_data = self::get_checkout_data( $woopay_request );
$email = self::get_user_email( $user );

if ( $woopay_request ) {
$order_id = $checkout_data['order_id'] ?? null;
}

$email = ! empty( $_POST['email'] ) ? wc_clean( wp_unslash( $_POST['email'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification

$request = [
'wcpay_version' => WCPAY_VERSION_NUMBER,
'user_id' => $user->ID,
Expand Down
36 changes: 36 additions & 0 deletions includes/woopay/class-woopay-utilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,42 @@ public static function encrypt_and_sign_data( $data ) {
];
}

/**
* Decode encrypted and signed data and return it.
*
* @param array $data The session, iv, and hash data for the encryption.
* @return mixed The decoded data.
*/
public static function decrypt_signed_data( $data ) {
$store_blog_token = ( self::get_woopay_url() === self::DEFAULT_WOOPAY_URL ) ? Jetpack_Options::get_option( 'blog_token' ) : 'dev_mode';

if ( empty( $store_blog_token ) ) {
return null;
}

// Decode the data.
$decoded_data_request = array_map( 'base64_decode', $data );

// Verify the HMAC hash before decryption to ensure data integrity.
$computed_hash = hash_hmac( 'sha256', $decoded_data_request['iv'] . $decoded_data_request['data'], $store_blog_token );

// If the hashes don't match, the message may have been tampered with.
if ( ! hash_equals( $computed_hash, $decoded_data_request['hash'] ) ) {
return null;
}

// Decipher the data using the blog token and the IV.
$decrypted_data = openssl_decrypt( $decoded_data_request['data'], 'aes-256-cbc', $store_blog_token, OPENSSL_RAW_DATA, $decoded_data_request['iv'] );

if ( false === $decrypted_data ) {
return null;
}

$decrypted_data = json_decode( $decrypted_data, true );

return $decrypted_data;
}

/**
* Get the persisted available countries.
*
Expand Down
7 changes: 2 additions & 5 deletions tests/unit/test-class-wc-payments-features.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ public function test_is_woopay_express_checkout_enabled_returns_false_when_woopa

public function test_is_woopay_direct_checkout_enabled_returns_true() {
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_EXPRESS_CHECKOUT_FLAG_NAME, '1' );
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_FIRST_PARTY_AUTH_FLAG_NAME, '1' );
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_DIRECT_CHECKOUT_FLAG_NAME, '1' );
$this->mock_cache->method( 'get' )->willReturn(
[
Expand All @@ -196,7 +195,6 @@ public function test_is_woopay_direct_checkout_enabled_returns_true() {

public function test_is_woopay_direct_checkout_enabled_returns_false_when_flag_is_false() {
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_EXPRESS_CHECKOUT_FLAG_NAME, '1' );
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_FIRST_PARTY_AUTH_FLAG_NAME, '1' );
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_DIRECT_CHECKOUT_FLAG_NAME, '0' );
$this->mock_cache->method( 'get' )->willReturn(
[
Expand All @@ -209,7 +207,6 @@ public function test_is_woopay_direct_checkout_enabled_returns_false_when_flag_i

public function test_is_woopay_direct_checkout_enabled_returns_false_when_woopay_eligible_is_false() {
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_EXPRESS_CHECKOUT_FLAG_NAME, '1' );
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_FIRST_PARTY_AUTH_FLAG_NAME, '1' );
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_DIRECT_CHECKOUT_FLAG_NAME, '1' );
$this->mock_cache->method( 'get' )->willReturn(
[
Expand All @@ -220,7 +217,7 @@ public function test_is_woopay_direct_checkout_enabled_returns_false_when_woopay
$this->assertFalse( WC_Payments_Features::is_woopay_direct_checkout_enabled() );
}

public function test_is_woopay_direct_checkout_enabled_returns_false_when_first_party_auth_is_disabled() {
public function test_is_woopay_direct_checkout_enabled_returns_true_when_first_party_auth_is_disabled() {
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_EXPRESS_CHECKOUT_FLAG_NAME, '1' );
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_FIRST_PARTY_AUTH_FLAG_NAME, '0' );
$this->set_feature_flag_option( WC_Payments_Features::WOOPAY_DIRECT_CHECKOUT_FLAG_NAME, '1' );
Expand All @@ -230,7 +227,7 @@ public function test_is_woopay_direct_checkout_enabled_returns_false_when_first_
'platform_direct_checkout_eligible' => true,
]
);
$this->assertFalse( WC_Payments_Features::is_woopay_direct_checkout_enabled() );
$this->assertTrue( WC_Payments_Features::is_woopay_direct_checkout_enabled() );
}

public function test_is_wcpay_frt_review_feature_active_returns_true() {
Expand Down

0 comments on commit 25db37e

Please sign in to comment.