Add save payment method for purchase later (WIP)

This commit is contained in:
Emili Castells Guasch 2023-10-26 16:36:38 +02:00
parent 9f41d5c874
commit 34cc1bffe4
8 changed files with 388 additions and 8 deletions

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
@ -249,6 +250,13 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.endpoint.payment-method-tokens' => static function( ContainerInterface $container ): PaymentMethodTokensEndpoint {
return new PaymentMethodTokensEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.repository.application-context' => static function( ContainerInterface $container ) : ApplicationContextRepository {
$settings = $container->get( 'wcgateway.settings' );

View file

@ -0,0 +1,109 @@
<?php
/**
* The Payment Method Tokens endpoint.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use Psr\Log\LoggerInterface;
use stdClass;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
class PaymentMethodTokensEndpoint {
use RequestTrait;
private string $host;
private Bearer $bearer;
private LoggerInterface $logger;
public function __construct(string $host, Bearer $bearer, LoggerInterface $logger)
{
$this->host = $host;
$this->bearer = $bearer;
$this->logger = $logger;
}
public function setup_tokens(PaymentSource $payment_source): stdClass {
$data = array(
'payment_source' => array(
$payment_source->name() => $payment_source->properties()
),
);
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v3/vault/setup-tokens';
$args = array(
'method' => 'POST',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'PayPal-Request-Id' => uniqid( 'ppcp-', true ),
),
'body' => wp_json_encode( $data ),
);
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) || ! is_array( $response ) ) {
throw new RuntimeException( 'Not able to create setup token.' );
}
$json = json_decode( $response['body'] );
$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( ! in_array( $status_code, array( 200, 201 ), true ) ) {
throw new PayPalApiException(
$json,
$status_code
);
}
return $json;
}
public function payment_tokens(PaymentSource $payment_source) {
$data = array(
'payment_source' => array(
$payment_source->name() => $payment_source->properties()
),
);
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v3/vault/payment-tokens';
$args = array(
'method' => 'POST',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'PayPal-Request-Id' => uniqid( 'ppcp-', true ),
),
'body' => wp_json_encode( $data ),
);
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) || ! is_array( $response ) ) {
throw new RuntimeException( 'Not able to create setup token.' );
}
$json = json_decode( $response['body'] );
$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( ! in_array( $status_code, array( 200, 201 ), true ) ) {
throw new PayPalApiException(
$json,
$status_code
);
}
return $json;
}
}

View file

@ -25,7 +25,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
use WooCommerce\PayPalCommerce\Button\Exception\ValidationException;
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;

View file

@ -7,6 +7,58 @@ import {
import {setVisible} from "../../../ppcp-button/resources/js/modules/Helper/Hiding";
import {loadPaypalJsScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
loadPaypalJsScript(
{
clientId: ppcp_add_payment_method.client_id,
merchantId: ppcp_add_payment_method.merchant_id,
dataUserIdToken: ppcp_add_payment_method.id_token,
},
{
createVaultSetupToken: async () => {
const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, {
method: "POST",
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce,
})
})
const result = await response.json()
if(result.data.id) {
return result.data.id
}
},
onApprove: async ({ vaultSetupToken }) => {
console.log(vaultSetupToken)
const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, {
method: "POST",
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonce: ppcp_add_payment_method.ajax.create_payment_token.nonce,
vault_setup_token: vaultSetupToken,
})
})
const result = await response.json()
console.log(result)
},
onError: (error) => {
console.error(error)
}
},
`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`
);
const init = () => {
setVisible(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL);
setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.PAYPAL);
@ -18,12 +70,6 @@ document.addEventListener(
jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () {
init()
});
loadPaypalJsScript(
{},
{},
`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`
);
}
);

View file

@ -9,6 +9,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\SavePaymentMethods;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Helper\SavePaymentMethodsApplies;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@ -52,4 +54,16 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
'save-payment-methods.endpoint.create-setup-token' => static function (ContainerInterface $container): CreateSetupToken {
return new CreateSetupToken(
$container->get( 'button.request-data' ),
$container->get('api.endpoint.payment-method-tokens')
);
},
'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')
);
}
);

View file

@ -0,0 +1,61 @@
<?php
namespace WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint;
use Exception;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
class CreatePaymentToken implements EndpointInterface {
const ENDPOINT = 'ppc-create-payment-token';
private RequestData $request_data;
private PaymentMethodTokensEndpoint $payment_method_tokens_endpoint;
public function __construct(
RequestData $request_data,
PaymentMethodTokensEndpoint $payment_method_tokens_endpoint
) {
$this->request_data = $request_data;
$this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint;
}
/**
* Returns the nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the request.
*
* @return bool
* @throws Exception On Error.
*/
public function handle_request(): bool {
try {
$data = $this->request_data->read_request( $this->nonce() );
$payment_source = new PaymentSource(
'token',
(object) array(
'id' => $data['vault_setup_token'],
'type' => 'SETUP-TOKEN',
)
);
$result = $this->payment_method_tokens_endpoint->payment_tokens( $payment_source );
wp_send_json_success( $result );
return true;
} catch ( Exception $exception ) {
wp_send_json_error();
return false;
}
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint;
use Exception;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
class CreateSetupToken implements EndpointInterface
{
const ENDPOINT = 'ppc-create-setup-token';
/**
* * The request data helper.
*
* @var RequestData
*/
private RequestData $request_data;
/**
* Payment Method Tokens endpoint.
*
* @var PaymentMethodTokensEndpoint
*/
private PaymentMethodTokensEndpoint $payment_method_tokens_endpoint;
public function __construct(
RequestData $request_data,
PaymentMethodTokensEndpoint $payment_method_tokens_endpoint
)
{
$this->request_data = $request_data;
$this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint;
}
/**
* Returns the nonce.
*
* @return string
*/
public static function nonce(): string
{
return self::ENDPOINT;
}
/**
* Handles the request.
*
* @return bool
* @throws Exception On Error.
*/
public function handle_request(): bool
{
try {
$this->request_data->read_request( $this->nonce() );
$payment_source = new PaymentSource(
'paypal',
(object) array(
'usage_type' => 'MERCHANT',
'experience_context' => (object) array(
'return_url' => esc_url( wc_get_account_endpoint_url( 'payment-methods' ) ),
'cancel_url' => esc_url( wc_get_account_endpoint_url( 'add-payment-method' ) ),
)
)
);
$result = $this->payment_method_tokens_endpoint->setup_tokens($payment_source);
wp_send_json_success($result);
return true;
} catch (Exception $exception) {
wp_send_json_error();
return false;
}
}
}

View file

@ -17,6 +17,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
@ -58,7 +60,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
try {
$target_customer_id = '';
if ( get_current_user_id() !== 0 ) {
if ( is_user_logged_in() ) {
$target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
}
@ -167,6 +169,48 @@ class SavePaymentMethodsModule implements ModuleInterface {
$c->get( 'ppcp.asset-version' ),
true
);
$api = $c->get('api.user-id-token');
assert($api instanceof UserIdToken);
try {
$target_customer_id = '';
if (is_user_logged_in()) {
$target_customer_id = get_user_meta(get_current_user_id(), '_ppcp_target_customer_id', true);
}
$id_token = $api->id_token($target_customer_id);
wp_localize_script(
'ppcp-add-payment-method',
'ppcp_add_payment_method',
array(
'client_id' => $c->get( 'button.client_id' ),
'merchant_id' => $c->get( 'api.merchant_id' ),
'id_token' => $id_token,
'ajax' => array(
'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() ),
),
),
)
);
} catch (RuntimeException $exception) {
$logger = $c->get('woocommerce.logger.woocommerce');
assert($logger instanceof LoggerInterface);
$error = $exception->getMessage();
if (is_a($exception, PayPalApiException::class)) {
$error = $exception->get_details($error);
}
$logger->error($error);
}
}
);
@ -180,5 +224,25 @@ class SavePaymentMethodsModule implements ModuleInterface {
echo '<div id="ppc-button-' . PayPalGateway::ID . '-save-payment-method"></div>';
}
);
add_action(
'wc_ajax_' . CreateSetupToken::ENDPOINT,
static function () use ( $c ) {
$endpoint = $c->get( 'save-payment-methods.endpoint.create-setup-token' );
assert($endpoint instanceof CreateSetupToken);
$endpoint->handle_request();
}
);
add_action(
'wc_ajax_' . CreatePaymentToken::ENDPOINT,
static function () use ( $c ) {
$endpoint = $c->get( 'save-payment-methods.endpoint.create-payment-token' );
assert($endpoint instanceof CreatePaymentToken);
$endpoint->handle_request();
}
);
}
}