mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge branch 'trunk' into release/1.6.0
This commit is contained in:
commit
02d5411b18
41 changed files with 6396 additions and 70 deletions
|
@ -23,6 +23,7 @@ return function ( string $root_dir ): iterable {
|
|||
( require "$modules_dir/ppcp-subscription/module.php" )(),
|
||||
( require "$modules_dir/ppcp-wc-gateway/module.php" )(),
|
||||
( require "$modules_dir/ppcp-webhooks/module.php" )(),
|
||||
( require "$modules_dir/ppcp-vaulting/module.php" )(),
|
||||
);
|
||||
|
||||
return $modules;
|
||||
|
|
|
@ -35,6 +35,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerStatusFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||
|
@ -114,6 +115,7 @@ return array(
|
|||
$container->get( 'api.host' ),
|
||||
$container->get( 'api.bearer' ),
|
||||
$container->get( 'api.factory.webhook' ),
|
||||
$container->get( 'api.factory.webhook-event' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
@ -214,6 +216,9 @@ return array(
|
|||
'api.factory.webhook' => static function ( $container ): WebhookFactory {
|
||||
return new WebhookFactory();
|
||||
},
|
||||
'api.factory.webhook-event' => static function ( $container ): WebhookEventFactory {
|
||||
return new WebhookEventFactory();
|
||||
},
|
||||
'api.factory.capture' => static function ( $container ): CaptureFactory {
|
||||
|
||||
$amount_factory = $container->get( 'api.factory.amount' );
|
||||
|
|
|
@ -11,8 +11,10 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
|
|||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
@ -44,6 +46,13 @@ class WebhookEndpoint {
|
|||
*/
|
||||
private $webhook_factory;
|
||||
|
||||
/**
|
||||
* The webhook event factory.
|
||||
*
|
||||
* @var WebhookEventFactory
|
||||
*/
|
||||
private $webhook_event_factory;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -54,22 +63,25 @@ class WebhookEndpoint {
|
|||
/**
|
||||
* WebhookEndpoint constructor.
|
||||
*
|
||||
* @param string $host The host.
|
||||
* @param Bearer $bearer The bearer.
|
||||
* @param WebhookFactory $webhook_factory The webhook factory.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param string $host The host.
|
||||
* @param Bearer $bearer The bearer.
|
||||
* @param WebhookFactory $webhook_factory The webhook factory.
|
||||
* @param WebhookEventFactory $webhook_event_factory The webhook event factory.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
string $host,
|
||||
Bearer $bearer,
|
||||
WebhookFactory $webhook_factory,
|
||||
WebhookEventFactory $webhook_event_factory,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
$this->host = $host;
|
||||
$this->bearer = $bearer;
|
||||
$this->webhook_factory = $webhook_factory;
|
||||
$this->logger = $logger;
|
||||
$this->host = $host;
|
||||
$this->bearer = $bearer;
|
||||
$this->webhook_factory = $webhook_factory;
|
||||
$this->webhook_event_factory = $webhook_event_factory;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -189,6 +201,51 @@ class WebhookEndpoint {
|
|||
return wp_remote_retrieve_response_code( $response ) === 204;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a simulated webhook to be sent.
|
||||
*
|
||||
* @param Webhook $hook The webhook subscription to use.
|
||||
* @param string $event_type The event type, such as CHECKOUT.ORDER.APPROVED.
|
||||
*
|
||||
* @return WebhookEvent
|
||||
* @throws RuntimeException If the request fails.
|
||||
* @throws PayPalApiException If the request fails.
|
||||
*/
|
||||
public function simulate( Webhook $hook, string $event_type ): WebhookEvent {
|
||||
$bearer = $this->bearer->bearer();
|
||||
$url = trailingslashit( $this->host ) . 'v1/notifications/simulate-event';
|
||||
$args = array(
|
||||
'method' => 'POST',
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $bearer->token(),
|
||||
'Content-Type' => 'application/json',
|
||||
),
|
||||
'body' => wp_json_encode(
|
||||
array(
|
||||
'webhook_id' => $hook->id(),
|
||||
'event_type' => $event_type,
|
||||
)
|
||||
),
|
||||
);
|
||||
$response = $this->request( $url, $args );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'Not able to simulate webhook.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
$json = json_decode( $response['body'] );
|
||||
$status_code = (int) wp_remote_retrieve_response_code( $response );
|
||||
if ( 202 !== $status_code ) {
|
||||
throw new PayPalApiException(
|
||||
$json,
|
||||
$status_code
|
||||
);
|
||||
}
|
||||
|
||||
return $this->webhook_event_factory->from_paypal_response( $json );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if a webhook event is legitimate.
|
||||
*
|
||||
|
|
170
modules/ppcp-api-client/src/Entity/class-webhookevent.php
Normal file
170
modules/ppcp-api-client/src/Entity/class-webhookevent.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
/**
|
||||
* The Webhook event notification object.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
|
||||
|
||||
use DateTime;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class WebhookEvent
|
||||
*/
|
||||
class WebhookEvent {
|
||||
|
||||
/**
|
||||
* The ID of the event notification.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The date and time when the event notification was created.
|
||||
*
|
||||
* @var DateTime|null
|
||||
*/
|
||||
private $create_time;
|
||||
|
||||
/**
|
||||
* The name of the resource related to the webhook notification event, such as 'checkout-order'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $resource_type;
|
||||
|
||||
/**
|
||||
* The event version in the webhook notification, such as '1.0'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $event_version;
|
||||
|
||||
/**
|
||||
* The event that triggered the webhook event notification, such as 'CHECKOUT.ORDER.APPROVED'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $event_type;
|
||||
|
||||
/**
|
||||
* A summary description for the event notification.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $summary;
|
||||
|
||||
/**
|
||||
* The resource version in the webhook notification, such as '1.0'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $resource_version;
|
||||
|
||||
/**
|
||||
* The resource that triggered the webhook event notification.
|
||||
*
|
||||
* @var stdClass
|
||||
*/
|
||||
private $resource;
|
||||
|
||||
/**
|
||||
* WebhookEvent constructor.
|
||||
*
|
||||
* @param string $id The ID of the event notification.
|
||||
* @param DateTime|null $create_time The date and time when the event notification was created.
|
||||
* @param string $resource_type The name of the resource related to the webhook notification event, such as 'checkout-order'.
|
||||
* @param string $event_version The event version in the webhook notification, such as '1.0'.
|
||||
* @param string $event_type The event that triggered the webhook event notification, such as 'CHECKOUT.ORDER.APPROVED'.
|
||||
* @param string $summary A summary description for the event notification.
|
||||
* @param string $resource_version The resource version in the webhook notification, such as '1.0'.
|
||||
* @param stdClass $resource The resource that triggered the webhook event notification.
|
||||
*/
|
||||
public function __construct( string $id, ?DateTime $create_time, string $resource_type, string $event_version, string $event_type, string $summary, string $resource_version, stdClass $resource ) {
|
||||
$this->id = $id;
|
||||
$this->create_time = $create_time;
|
||||
$this->resource_type = $resource_type;
|
||||
$this->event_version = $event_version;
|
||||
$this->event_type = $event_type;
|
||||
$this->summary = $summary;
|
||||
$this->resource_version = $resource_version;
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the event notification.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The date and time when the event notification was created.
|
||||
*
|
||||
* @return DateTime|null
|
||||
*/
|
||||
public function create_time(): ?DateTime {
|
||||
return $this->create_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the resource related to the webhook notification event, such as 'checkout-order'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function resource_type(): string {
|
||||
return $this->resource_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The event version in the webhook notification, such as '1.0'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function event_version(): string {
|
||||
return $this->event_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* The event that triggered the webhook event notification, such as 'CHECKOUT.ORDER.APPROVED'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function event_type(): string {
|
||||
return $this->event_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary description for the event notification.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function summary(): string {
|
||||
return $this->summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* The resource version in the webhook notification, such as '1.0'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function resource_version(): string {
|
||||
return $this->resource_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* The resource that triggered the webhook event notification.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function resource(): stdClass {
|
||||
return $this->resource;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
/**
|
||||
* Creates WebhookEvent.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
||||
|
||||
use DateTime;
|
||||
use stdClass;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Class WebhookEventFactory
|
||||
*/
|
||||
class WebhookEventFactory {
|
||||
|
||||
/**
|
||||
* Returns a webhook from a given data array.
|
||||
*
|
||||
* @param array $data The data array.
|
||||
*
|
||||
* @return WebhookEvent
|
||||
*/
|
||||
public function from_array( array $data ): WebhookEvent {
|
||||
return $this->from_paypal_response( (object) $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Webhook based of a PayPal JSON response.
|
||||
*
|
||||
* @param stdClass $data The JSON object.
|
||||
*
|
||||
* @return WebhookEvent
|
||||
* @throws RuntimeException When JSON object is malformed.
|
||||
*/
|
||||
public function from_paypal_response( stdClass $data ): WebhookEvent {
|
||||
if ( ! isset( $data->id ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'ID for webhook event not found.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
if ( ! isset( $data->event_type ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'Event type for webhook event not found.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
|
||||
$create_time = ( isset( $data->create_time ) ) ?
|
||||
DateTime::createFromFormat( 'Y-m-d\TH:i:sO', $data->create_time )
|
||||
: null;
|
||||
|
||||
// Sometimes the time may be in weird format 2018-12-19T22:20:32.000Z (at least in simulation),
|
||||
// we do not care much about time, so just ignore on failure.
|
||||
if ( false === $create_time ) {
|
||||
$create_time = null;
|
||||
}
|
||||
|
||||
return new WebhookEvent(
|
||||
(string) $data->id,
|
||||
$create_time,
|
||||
(string) $data->resource_type ?? '',
|
||||
(string) $data->event_version ?? '',
|
||||
(string) $data->event_type,
|
||||
(string) $data->summary ?? '',
|
||||
(string) $data->resource_version ?? '',
|
||||
(object) $data->resource ?? ( new stdClass() )
|
||||
);
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ return array(
|
|||
$subscription_helper = $container->get( 'subscription.helper' );
|
||||
$messages_apply = $container->get( 'button.helper.messages-apply' );
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
$payment_token_repository = $container->get( 'subscription.repository.payment-token' );
|
||||
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
$settings_status = $container->get( 'wcgateway.settings.status' );
|
||||
return new SmartButton(
|
||||
$container->get( 'button.url' ),
|
||||
|
|
|
@ -20,7 +20,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
|
|||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use Woocommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
|
|
@ -10,16 +10,14 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Subscription;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
'subscription.helper' => static function ( $container ): SubscriptionHelper {
|
||||
'subscription.helper' => static function ( $container ): SubscriptionHelper {
|
||||
return new SubscriptionHelper();
|
||||
},
|
||||
'subscription.renewal-handler' => static function ( $container ): RenewalHandler {
|
||||
'subscription.renewal-handler' => static function ( $container ): RenewalHandler {
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
$repository = $container->get( 'subscription.repository.payment-token' );
|
||||
$repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
$endpoint = $container->get( 'api.endpoint.order' );
|
||||
$purchase_unit_factory = $container->get( 'api.factory.purchase-unit' );
|
||||
$payer_factory = $container->get( 'api.factory.payer' );
|
||||
|
@ -31,9 +29,4 @@ return array(
|
|||
$payer_factory
|
||||
);
|
||||
},
|
||||
'subscription.repository.payment-token' => static function ( $container ): PaymentTokenRepository {
|
||||
$factory = $container->get( 'api.factory.payment-token' );
|
||||
$endpoint = $container->get( 'api.endpoint.payment-token' );
|
||||
return new PaymentTokenRepository( $factory, $endpoint );
|
||||
},
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use Dhii\Modular\Module\ModuleInterface;
|
|||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use Interop\Container\ServiceProviderInterface;
|
||||
|
@ -66,7 +66,7 @@ class SubscriptionModule implements ModuleInterface {
|
|||
add_action(
|
||||
'woocommerce_subscription_payment_complete',
|
||||
function ( $subscription ) use ( $container ) {
|
||||
$payment_token_repository = $container->get( 'subscription.repository.payment-token' );
|
||||
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
|
||||
$this->add_payment_token_id( $subscription, $payment_token_repository, $logger );
|
||||
|
@ -76,7 +76,7 @@ class SubscriptionModule implements ModuleInterface {
|
|||
add_filter(
|
||||
'woocommerce_gateway_description',
|
||||
function ( $description, $id ) use ( $container ) {
|
||||
$payment_token_repository = $container->get( 'subscription.repository.payment-token' );
|
||||
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
$subscription_helper = $container->get( 'subscription.helper' );
|
||||
|
||||
|
@ -89,7 +89,7 @@ class SubscriptionModule implements ModuleInterface {
|
|||
add_filter(
|
||||
'woocommerce_credit_card_form_fields',
|
||||
function ( $default_fields, $id ) use ( $container ) {
|
||||
$payment_token_repository = $container->get( 'subscription.repository.payment-token' );
|
||||
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
$subscription_helper = $container->get( 'subscription.helper' );
|
||||
|
||||
|
|
2
modules/ppcp-vaulting/.gitignore
vendored
Normal file
2
modules/ppcp-vaulting/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
/assets
|
12
modules/ppcp-vaulting/extensions.php
Normal file
12
modules/ppcp-vaulting/extensions.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* The vaulting module extensions.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vaulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vaulting;
|
||||
|
||||
return array();
|
16
modules/ppcp-vaulting/module.php
Normal file
16
modules/ppcp-vaulting/module.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* The vaulting module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vaulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vaulting;
|
||||
|
||||
use Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return new VaultingModule();
|
||||
};
|
23
modules/ppcp-vaulting/package.json
Normal file
23
modules/ppcp-vaulting/package.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "ppcp-vaulting",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"main": "resources/js/myaccount-payments.js",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"babel-loader": "^8.1.0",
|
||||
"cross-env": "^5.0.1",
|
||||
"file-loader": "^4.2.0",
|
||||
"node-sass": "^4.13.0",
|
||||
"sass-loader": "^8.0.0",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
"watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch",
|
||||
"dev": "cross-env BABEL_ENV=default webpack --watch"
|
||||
}
|
||||
}
|
41
modules/ppcp-vaulting/resources/js/myaccount-payments.js
Normal file
41
modules/ppcp-vaulting/resources/js/myaccount-payments.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
jQuery('.ppcp-delete-payment-button').click(async (event) => {
|
||||
event.preventDefault();
|
||||
jQuery(this).prop('disabled', true);
|
||||
const token = event.target.id;
|
||||
|
||||
const response = await fetch(
|
||||
PayPalCommerceGatewayVaulting.delete.endpoint,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(
|
||||
{
|
||||
nonce: PayPalCommerceGatewayVaulting.delete.nonce,
|
||||
token,
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
const reportError = error => {
|
||||
alert(error);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
try {
|
||||
const result = await response.json();
|
||||
reportError(result.data);
|
||||
} catch (exc) {
|
||||
console.error(exc);
|
||||
reportError(response.status);
|
||||
}
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
42
modules/ppcp-vaulting/services.php
Normal file
42
modules/ppcp-vaulting/services.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
/**
|
||||
* The vaulting module services.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vaulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vaulting;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vaulting\Assets\MyAccountPaymentsAssets;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\Endpoint\DeletePaymentTokenEndpoint;
|
||||
|
||||
return array(
|
||||
'vaulting.module-url' => static function ( $container ): string {
|
||||
return plugins_url(
|
||||
'/modules/ppcp-vaulting/',
|
||||
dirname( __FILE__, 3 ) . '/woocommerce-paypal-payments.php'
|
||||
);
|
||||
},
|
||||
'vaulting.assets.myaccount-payments' => function( $container ) : MyAccountPaymentsAssets {
|
||||
return new MyAccountPaymentsAssets(
|
||||
$container->get( 'vaulting.module-url' )
|
||||
);
|
||||
},
|
||||
'vaulting.payment-tokens-renderer' => static function (): PaymentTokensRendered {
|
||||
return new PaymentTokensRendered();
|
||||
},
|
||||
'vaulting.repository.payment-token' => static function ( $container ): PaymentTokenRepository {
|
||||
$factory = $container->get( 'api.factory.payment-token' );
|
||||
$endpoint = $container->get( 'api.endpoint.payment-token' );
|
||||
return new PaymentTokenRepository( $factory, $endpoint );
|
||||
},
|
||||
'vaulting.endpoint.delete' => function( $container ) : DeletePaymentTokenEndpoint {
|
||||
return new DeletePaymentTokenEndpoint(
|
||||
$container->get( 'vaulting.repository.payment-token' ),
|
||||
$container->get( 'button.request-data' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
);
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* Register and configure assets for My account PayPal payments page.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vaulting\Assets
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vaulting\Assets;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vaulting\Endpoint\DeletePaymentTokenEndpoint;
|
||||
|
||||
/**
|
||||
* Class MyAccountPaymentsAssets
|
||||
*/
|
||||
class MyAccountPaymentsAssets {
|
||||
|
||||
/**
|
||||
* The URL to the module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $module_url;
|
||||
|
||||
/**
|
||||
* WebhooksStatusPageAssets constructor.
|
||||
*
|
||||
* @param string $module_url The URL to the module.
|
||||
*/
|
||||
public function __construct(
|
||||
string $module_url
|
||||
) {
|
||||
$this->module_url = untrailingslashit( $module_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the necessary scripts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue(): void {
|
||||
wp_enqueue_script(
|
||||
'ppcp-vaulting-myaccount-payments',
|
||||
$this->module_url . '/assets/js/myaccount-payments.js',
|
||||
array( 'jquery' ),
|
||||
'1',
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize script.
|
||||
*/
|
||||
public function localize() {
|
||||
wp_localize_script(
|
||||
'ppcp-vaulting-myaccount-payments',
|
||||
'PayPalCommerceGatewayVaulting',
|
||||
array(
|
||||
'delete' => array(
|
||||
'endpoint' => home_url( \WC_AJAX::get_endpoint( DeletePaymentTokenEndpoint::ENDPOINT ) ),
|
||||
'nonce' => wp_create_nonce( DeletePaymentTokenEndpoint::nonce() ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
/**
|
||||
* The endpoint for deleting payment tokens.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vaulting\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vaulting\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
|
||||
/**
|
||||
* Class DeletePayment
|
||||
*/
|
||||
class DeletePaymentTokenEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-vaulting-delete';
|
||||
|
||||
/**
|
||||
* The repository.
|
||||
*
|
||||
* @var PaymentTokenRepository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* The request data.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
protected $request_data;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* DeletePaymentTokenEndpoint constructor.
|
||||
*
|
||||
* @param PaymentTokenRepository $repository The repository.
|
||||
* @param RequestData $request_data The request data.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct( PaymentTokenRepository $repository, RequestData $request_data, LoggerInterface $logger ) {
|
||||
$this->repository = $repository;
|
||||
$this->request_data = $request_data;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nonce for the endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return self::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the incoming request.
|
||||
*/
|
||||
public function handle_request() {
|
||||
try {
|
||||
$data = $this->request_data->read_request( $this->nonce() );
|
||||
|
||||
$tokens = $this->repository->all_for_user_id( get_current_user_id() );
|
||||
if ( $tokens ) {
|
||||
foreach ( $tokens as $token ) {
|
||||
if ( isset( $data['token'] ) && $token->id() === $data['token'] ) {
|
||||
if ( $this->repository->delete_token( get_current_user_id(), $token ) ) {
|
||||
wp_send_json_success();
|
||||
return true;
|
||||
}
|
||||
|
||||
wp_send_json_error( 'Could not delete payment token.' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ( Exception $error ) {
|
||||
$this->logger->error( 'Failed to delete payment: ' . $error->getMessage() );
|
||||
wp_send_json_error( $error->getMessage(), 403 );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
/**
|
||||
* The payment token repository returns or deletes payment tokens for users.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Subscription\Repository
|
||||
* @package WooCommerce\PayPalCommerce\Vaulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Subscription\Repository;
|
||||
namespace WooCommerce\PayPalCommerce\Vaulting;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokenEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
83
modules/ppcp-vaulting/src/class-paymenttokensrenderer.php
Normal file
83
modules/ppcp-vaulting/src/class-paymenttokensrenderer.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
/**
|
||||
* The payment tokens renderer.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vaulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vaulting;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
|
||||
/**
|
||||
* Class PaymentTokensRendered
|
||||
*/
|
||||
class PaymentTokensRenderer {
|
||||
|
||||
/**
|
||||
* Render payment tokens.
|
||||
*
|
||||
* @param PaymentToken[] $tokens The tokens.
|
||||
* @return false|string
|
||||
*/
|
||||
public function render( array $tokens ) {
|
||||
ob_start();
|
||||
?>
|
||||
<table class="shop_table shop_table_responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo esc_html__( 'Payment sources', 'woocommerce-paypal-payments' ); ?></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ( $tokens as $token ) {
|
||||
$source = $token->source() ?? null;
|
||||
if ( $source && isset( $source->card ) ) {
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo esc_attr( $source->card->brand ) . ' ...' . esc_attr( $source->card->last_digits ); ?></td>
|
||||
<td>
|
||||
<a class="ppcp-delete-payment-button" id="<?php echo esc_attr( $token->id() ); ?>" href=""><?php echo esc_html__( 'Delete', 'woocommerce-paypal-payments' ); ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
if ( $source && isset( $source->paypal ) ) {
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo esc_attr( $source->paypal->payer->email_address ); ?></td>
|
||||
<td>
|
||||
<a class="ppcp-delete-payment-button" id="<?php echo esc_attr( $token->id() ); ?>" href=""><?php echo esc_html__( 'Delete', 'woocommerce-paypal-payments' ); ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render no payments message.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function render_no_tokens() {
|
||||
ob_start();
|
||||
?>
|
||||
<div class="woocommerce-Message woocommerce-Message--info woocommerce-info">
|
||||
<?php echo esc_html__( 'No payments available yet.', 'woocommerce-paypal-payments' ); ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
116
modules/ppcp-vaulting/src/class-vaultingmodule.php
Normal file
116
modules/ppcp-vaulting/src/class-vaultingmodule.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
/**
|
||||
* The vaulting module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vaulting
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vaulting;
|
||||
|
||||
use Dhii\Container\ServiceProvider;
|
||||
use Dhii\Modular\Module\ModuleInterface;
|
||||
use Interop\Container\ServiceProviderInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\Endpoint\DeletePaymentTokenEndpoint;
|
||||
|
||||
/**
|
||||
* Class StatusReportModule
|
||||
*/
|
||||
class VaultingModule implements ModuleInterface {
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param ContainerInterface $container A services container instance.
|
||||
*/
|
||||
public function run( ContainerInterface $container ): void {
|
||||
|
||||
add_filter(
|
||||
'woocommerce_account_menu_items',
|
||||
function( $menu_links ) {
|
||||
$menu_links = array_slice( $menu_links, 0, 5, true )
|
||||
+ array( 'ppcp-paypal-payment-tokens' => 'PayPal payments' )
|
||||
+ array_slice( $menu_links, 5, null, true );
|
||||
|
||||
return $menu_links;
|
||||
},
|
||||
40
|
||||
);
|
||||
|
||||
add_action(
|
||||
'init',
|
||||
function () {
|
||||
add_rewrite_endpoint( 'ppcp-paypal-payment-tokens', EP_PAGES );
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_account_ppcp-paypal-payment-tokens_endpoint',
|
||||
function () use ( $container ) {
|
||||
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
$renderer = $container->get( 'vaulting.payment-tokens-renderer' );
|
||||
|
||||
$tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
|
||||
if ( $tokens ) {
|
||||
echo wp_kses_post( $renderer->render( $tokens ) );
|
||||
} else {
|
||||
echo wp_kses_post( $renderer->render_no_tokens() );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$asset_loader = $container->get( 'vaulting.assets.myaccount-payments' );
|
||||
add_action(
|
||||
'wp_enqueue_scripts',
|
||||
function () use ( $asset_loader ) {
|
||||
if ( is_account_page() && $this->is_payments_page() ) {
|
||||
$asset_loader->enqueue();
|
||||
$asset_loader->localize();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . DeletePaymentTokenEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'vaulting.endpoint.delete' );
|
||||
assert( $endpoint instanceof DeletePaymentTokenEndpoint );
|
||||
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getKey() { }
|
||||
|
||||
/**
|
||||
* Check if is payments page.
|
||||
*
|
||||
* @return bool Whethen page is payments or not.
|
||||
*/
|
||||
private function is_payments_page(): bool {
|
||||
global $wp;
|
||||
$request = explode( '/', wp_parse_url( $wp->request, PHP_URL_PATH ) );
|
||||
if ( end( $request ) === 'ppcp-paypal-payment-tokens' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
22
modules/ppcp-vaulting/webpack.config.js
Normal file
22
modules/ppcp-vaulting/webpack.config.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
const path = require('path');
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
module.exports = {
|
||||
devtool: 'sourcemap',
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
target: 'web',
|
||||
entry: {
|
||||
'myaccount-payments': path.resolve('./resources/js/myaccount-payments.js'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'assets/'),
|
||||
filename: 'js/[name].js',
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.js?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
}]
|
||||
}
|
||||
};
|
4830
modules/ppcp-vaulting/yarn.lock
Normal file
4830
modules/ppcp-vaulting/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
|
@ -78,7 +78,7 @@ return array(
|
|||
$refund_processor = $container->get( 'wcgateway.processor.refunds' );
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
$transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' );
|
||||
$payment_token_repository = $container->get( 'subscription.repository.payment-token' );
|
||||
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
$purchase_unit_factory = $container->get( 'api.factory.purchase-unit' );
|
||||
$payer_factory = $container->get( 'api.factory.payer' );
|
||||
$order_endpoint = $container->get( 'api.endpoint.order' );
|
||||
|
|
|
@ -15,7 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
|||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||
|
|
|
@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
|
|
|
@ -43,6 +43,27 @@ return array(
|
|||
),
|
||||
);
|
||||
|
||||
$is_registered = $container->get( 'webhook.is-registered' );
|
||||
if ( $is_registered ) {
|
||||
$status_page_fields = array_merge(
|
||||
$status_page_fields,
|
||||
array(
|
||||
'webhooks_simulate' => array(
|
||||
'title' => __( 'Webhook simulation', 'woocommerce-paypal-payments' ),
|
||||
'type' => 'ppcp-text',
|
||||
'text' => '<button type="button" class="button ppcp-webhooks-simulate">' . esc_html__( 'Simulate', 'woocommerce-paypal-payments' ) . '</button>',
|
||||
'screens' => array(
|
||||
State::STATE_PROGRESSIVE,
|
||||
State::STATE_ONBOARDED,
|
||||
),
|
||||
'requirements' => array(),
|
||||
'gateway' => WebhooksStatusPage::ID,
|
||||
'description' => __( 'Click to request a sample webhook payload from PayPal, allowing to check that your server can successfully receive webhooks.', 'woocommerce-paypal-payments' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return array_merge( $fields, $status_page_fields );
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
.ppcp-webhooks-table {
|
||||
.ppcp-webhooks-table, .ppcp-webhooks-status-text {
|
||||
.error {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-webhooks-table {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
|
||||
|
@ -21,3 +28,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-webhooks-status-text {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
|
|
@ -38,5 +38,111 @@ document.addEventListener(
|
|||
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const simulateBtn = jQuery(PayPalCommerceGatewayWebhooksStatus.simulation.start.button);
|
||||
simulateBtn.click(async () => {
|
||||
simulateBtn.prop('disabled', true);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
PayPalCommerceGatewayWebhooksStatus.simulation.start.endpoint,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(
|
||||
{
|
||||
nonce: PayPalCommerceGatewayWebhooksStatus.simulation.start.nonce,
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
const reportError = error => {
|
||||
const msg = PayPalCommerceGatewayWebhooksStatus.simulation.start.failureMessage + ' ' + error;
|
||||
alert(msg);
|
||||
};
|
||||
|
||||
if (!response.ok) {
|
||||
try {
|
||||
const result = await response.json();
|
||||
reportError(result.data);
|
||||
} catch (exc) {
|
||||
console.error(exc);
|
||||
reportError(response.status);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const showStatus = html => {
|
||||
let statusBlock = simulateBtn.siblings('.ppcp-webhooks-status-text');
|
||||
if (!statusBlock.length) {
|
||||
statusBlock = jQuery('<div class="ppcp-webhooks-status-text"></div>').insertAfter(simulateBtn);
|
||||
}
|
||||
statusBlock.html(html);
|
||||
};
|
||||
|
||||
simulateBtn.siblings('.description').hide();
|
||||
|
||||
showStatus(
|
||||
PayPalCommerceGatewayWebhooksStatus.simulation.state.waitingMessage +
|
||||
'<span class="spinner is-active" style="float: none;"></span>'
|
||||
);
|
||||
|
||||
const delay = 2000;
|
||||
const retriesBeforeErrorMessage = 15;
|
||||
const maxRetries = 30;
|
||||
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
await sleep(delay);
|
||||
|
||||
const stateResponse = await fetch(
|
||||
PayPalCommerceGatewayWebhooksStatus.simulation.state.endpoint,
|
||||
{
|
||||
method: 'GET',
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await stateResponse.json();
|
||||
|
||||
if (!stateResponse.ok || !result.success) {
|
||||
console.error('Simulation state query failed: ' + result.data);
|
||||
continue;
|
||||
}
|
||||
|
||||
const state = result.data.state;
|
||||
if (state === PayPalCommerceGatewayWebhooksStatus.simulation.state.successState) {
|
||||
showStatus(
|
||||
'<span class="success">' +
|
||||
'✔️ ' +
|
||||
PayPalCommerceGatewayWebhooksStatus.simulation.state.successMessage +
|
||||
'</span>'
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (exc) {
|
||||
console.error(exc);
|
||||
}
|
||||
|
||||
if (i === retriesBeforeErrorMessage) {
|
||||
showStatus(
|
||||
'<span class="error">' +
|
||||
PayPalCommerceGatewayWebhooksStatus.simulation.state.tooLongDelayMessage +
|
||||
'</span>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
simulateBtn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -10,16 +10,21 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Webhooks;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\WebhooksStatusPageAssets;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderApproved;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderCompleted;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureCompleted;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureRefunded;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||
|
||||
return array(
|
||||
|
||||
|
@ -37,16 +42,20 @@ return array(
|
|||
},
|
||||
'webhook.endpoint.controller' => function( $container ) : IncomingWebhookEndpoint {
|
||||
$webhook_endpoint = $container->get( 'api.endpoint.webhook' );
|
||||
$webhook_factory = $container->get( 'api.factory.webhook' );
|
||||
$webhook = $container->get( 'webhook.current' );
|
||||
$handler = $container->get( 'webhook.endpoint.handler' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
$verify_request = ! defined( 'PAYPAL_WEBHOOK_REQUEST_VERIFICATION' ) || PAYPAL_WEBHOOK_REQUEST_VERIFICATION;
|
||||
$webhook_event_factory = $container->get( 'api.factory.webhook-event' );
|
||||
$simulation = $container->get( 'webhook.status.simulation' );
|
||||
|
||||
return new IncomingWebhookEndpoint(
|
||||
$webhook_endpoint,
|
||||
$webhook_factory,
|
||||
$webhook,
|
||||
$logger,
|
||||
$verify_request,
|
||||
$webhook_event_factory,
|
||||
$simulation,
|
||||
... $handler
|
||||
);
|
||||
},
|
||||
|
@ -63,6 +72,29 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'webhook.current' => function( $container ) : ?Webhook {
|
||||
$data = (array) get_option( WebhookRegistrar::KEY, array() );
|
||||
if ( empty( $data ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$factory = $container->get( 'api.factory.webhook' );
|
||||
assert( $factory instanceof WebhookFactory );
|
||||
|
||||
try {
|
||||
return $factory->from_array( $data );
|
||||
} catch ( Exception $exception ) {
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
assert( $logger instanceof LoggerInterface );
|
||||
$logger->error( 'Failed to parse the stored webhook data: ' . $exception->getMessage() );
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
'webhook.is-registered' => function( $container ) : bool {
|
||||
return $container->get( 'webhook.current' ) !== null;
|
||||
},
|
||||
|
||||
'webhook.status.registered-webhooks' => function( $container ) : array {
|
||||
$endpoint = $container->get( 'api.endpoint.webhook' );
|
||||
assert( $endpoint instanceof WebhookEndpoint );
|
||||
|
@ -107,6 +139,16 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'webhook.status.simulation' => function( $container ) : WebhookSimulation {
|
||||
$webhook_endpoint = $container->get( 'api.endpoint.webhook' );
|
||||
$webhook = $container->get( 'webhook.current' );
|
||||
return new WebhookSimulation(
|
||||
$webhook_endpoint,
|
||||
$webhook,
|
||||
'PAYMENT.AUTHORIZATION.CREATED'
|
||||
);
|
||||
},
|
||||
|
||||
'webhook.status.assets' => function( $container ) : WebhooksStatusPageAssets {
|
||||
return new WebhooksStatusPageAssets(
|
||||
$container->get( 'webhook.module-url' )
|
||||
|
@ -123,6 +165,23 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'webhook.endpoint.simulate' => static function ( $container ) : SimulateEndpoint {
|
||||
$simulation = $container->get( 'webhook.status.simulation' );
|
||||
$request_data = $container->get( 'button.request-data' );
|
||||
|
||||
return new SimulateEndpoint(
|
||||
$simulation,
|
||||
$request_data
|
||||
);
|
||||
},
|
||||
'webhook.endpoint.simulation-state' => static function ( $container ) : SimulationStateEndpoint {
|
||||
$simulation = $container->get( 'webhook.status.simulation' );
|
||||
|
||||
return new SimulationStateEndpoint(
|
||||
$simulation
|
||||
);
|
||||
},
|
||||
|
||||
'webhook.module-url' => static function ( $container ): string {
|
||||
return plugins_url(
|
||||
'/modules/ppcp-webhooks/',
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/**
|
||||
* The endpoint for starting webhooks simulation.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Webhooks\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||
|
||||
/**
|
||||
* Class SimulateEndpoint
|
||||
*/
|
||||
class SimulateEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-webhooks-simulate';
|
||||
|
||||
/**
|
||||
* The simulation handler.
|
||||
*
|
||||
* @var WebhookSimulation
|
||||
*/
|
||||
private $simulation;
|
||||
|
||||
/**
|
||||
* The Request Data helper object.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* SimulateEndpoint constructor.
|
||||
*
|
||||
* @param WebhookSimulation $simulation The simulation handler.
|
||||
* @param RequestData $request_data The Request Data helper object.
|
||||
*/
|
||||
public function __construct(
|
||||
WebhookSimulation $simulation,
|
||||
RequestData $request_data
|
||||
) {
|
||||
$this->simulation = $simulation;
|
||||
$this->request_data = $request_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nonce for the endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return self::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the incoming request.
|
||||
*/
|
||||
public function handle_request() {
|
||||
try {
|
||||
// Validate nonce.
|
||||
$this->request_data->read_request( $this->nonce() );
|
||||
|
||||
$this->simulation->start();
|
||||
|
||||
wp_send_json_success();
|
||||
return true;
|
||||
} catch ( Exception $error ) {
|
||||
wp_send_json_error( $error->getMessage(), 500 );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* The endpoint for getting the current webhooks simulation state.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Webhooks\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||
|
||||
/**
|
||||
* Class SimulationStateEndpoint
|
||||
*/
|
||||
class SimulationStateEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-webhooks-simulation-state';
|
||||
|
||||
/**
|
||||
* The simulation handler.
|
||||
*
|
||||
* @var WebhookSimulation
|
||||
*/
|
||||
private $simulation;
|
||||
|
||||
/**
|
||||
* SimulationStateEndpoint constructor.
|
||||
*
|
||||
* @param WebhookSimulation $simulation The simulation handler.
|
||||
*/
|
||||
public function __construct(
|
||||
WebhookSimulation $simulation
|
||||
) {
|
||||
$this->simulation = $simulation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nonce for the endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return self::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the incoming request.
|
||||
*/
|
||||
public function handle_request() {
|
||||
try {
|
||||
$state = $this->simulation->get_state();
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'state' => $state,
|
||||
)
|
||||
);
|
||||
return true;
|
||||
} catch ( Exception $error ) {
|
||||
wp_send_json_error( $error->getMessage(), 500 );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Assets;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||
|
||||
/**
|
||||
* Class WebhooksStatusPageAssets
|
||||
|
@ -77,6 +80,21 @@ class WebhooksStatusPageAssets {
|
|||
'button' => '.ppcp-webhooks-resubscribe',
|
||||
'failureMessage' => __( 'Operation failed. Check WooCommerce logs for more details.', 'woocommerce-paypal-payments' ),
|
||||
),
|
||||
'simulation' => array(
|
||||
'start' => array(
|
||||
'endpoint' => home_url( \WC_AJAX::get_endpoint( SimulateEndpoint::ENDPOINT ) ),
|
||||
'nonce' => wp_create_nonce( SimulateEndpoint::nonce() ),
|
||||
'button' => '.ppcp-webhooks-simulate',
|
||||
'failureMessage' => __( 'Operation failed. Check WooCommerce logs for more details.', 'woocommerce-paypal-payments' ),
|
||||
),
|
||||
'state' => array(
|
||||
'endpoint' => home_url( \WC_AJAX::get_endpoint( SimulationStateEndpoint::ENDPOINT ) ),
|
||||
'successState' => WebhookSimulation::STATE_RECEIVED,
|
||||
'waitingMessage' => __( 'Waiting for the webhook to arrive...', 'woocommerce-paypal-payments' ),
|
||||
'successMessage' => __( 'The webhook was received successfully.', 'woocommerce-paypal-payments' ),
|
||||
'tooLongDelayMessage' => __( 'Looks like the webhook cannot be received. Check that your website is accessible from the internet.', 'woocommerce-paypal-payments' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
170
modules/ppcp-webhooks/src/Status/class-webhooksimulation.php
Normal file
170
modules/ppcp-webhooks/src/Status/class-webhooksimulation.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles the webhook simulation.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Webhooks\Status
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks\Status;
|
||||
|
||||
use Exception;
|
||||
use UnexpectedValueException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
|
||||
|
||||
/**
|
||||
* Class WebhookSimulation
|
||||
*/
|
||||
class WebhookSimulation {
|
||||
|
||||
public const STATE_WAITING = 'waiting';
|
||||
public const STATE_RECEIVED = 'received';
|
||||
|
||||
private const OPTION_ID = 'ppcp-webhook-simulation';
|
||||
|
||||
/**
|
||||
* The webhooks endpoint.
|
||||
*
|
||||
* @var WebhookEndpoint
|
||||
*/
|
||||
private $webhook_endpoint;
|
||||
|
||||
/**
|
||||
* Our registered webhook.
|
||||
*
|
||||
* @var Webhook|null
|
||||
*/
|
||||
private $webhook;
|
||||
|
||||
/**
|
||||
* The event type that will be simulated, such as CHECKOUT.ORDER.APPROVED.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $event_type;
|
||||
|
||||
/**
|
||||
* WebhookSimulation constructor.
|
||||
*
|
||||
* @param WebhookEndpoint $webhook_endpoint The webhooks endpoint.
|
||||
* @param Webhook|null $webhook Our registered webhook.
|
||||
* @param string $event_type The event type that will be simulated, such as CHECKOUT.ORDER.APPROVED.
|
||||
*/
|
||||
public function __construct(
|
||||
WebhookEndpoint $webhook_endpoint,
|
||||
Webhook $webhook,
|
||||
string $event_type
|
||||
) {
|
||||
$this->webhook_endpoint = $webhook_endpoint;
|
||||
$this->webhook = $webhook;
|
||||
$this->event_type = $event_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the simulation by sending request to PayPal and saving the simulation data with STATE_WAITING.
|
||||
*
|
||||
* @throws Exception If failed to start simulation.
|
||||
*/
|
||||
public function start() {
|
||||
if ( ! $this->webhook ) {
|
||||
throw new Exception( 'Webhooks not registered' );
|
||||
}
|
||||
|
||||
$event = $this->webhook_endpoint->simulate( $this->webhook, $this->event_type );
|
||||
|
||||
$this->save(
|
||||
array(
|
||||
'id' => $event->id(),
|
||||
'state' => self::STATE_WAITING,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given event matches the expected simulation event.
|
||||
*
|
||||
* @param WebhookEvent $event The webhook event.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_simulation_event( WebhookEvent $event ): bool {
|
||||
try {
|
||||
$data = $this->load();
|
||||
|
||||
return isset( $data['id'] ) && $event->id() === $data['id'];
|
||||
} catch ( Exception $exception ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the simulation state to STATE_RECEIVED if the given event matches the expected simulation event.
|
||||
*
|
||||
* @param WebhookEvent $event The webhook event.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception If failed to save new state.
|
||||
*/
|
||||
public function receive( WebhookEvent $event ): bool {
|
||||
if ( ! $this->is_simulation_event( $event ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->set_state( self::STATE_RECEIVED );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current simulation state, one of the STATE_ constants.
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception If failed to load state.
|
||||
*/
|
||||
public function get_state(): string {
|
||||
$data = $this->load();
|
||||
|
||||
return $data['state'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the new state.
|
||||
*
|
||||
* @param string $state One of the STATE_ constants.
|
||||
*
|
||||
* @throws Exception If failed to load state.
|
||||
*/
|
||||
private function set_state( string $state ): void {
|
||||
$data = $this->load();
|
||||
|
||||
$data['state'] = $state;
|
||||
|
||||
$this->save( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the simulation data.
|
||||
*
|
||||
* @param array $data The simulation data.
|
||||
*/
|
||||
private function save( array $data ): void {
|
||||
update_option( self::OPTION_ID, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current simulation data.
|
||||
*
|
||||
* @return array
|
||||
* @throws UnexpectedValueException If failed to load.
|
||||
*/
|
||||
private function load(): array {
|
||||
$data = get_option( self::OPTION_ID );
|
||||
if ( ! $data ) {
|
||||
throw new UnexpectedValueException( 'Webhook simulation data not found.' );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -9,11 +9,15 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks;
|
||||
|
||||
use phpDocumentor\Reflection\Types\This;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||
|
||||
/**
|
||||
* Class IncomingWebhookEndpoint
|
||||
|
@ -31,11 +35,11 @@ class IncomingWebhookEndpoint {
|
|||
private $webhook_endpoint;
|
||||
|
||||
/**
|
||||
* The Webhook Factory.
|
||||
* Our registered webhook.
|
||||
*
|
||||
* @var WebhookFactory
|
||||
* @var Webhook|null
|
||||
*/
|
||||
private $webhook_factory;
|
||||
private $webhook;
|
||||
|
||||
/**
|
||||
* The Request handlers.
|
||||
|
@ -58,28 +62,48 @@ class IncomingWebhookEndpoint {
|
|||
*/
|
||||
private $verify_request;
|
||||
|
||||
/**
|
||||
* The webhook event factory.
|
||||
*
|
||||
* @var WebhookEventFactory
|
||||
*/
|
||||
private $webhook_event_factory;
|
||||
|
||||
/**
|
||||
* The simulation handler.
|
||||
*
|
||||
* @var WebhookSimulation
|
||||
*/
|
||||
private $simulation;
|
||||
|
||||
/**
|
||||
* IncomingWebhookEndpoint constructor.
|
||||
*
|
||||
* @param WebhookEndpoint $webhook_endpoint The webhook endpoint.
|
||||
* @param WebhookFactory $webhook_factory The webhook factory.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param bool $verify_request Whether requests need to be verified or not.
|
||||
* @param RequestHandler ...$handlers The handlers, which process a request in the end.
|
||||
* @param WebhookEndpoint $webhook_endpoint The webhook endpoint.
|
||||
* @param Webhook|null $webhook Our registered webhook.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param bool $verify_request Whether requests need to be verified or not.
|
||||
* @param WebhookEventFactory $webhook_event_factory The webhook event factory.
|
||||
* @param WebhookSimulation $simulation The simulation handler.
|
||||
* @param RequestHandler ...$handlers The handlers, which process a request in the end.
|
||||
*/
|
||||
public function __construct(
|
||||
WebhookEndpoint $webhook_endpoint,
|
||||
WebhookFactory $webhook_factory,
|
||||
?Webhook $webhook,
|
||||
LoggerInterface $logger,
|
||||
bool $verify_request,
|
||||
WebhookEventFactory $webhook_event_factory,
|
||||
WebhookSimulation $simulation,
|
||||
RequestHandler ...$handlers
|
||||
) {
|
||||
|
||||
$this->webhook_endpoint = $webhook_endpoint;
|
||||
$this->webhook_factory = $webhook_factory;
|
||||
$this->handlers = $handlers;
|
||||
$this->logger = $logger;
|
||||
$this->verify_request = $verify_request;
|
||||
$this->webhook_endpoint = $webhook_endpoint;
|
||||
$this->webhook = $webhook;
|
||||
$this->handlers = $handlers;
|
||||
$this->logger = $logger;
|
||||
$this->verify_request = $verify_request;
|
||||
$this->webhook_event_factory = $webhook_event_factory;
|
||||
$this->simulation = $simulation;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,35 +134,34 @@ class IncomingWebhookEndpoint {
|
|||
/**
|
||||
* Verifies the current request.
|
||||
*
|
||||
* @param \WP_REST_Request $request The request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function verify_request(): bool {
|
||||
public function verify_request( \WP_REST_Request $request ): bool {
|
||||
if ( ! $this->verify_request ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! $this->webhook ) {
|
||||
$this->logger->error( 'Failed to retrieve stored webhook data.' );
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = (array) get_option( WebhookRegistrar::KEY, array() );
|
||||
$webhook = $this->webhook_factory->from_array( $data );
|
||||
$result = $this->webhook_endpoint->verify_current_request_for_webhook( $webhook );
|
||||
$event = $this->event_from_request( $request );
|
||||
|
||||
if ( $this->simulation->is_simulation_event( $event ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = $this->webhook_endpoint->verify_current_request_for_webhook( $this->webhook );
|
||||
if ( ! $result ) {
|
||||
$this->logger->log(
|
||||
'error',
|
||||
__( 'Illegit Webhook request detected.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
$this->logger->error( 'Webhook verification failed.' );
|
||||
}
|
||||
return $result;
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$this->logger->log(
|
||||
'error',
|
||||
sprintf(
|
||||
// translators: %s is the error message.
|
||||
__(
|
||||
'Illegit Webhook request detected: %s',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
$exception->getMessage()
|
||||
)
|
||||
);
|
||||
$this->logger->error( 'Webhook verification failed: ' . $exception->getMessage() );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +174,17 @@ class IncomingWebhookEndpoint {
|
|||
* @return \WP_REST_Response
|
||||
*/
|
||||
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
|
||||
$event = $this->event_from_request( $request );
|
||||
|
||||
if ( $this->simulation->is_simulation_event( $event ) ) {
|
||||
$this->logger->info( 'Received simulated webhook.' );
|
||||
$this->simulation->receive( $event );
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $this->handlers as $handler ) {
|
||||
if ( $handler->responsible_for_request( $request ) ) {
|
||||
|
@ -211,4 +245,16 @@ class IncomingWebhookEndpoint {
|
|||
}
|
||||
return array_unique( $event_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates WebhookEvent from request data.
|
||||
*
|
||||
* @param \WP_REST_Request $request The request with event data.
|
||||
*
|
||||
* @return WebhookEvent
|
||||
* @throws RuntimeException When failed to create.
|
||||
*/
|
||||
private function event_from_request( \WP_REST_Request $request ): WebhookEvent {
|
||||
return $this->webhook_event_factory->from_array( $request->get_params() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ use Psr\Container\ContainerInterface;
|
|||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\WebhooksStatusPageAssets;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
|
||||
|
||||
/**
|
||||
|
@ -94,6 +96,25 @@ class WebhookModule implements ModuleInterface {
|
|||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . SimulateEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'webhook.endpoint.simulate' );
|
||||
assert( $endpoint instanceof SimulateEndpoint );
|
||||
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
add_action(
|
||||
'wc_ajax_' . SimulationStateEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'webhook.endpoint.simulation-state' );
|
||||
assert( $endpoint instanceof SimulationStateEndpoint );
|
||||
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
$page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' );
|
||||
if ( WebhooksStatusPage::ID === $page_id ) {
|
||||
$GLOBALS['hide_save_button'] = true;
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
"install:modules:ppcp-button": "cd modules/ppcp-button && yarn install && cd -",
|
||||
"install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install && cd -",
|
||||
"install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install && cd -",
|
||||
"install:modules": "yarn run install:modules:ppcp-button && yarn run install:modules:ppcp-wc-gateway && yarn run install:modules:ppcp-webhooks",
|
||||
"install:modules:ppcp-vaulting": "cd modules/ppcp-vaulting && yarn install && cd -",
|
||||
"install:modules": "yarn run install:modules:ppcp-button && yarn run install:modules:ppcp-wc-gateway && yarn run install:modules:ppcp-webhooks && yarn run install:modules:ppcp-vaulting",
|
||||
"build:modules:ppcp-button": "cd modules/ppcp-button && yarn run build && cd -",
|
||||
"build:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run build && cd -",
|
||||
"build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build && cd -",
|
||||
"build:modules": "yarn run build:modules:ppcp-button && yarn build:modules:ppcp-wc-gateway && yarn build:modules:ppcp-webhooks",
|
||||
"build:modules:ppcp-vaulting": "cd modules/ppcp-vaulting && yarn run build && cd -",
|
||||
"build:modules": "yarn run build:modules:ppcp-button && yarn build:modules:ppcp-wc-gateway && yarn build:modules:ppcp-webhooks && yarn build:modules:ppcp-vaulting",
|
||||
"build:dev": "yarn run install:modules && yarn run build:modules",
|
||||
|
||||
"docker:build": "docker-compose build",
|
||||
|
|
|
@ -11,10 +11,10 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository;
|
||||
use WooCommerce\PayPalCommerce\TestCase;
|
||||
use Mockery;
|
||||
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
|
||||
class RenewalHandlerTest extends TestCase
|
||||
{
|
||||
|
|
|
@ -10,6 +10,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
|
|||
use WooCommerce\PayPalCommerce\TestCase;
|
||||
use Mockery;
|
||||
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||
use function Brain\Monkey\Functions\expect;
|
||||
use function Brain\Monkey\Functions\when;
|
||||
|
||||
|
|
76
tests/PHPUnit/Webhooks/Status/WebhookSimulationTest.php
Normal file
76
tests/PHPUnit/Webhooks/Status/WebhookSimulationTest.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks\Status;
|
||||
|
||||
use Mockery;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
|
||||
use WooCommerce\PayPalCommerce\TestCase;
|
||||
use function Brain\Monkey\Functions\when;
|
||||
|
||||
class WebhookSimulationTest extends TestCase
|
||||
{
|
||||
private $webhook_endpoint;
|
||||
|
||||
private $webhook;
|
||||
|
||||
private $event_type = 'CHECKOUT.ORDER.APPROVED';
|
||||
|
||||
private $sut;
|
||||
|
||||
private $storage;
|
||||
|
||||
private $event_id = '123';
|
||||
private $event;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->webhook_endpoint = Mockery::mock(WebhookEndpoint::class);
|
||||
$this->webhook = new Webhook('https://example.com', []);
|
||||
|
||||
$this->sut = new WebhookSimulation($this->webhook_endpoint, $this->webhook, $this->event_type);
|
||||
|
||||
when('update_option')->alias(function ($key, $value) {
|
||||
$this->storage[$key] = $value;
|
||||
});
|
||||
when('get_option')->alias(function ($key, $default = false) {
|
||||
return $this->storage[$key] ?? $default;
|
||||
});
|
||||
|
||||
$this->event = $this->createEvent($this->event_id);
|
||||
}
|
||||
|
||||
public function testSimulation()
|
||||
{
|
||||
$this->webhook_endpoint
|
||||
->expects('simulate')
|
||||
->with($this->webhook, $this->event_type)
|
||||
->andReturn($this->event);
|
||||
|
||||
$this->sut->start();
|
||||
|
||||
self::assertTrue($this->sut->is_simulation_event($this->createEvent($this->event_id)));
|
||||
self::assertFalse($this->sut->is_simulation_event($this->createEvent('456')));
|
||||
|
||||
self::assertFalse($this->sut->receive($this->createEvent('456')));
|
||||
|
||||
self::assertEquals(WebhookSimulation::STATE_WAITING, $this->sut->get_state());
|
||||
|
||||
self::assertTrue($this->sut->receive($this->createEvent($this->event_id)));
|
||||
self::assertEquals(WebhookSimulation::STATE_RECEIVED, $this->sut->get_state());
|
||||
}
|
||||
|
||||
public function testIsSimulationNeverThrows()
|
||||
{
|
||||
self::assertFalse($this->sut->is_simulation_event($this->createEvent($this->event_id)));
|
||||
}
|
||||
|
||||
private function createEvent(string $id): WebhookEvent
|
||||
{
|
||||
return new WebhookEvent($id, null, '', '', $this->event_type, '', '', (object) []);
|
||||
}
|
||||
}
|
|
@ -88,6 +88,7 @@ define( 'PPCP_FLAG_SUBSCRIPTION', true );
|
|||
function () {
|
||||
init();
|
||||
do_action( 'woocommerce_paypal_payments_gateway_activate' );
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
);
|
||||
register_deactivation_hook(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue