Add my account payment method for PayPal payment

This commit is contained in:
Emili Castells Guasch 2023-10-27 12:25:42 +02:00
parent 34cc1bffe4
commit 48c5773ecd
7 changed files with 298 additions and 91 deletions

View file

@ -16,25 +16,61 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
* Class PaymentMethodTokensEndpoint
*/
class PaymentMethodTokensEndpoint { class PaymentMethodTokensEndpoint {
use RequestTrait; use RequestTrait;
private string $host; /**
private Bearer $bearer; * The host.
private LoggerInterface $logger; *
* @var string
*/
private $host;
public function __construct(string $host, Bearer $bearer, LoggerInterface $logger) /**
{ * The bearer.
$this->host = $host; *
* @var Bearer
*/
private $bearer;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* PaymentMethodTokensEndpoint constructor.
*
* @param string $host The host.
* @param Bearer $bearer The bearer.
* @param LoggerInterface $logger The logger.
*/
public function __construct( string $host, Bearer $bearer, LoggerInterface $logger ) {
$this->host = $host;
$this->bearer = $bearer; $this->bearer = $bearer;
$this->logger = $logger; $this->logger = $logger;
} }
public function setup_tokens(PaymentSource $payment_source): stdClass { /**
* Creates a setup token.
*
* @param PaymentSource $payment_source The payment source.
*
* @return stdClass
*
* @throws RuntimeException When something when wrong with the request.
* @throws PayPalApiException When something when wrong setting up the token.
*/
public function setup_tokens( PaymentSource $payment_source ): stdClass {
$data = array( $data = array(
'payment_source' => array( 'payment_source' => array(
$payment_source->name() => $payment_source->properties() $payment_source->name() => $payment_source->properties(),
), ),
); );
@ -44,9 +80,9 @@ class PaymentMethodTokensEndpoint {
$args = array( $args = array(
'method' => 'POST', 'method' => 'POST',
'headers' => array( 'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(), 'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'PayPal-Request-Id' => uniqid( 'ppcp-', true ), 'PayPal-Request-Id' => uniqid( 'ppcp-', true ),
), ),
'body' => wp_json_encode( $data ), 'body' => wp_json_encode( $data ),
); );
@ -69,10 +105,20 @@ class PaymentMethodTokensEndpoint {
return $json; return $json;
} }
public function payment_tokens(PaymentSource $payment_source) { /**
* Creates a payment token for the given payment source.
*
* @param PaymentSource $payment_source The payment source.
*
* @return stdClass
*
* @throws RuntimeException When something when wrong with the request.
* @throws PayPalApiException When something when wrong setting up the token.
*/
public function payment_tokens( PaymentSource $payment_source ): stdClass {
$data = array( $data = array(
'payment_source' => array( 'payment_source' => array(
$payment_source->name() => $payment_source->properties() $payment_source->name() => $payment_source->properties(),
), ),
); );
@ -82,9 +128,9 @@ class PaymentMethodTokensEndpoint {
$args = array( $args = array(
'method' => 'POST', 'method' => 'POST',
'headers' => array( 'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(), 'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'PayPal-Request-Id' => uniqid( 'ppcp-', true ), 'PayPal-Request-Id' => uniqid( 'ppcp-', true ),
), ),
'body' => wp_json_encode( $data ), 'body' => wp_json_encode( $data ),
); );

View file

@ -15,7 +15,7 @@ use WooCommerce\PayPalCommerce\SavePaymentMethods\Helper\SavePaymentMethodsAppli
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array( return array(
'save-payment-methods.eligible' => static function ( ContainerInterface $container ): bool { 'save-payment-methods.eligible' => static function ( ContainerInterface $container ): bool {
$save_payment_methods_applies = $container->get( 'save-payment-methods.helpers.save-payment-methods-applies' ); $save_payment_methods_applies = $container->get( 'save-payment-methods.helpers.save-payment-methods-applies' );
assert( $save_payment_methods_applies instanceof SavePaymentMethodsApplies ); assert( $save_payment_methods_applies instanceof SavePaymentMethodsApplies );
@ -43,7 +43,7 @@ return array(
) )
); );
}, },
'save-payment-methods.module.url' => static function ( ContainerInterface $container ): string { 'save-payment-methods.module.url' => static function ( ContainerInterface $container ): string {
/** /**
* The path cannot be false. * The path cannot be false.
* *
@ -54,16 +54,24 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
); );
}, },
'save-payment-methods.endpoint.create-setup-token' => static function (ContainerInterface $container): CreateSetupToken { 'save-payment-methods.endpoint.create-setup-token' => static function ( ContainerInterface $container ): CreateSetupToken {
return new CreateSetupToken( return new CreateSetupToken(
$container->get( 'button.request-data' ), $container->get( 'button.request-data' ),
$container->get('api.endpoint.payment-method-tokens') $container->get( 'api.endpoint.payment-method-tokens' )
); );
}, },
'save-payment-methods.endpoint.create-payment-token' => static function (ContainerInterface $container): CreatePaymentToken { 'save-payment-methods.wc-payment-tokens' => static function( ContainerInterface $container ): WooCommercePaymentTokens {
return new CreatePaymentToken( return new WooCommercePaymentTokens(
$container->get('button.request-data'), $container->get( 'vaulting.payment-token-helper' ),
$container->get('api.endpoint.payment-method-tokens') $container->get( 'vaulting.payment-token-factory' ),
$container->get( 'woocommerce.logger.woocommerce' )
); );
} },
'save-payment-methods.endpoint.create-payment-token' => static function ( ContainerInterface $container ): CreatePaymentToken {
return new CreatePaymentToken(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.payment-method-tokens' ),
$container->get( 'save-payment-methods.wc-payment-tokens' )
);
},
); );

View file

@ -1,4 +1,11 @@
<?php <?php
/**
* The Create Payment Token endpoint.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint; namespace WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint;
@ -7,20 +14,51 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
use WooCommerce\PayPalCommerce\SavePaymentMethods\WooCommercePaymentTokens;
/**
* Class CreatePaymentToken
*/
class CreatePaymentToken implements EndpointInterface { class CreatePaymentToken implements EndpointInterface {
const ENDPOINT = 'ppc-create-payment-token'; const ENDPOINT = 'ppc-create-payment-token';
private RequestData $request_data; /**
private PaymentMethodTokensEndpoint $payment_method_tokens_endpoint; * The request data.
*
* @var RequestData
*/
private $request_data;
/**
* The payment method tokens endpoint.
*
* @var PaymentMethodTokensEndpoint
*/
private $payment_method_tokens_endpoint;
/**
* The WC payment tokens.
*
* @var WooCommercePaymentTokens
*/
private $wc_payment_tokens;
/**
* CreatePaymentToken constructor.
*
* @param RequestData $request_data The request data.
* @param PaymentMethodTokensEndpoint $payment_method_tokens_endpoint The payment method tokens endpoint.
* @param WooCommercePaymentTokens $wc_payment_tokens The WC payment tokens.
*/
public function __construct( public function __construct(
RequestData $request_data, RequestData $request_data,
PaymentMethodTokensEndpoint $payment_method_tokens_endpoint PaymentMethodTokensEndpoint $payment_method_tokens_endpoint,
WooCommercePaymentTokens $wc_payment_tokens
) { ) {
$this->request_data = $request_data; $this->request_data = $request_data;
$this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint; $this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint;
$this->wc_payment_tokens = $wc_payment_tokens;
} }
/** /**
@ -46,11 +84,27 @@ class CreatePaymentToken implements EndpointInterface {
'token', 'token',
(object) array( (object) array(
'id' => $data['vault_setup_token'], 'id' => $data['vault_setup_token'],
'type' => 'SETUP-TOKEN', 'type' => 'SETUP_TOKEN',
) )
); );
$result = $this->payment_method_tokens_endpoint->payment_tokens( $payment_source ); $result = $this->payment_method_tokens_endpoint->payment_tokens( $payment_source );
if ( is_user_logged_in() && isset( $result->customer->id ) ) {
update_user_meta( get_current_user_id(), '_ppcp_target_customer_id', $result->customer->id );
$email = '';
if ( isset( $result->payment_source->paypal->email_address ) ) {
$email = $result->payment_source->paypal->email_address;
}
$this->wc_payment_tokens->create_payment_token_paypal(
get_current_user_id(),
$result->id,
$email
);
}
wp_send_json_success( $result ); wp_send_json_success( $result );
return true; return true;
} catch ( Exception $exception ) { } catch ( Exception $exception ) {

View file

@ -1,4 +1,11 @@
<?php <?php
/**
* The Create Setup Token endpoint.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint; namespace WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint;
@ -8,30 +15,38 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
class CreateSetupToken implements EndpointInterface /**
{ * Class CreateSetupToken
*/
class CreateSetupToken implements EndpointInterface {
const ENDPOINT = 'ppc-create-setup-token'; const ENDPOINT = 'ppc-create-setup-token';
/** /**
* * The request data helper. * The request data helper.
* *
* @var RequestData * @var RequestData
*/ */
private RequestData $request_data; private $request_data;
/** /**
* Payment Method Tokens endpoint. * Payment Method Tokens endpoint.
* *
* @var PaymentMethodTokensEndpoint * @var PaymentMethodTokensEndpoint
*/ */
private PaymentMethodTokensEndpoint $payment_method_tokens_endpoint; private $payment_method_tokens_endpoint;
/**
* CreateSetupToken constructor.
*
* @param RequestData $request_data The request data helper.
* @param PaymentMethodTokensEndpoint $payment_method_tokens_endpoint Payment Method Tokens endpoint.
*/
public function __construct( public function __construct(
RequestData $request_data, RequestData $request_data,
PaymentMethodTokensEndpoint $payment_method_tokens_endpoint PaymentMethodTokensEndpoint $payment_method_tokens_endpoint
) ) {
{ $this->request_data = $request_data;
$this->request_data = $request_data;
$this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint; $this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint;
} }
@ -40,8 +55,7 @@ class CreateSetupToken implements EndpointInterface
* *
* @return string * @return string
*/ */
public static function nonce(): string public static function nonce(): string {
{
return self::ENDPOINT; return self::ENDPOINT;
} }
@ -51,27 +65,26 @@ class CreateSetupToken implements EndpointInterface
* @return bool * @return bool
* @throws Exception On Error. * @throws Exception On Error.
*/ */
public function handle_request(): bool public function handle_request(): bool {
{
try { try {
$this->request_data->read_request( $this->nonce() ); $this->request_data->read_request( $this->nonce() );
$payment_source = new PaymentSource( $payment_source = new PaymentSource(
'paypal', 'paypal',
(object) array( (object) array(
'usage_type' => 'MERCHANT', 'usage_type' => 'MERCHANT',
'experience_context' => (object) array( 'experience_context' => (object) array(
'return_url' => esc_url( wc_get_account_endpoint_url( 'payment-methods' ) ), 'return_url' => esc_url( wc_get_account_endpoint_url( 'payment-methods' ) ),
'cancel_url' => esc_url( wc_get_account_endpoint_url( 'add-payment-method' ) ), 'cancel_url' => esc_url( wc_get_account_endpoint_url( 'add-payment-method' ) ),
) ),
) )
); );
$result = $this->payment_method_tokens_endpoint->setup_tokens($payment_source); $result = $this->payment_method_tokens_endpoint->setup_tokens( $payment_source );
wp_send_json_success($result); wp_send_json_success( $result );
return true; return true;
} catch (Exception $exception) { } catch ( Exception $exception ) {
wp_send_json_error(); wp_send_json_error();
return false; return false;
} }

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\SavePaymentMethods;
use Exception; use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use stdClass;
use WC_Order; use WC_Order;
use WC_Payment_Tokens; use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
@ -116,36 +117,20 @@ class SavePaymentMethodsModule implements ModuleInterface {
$payment_token_helper = $c->get( 'vaulting.payment-token-helper' ); $payment_token_helper = $c->get( 'vaulting.payment-token-helper' );
assert( $payment_token_helper instanceof PaymentTokenHelper ); assert( $payment_token_helper instanceof PaymentTokenHelper );
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_order->get_customer_id(), PayPalGateway::ID );
if ( $payment_token_helper->token_exist( $wc_tokens, $payment_vault_attributes->id ) ) {
return;
}
$payment_token_factory = $c->get( 'vaulting.payment-token-factory' ); $payment_token_factory = $c->get( 'vaulting.payment-token-factory' );
assert( $payment_token_factory instanceof PaymentTokenFactory ); assert( $payment_token_factory instanceof PaymentTokenFactory );
$payment_token_paypal = $payment_token_factory->create( 'paypal' ); $logger = $c->get( 'woocommerce.logger.woocommerce' );
assert( $payment_token_paypal instanceof PaymentTokenPayPal ); assert( $logger instanceof LoggerInterface );
$payment_token_paypal->set_token( $payment_vault_attributes->id ); $wc_payment_tokens = $c->get( 'save-payment-methods.wc-payment-tokens' );
$payment_token_paypal->set_user_id( $wc_order->get_customer_id() ); assert( $wc_payment_tokens instanceof WooCommercePaymentTokens );
$payment_token_paypal->set_gateway_id( PayPalGateway::ID );
$email = $order->payment_source()->properties()->email_address ?? ''; $wc_payment_tokens->create_payment_token_paypal(
if ( $email && is_email( $email ) ) { $wc_order->get_customer_id(),
$payment_token_paypal->set_email( $email ); $payment_vault_attributes->id,
} $order->payment_source()->properties()->email_address ?? ''
);
try {
$payment_token_paypal->save();
} catch ( Exception $exception ) {
$logger = $c->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );
$logger->error(
"Could not save WC payment token PayPal for order #{$wc_order->get_id()}. " . $exception->getMessage()
);
}
} }
}, },
10, 10,
@ -170,46 +155,46 @@ class SavePaymentMethodsModule implements ModuleInterface {
true true
); );
$api = $c->get('api.user-id-token'); $api = $c->get( 'api.user-id-token' );
assert($api instanceof UserIdToken); assert( $api instanceof UserIdToken );
try { try {
$target_customer_id = ''; $target_customer_id = '';
if (is_user_logged_in()) { if ( is_user_logged_in() ) {
$target_customer_id = get_user_meta(get_current_user_id(), '_ppcp_target_customer_id', true); $target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
} }
$id_token = $api->id_token($target_customer_id); $id_token = $api->id_token( $target_customer_id );
wp_localize_script( wp_localize_script(
'ppcp-add-payment-method', 'ppcp-add-payment-method',
'ppcp_add_payment_method', 'ppcp_add_payment_method',
array( array(
'client_id' => $c->get( 'button.client_id' ), 'client_id' => $c->get( 'button.client_id' ),
'merchant_id' => $c->get( 'api.merchant_id' ), 'merchant_id' => $c->get( 'api.merchant_id' ),
'id_token' => $id_token, 'id_token' => $id_token,
'ajax' => array( 'ajax' => array(
'create_setup_token' => array( 'create_setup_token' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ), 'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ),
'nonce' => wp_create_nonce( CreateSetupToken::nonce() ), 'nonce' => wp_create_nonce( CreateSetupToken::nonce() ),
), ),
'create_payment_token' => array( 'create_payment_token' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentToken::ENDPOINT ), 'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentToken::ENDPOINT ),
'nonce' => wp_create_nonce( CreatePaymentToken::nonce() ), 'nonce' => wp_create_nonce( CreatePaymentToken::nonce() ),
), ),
), ),
) )
); );
} catch (RuntimeException $exception) { } catch ( RuntimeException $exception ) {
$logger = $c->get('woocommerce.logger.woocommerce'); $logger = $c->get( 'woocommerce.logger.woocommerce' );
assert($logger instanceof LoggerInterface); assert( $logger instanceof LoggerInterface );
$error = $exception->getMessage(); $error = $exception->getMessage();
if (is_a($exception, PayPalApiException::class)) { if ( is_a( $exception, PayPalApiException::class ) ) {
$error = $exception->get_details($error); $error = $exception->get_details( $error );
} }
$logger->error($error); $logger->error( $error );
} }
} }
); );
@ -221,7 +206,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
return; return;
} }
echo '<div id="ppc-button-' . PayPalGateway::ID . '-save-payment-method"></div>'; echo '<div id="ppc-button-' . esc_attr( PayPalGateway::ID ) . '-save-payment-method"></div>';
} }
); );
@ -229,7 +214,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
'wc_ajax_' . CreateSetupToken::ENDPOINT, 'wc_ajax_' . CreateSetupToken::ENDPOINT,
static function () use ( $c ) { static function () use ( $c ) {
$endpoint = $c->get( 'save-payment-methods.endpoint.create-setup-token' ); $endpoint = $c->get( 'save-payment-methods.endpoint.create-setup-token' );
assert($endpoint instanceof CreateSetupToken); assert( $endpoint instanceof CreateSetupToken );
$endpoint->handle_request(); $endpoint->handle_request();
} }
@ -239,7 +224,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
'wc_ajax_' . CreatePaymentToken::ENDPOINT, 'wc_ajax_' . CreatePaymentToken::ENDPOINT,
static function () use ( $c ) { static function () use ( $c ) {
$endpoint = $c->get( 'save-payment-methods.endpoint.create-payment-token' ); $endpoint = $c->get( 'save-payment-methods.endpoint.create-payment-token' );
assert($endpoint instanceof CreatePaymentToken); assert( $endpoint instanceof CreatePaymentToken );
$endpoint->handle_request(); $endpoint->handle_request();
} }

View file

@ -0,0 +1,102 @@
<?php
/**
* Service to create WC Payment Tokens.
*
* @package WooCommerce\PayPalCommerce\Applepay
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\SavePaymentMethods;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class WooCommercePaymentTokens
*/
class WooCommercePaymentTokens {
/**
* The payment token helper.
*
* @var PaymentTokenHelper
*/
private $payment_token_helper;
/**
* The payment token factory.
*
* @var PaymentTokenFactory
*/
private $payment_token_factory;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* WooCommercePaymentTokens constructor.
*
* @param PaymentTokenHelper $payment_token_helper The payment token helper.
* @param PaymentTokenFactory $payment_token_factory The payment token factory.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
PaymentTokenHelper $payment_token_helper,
PaymentTokenFactory $payment_token_factory,
LoggerInterface $logger
) {
$this->payment_token_helper = $payment_token_helper;
$this->payment_token_factory = $payment_token_factory;
$this->logger = $logger;
}
/**
* Creates a WC Payment Token for PayPal payment.
*
* @param int $customer_id The WC customer ID.
* @param string $token The PayPal payment token.
* @param string $email The PayPal customer email.
*
* @return void
*/
public function create_payment_token_paypal(
int $customer_id,
string $token,
string $email
): void {
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token ) ) {
return;
}
$payment_token_paypal = $this->payment_token_factory->create( 'paypal' );
assert( $payment_token_paypal instanceof PaymentTokenPayPal );
$payment_token_paypal->set_token( $token );
$payment_token_paypal->set_user_id( $customer_id );
$payment_token_paypal->set_gateway_id( PayPalGateway::ID );
if ( $email && is_email( $email ) ) {
$payment_token_paypal->set_email( $email );
}
try {
$payment_token_paypal->save();
} catch ( Exception $exception ) {
$this->logger->error(
"Could not create WC payment token PayPal for customer {$customer_id}. " . $exception->getMessage()
);
}
}
}

View file

@ -60,7 +60,6 @@ return array(
$source $source
); );
}, },
'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
$path_to_settings_fields = __DIR__ . '/src/Settings/Fields'; $path_to_settings_fields = __DIR__ . '/src/Settings/Fields';