mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-07 19:54:15 +08:00
Create wc order in approval webhook if missing
This commit is contained in:
parent
10d99578d6
commit
46ea7621d3
8 changed files with 302 additions and 44 deletions
|
@ -28,6 +28,8 @@ const cardsSpinner = new Spinner('#ppcp-hosted-fields');
|
|||
const bootstrap = () => {
|
||||
const checkoutFormSelector = 'form.woocommerce-checkout';
|
||||
|
||||
const context = PayPalCommerceGateway.context;
|
||||
|
||||
const errorHandler = new ErrorHandler(
|
||||
PayPalCommerceGateway.labels.error.generic,
|
||||
document.querySelector(checkoutFormSelector) ?? document.querySelector('.woocommerce-notices-wrapper')
|
||||
|
@ -58,7 +60,7 @@ const bootstrap = () => {
|
|||
}
|
||||
});
|
||||
|
||||
const onSmartButtonClick = (data, actions) => {
|
||||
const onSmartButtonClick = async (data, actions) => {
|
||||
window.ppcpFundingSource = data.fundingSource;
|
||||
const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input');
|
||||
requiredFields.each((i, input) => {
|
||||
|
@ -120,13 +122,20 @@ const bootstrap = () => {
|
|||
freeTrialHandler.handle();
|
||||
return actions.reject();
|
||||
}
|
||||
|
||||
if (context === 'checkout' && !PayPalCommerceGateway.funding_sources_without_redirect.includes(data.fundingSource)) {
|
||||
try {
|
||||
await formSaver.save(form);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
const onSmartButtonsInit = () => {
|
||||
buttonsSpinner.unblock();
|
||||
};
|
||||
const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick, onSmartButtonsInit);
|
||||
const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages);
|
||||
const context = PayPalCommerceGateway.context;
|
||||
if (context === 'mini-cart' || context === 'product') {
|
||||
if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') {
|
||||
const miniCartBootstrap = new MiniCartBootstap(
|
||||
|
|
|
@ -216,7 +216,9 @@ return array(
|
|||
);
|
||||
},
|
||||
'button.checkout-form-saver' => static function ( ContainerInterface $container ): CheckoutFormSaver {
|
||||
return new CheckoutFormSaver();
|
||||
return new CheckoutFormSaver(
|
||||
$container->get( 'session.handler' )
|
||||
);
|
||||
},
|
||||
'button.endpoint.save-checkout-form' => static function ( ContainerInterface $container ): SaveCheckoutFormEndpoint {
|
||||
return new SaveCheckoutFormEndpoint(
|
||||
|
|
|
@ -173,6 +173,13 @@ class SmartButton implements SmartButtonInterface {
|
|||
*/
|
||||
private $pay_now_contexts;
|
||||
|
||||
/**
|
||||
* The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $funding_sources_without_redirect;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -208,6 +215,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
* @param bool $basic_checkout_validation_enabled Whether the basic JS validation of the form iss enabled.
|
||||
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
|
||||
* @param array $pay_now_contexts The contexts that should have the Pay Now button.
|
||||
* @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -229,6 +237,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
bool $basic_checkout_validation_enabled,
|
||||
bool $early_validation_enabled,
|
||||
array $pay_now_contexts,
|
||||
array $funding_sources_without_redirect,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
|
@ -250,6 +259,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
$this->basic_checkout_validation_enabled = $basic_checkout_validation_enabled;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->pay_now_contexts = $pay_now_contexts;
|
||||
$this->funding_sources_without_redirect = $funding_sources_without_redirect;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
@ -939,6 +949,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
'mini_cart_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ),
|
||||
'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled,
|
||||
'early_checkout_validation_enabled' => $this->early_validation_enabled,
|
||||
'funding_sources_without_redirect' => $this->funding_sources_without_redirect,
|
||||
);
|
||||
|
||||
if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) {
|
||||
|
|
|
@ -10,11 +10,30 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
||||
|
||||
use WC_Checkout;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
|
||||
/**
|
||||
* Class CheckoutFormSaver
|
||||
*/
|
||||
class CheckoutFormSaver extends WC_Checkout {
|
||||
/**
|
||||
* The Session handler.
|
||||
*
|
||||
* @var SessionHandler
|
||||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* CheckoutFormSaver constructor.
|
||||
*
|
||||
* @param SessionHandler $session_handler The session handler.
|
||||
*/
|
||||
public function __construct(
|
||||
SessionHandler $session_handler
|
||||
) {
|
||||
$this->session_handler = $session_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the form data to the WC customer and session.
|
||||
*
|
||||
|
@ -28,5 +47,7 @@ class CheckoutFormSaver extends WC_Checkout {
|
|||
$data = $this->get_posted_data();
|
||||
|
||||
$this->update_session( $data );
|
||||
|
||||
$this->session_handler->replace_checkout_form( $data );
|
||||
}
|
||||
}
|
||||
|
|
75
modules/ppcp-session/src/MemoryWcSession.php
Normal file
75
modules/ppcp-session/src/MemoryWcSession.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
/**
|
||||
* A WC_Session_Handler subclass for loading the session when it is normally not available (e.g. in webhooks).
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Session
|
||||
*
|
||||
* phpcs:disable Generic.Commenting.DocComment
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Session;
|
||||
|
||||
use WC_Session_Handler;
|
||||
|
||||
/**
|
||||
* MemoryWcSession class.
|
||||
*/
|
||||
class MemoryWcSession extends WC_Session_Handler {
|
||||
/**
|
||||
* The session data (from WC()->session->get_session).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $data;
|
||||
|
||||
/**
|
||||
* The customer ID.
|
||||
*
|
||||
* @var string|int
|
||||
*/
|
||||
private static $customer_id;
|
||||
|
||||
/**
|
||||
* Enqueues this session handler with the given data to be used by WC.
|
||||
*
|
||||
* @param array $session_data The session data (from WC()->session->get_session).
|
||||
* @param int|string $customer_id The customer ID.
|
||||
*/
|
||||
public static function replace_session_handler( array $session_data, $customer_id ): void {
|
||||
self::$data = $session_data;
|
||||
self::$customer_id = $customer_id;
|
||||
|
||||
add_filter(
|
||||
'woocommerce_session_handler',
|
||||
function () {
|
||||
return MemoryWcSession::class;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
public function init_session_cookie() {
|
||||
$this->_customer_id = self::$customer_id;
|
||||
$this->_data = self::$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
public function get_session_data() {
|
||||
return self::$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
public function forget_session() {
|
||||
self::$data = array();
|
||||
|
||||
parent::forget_session();
|
||||
}
|
||||
}
|
|
@ -47,6 +47,13 @@ class SessionHandler {
|
|||
*/
|
||||
private $funding_source = null;
|
||||
|
||||
/**
|
||||
* The checkout form data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $checkout_form = array();
|
||||
|
||||
/**
|
||||
* Returns the order.
|
||||
*
|
||||
|
@ -73,6 +80,30 @@ class SessionHandler {
|
|||
$this->store_session();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the checkout form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkout_form(): array {
|
||||
$this->load_session();
|
||||
|
||||
return $this->checkout_form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the checkout form data.
|
||||
*
|
||||
* @param array $checkout_form The checkout form data.
|
||||
*/
|
||||
public function replace_checkout_form( array $checkout_form ): void {
|
||||
$this->load_session();
|
||||
|
||||
$this->checkout_form = $checkout_form;
|
||||
|
||||
$this->store_session();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BN Code.
|
||||
*
|
||||
|
@ -153,6 +184,7 @@ class SessionHandler {
|
|||
$this->bn_code = '';
|
||||
$this->insufficient_funding_tries = 0;
|
||||
$this->funding_source = null;
|
||||
$this->checkout_form = array();
|
||||
$this->store_session();
|
||||
return $this;
|
||||
}
|
||||
|
@ -190,6 +222,7 @@ class SessionHandler {
|
|||
if ( ! is_string( $this->funding_source ) ) {
|
||||
$this->funding_source = null;
|
||||
}
|
||||
$this->checkout_form = $data['checkout_form'] ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,6 +237,7 @@ class SessionHandler {
|
|||
'bn_code' => $obj->bn_code,
|
||||
'insufficient_funding_tries' => $obj->insufficient_funding_tries,
|
||||
'funding_source' => $obj->funding_source,
|
||||
'checkout_form' => $obj->checkout_form,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,13 @@ return array(
|
|||
$payment_token_factory = $container->get( 'vaulting.payment-token-factory' );
|
||||
|
||||
return array(
|
||||
new CheckoutOrderApproved( $logger, $order_endpoint ),
|
||||
new CheckoutOrderApproved(
|
||||
$logger,
|
||||
$order_endpoint,
|
||||
$container->get( 'session.handler' ),
|
||||
$container->get( 'wcgateway.funding-source.renderer' ),
|
||||
$container->get( 'wcgateway.order-processor' )
|
||||
),
|
||||
new CheckoutOrderCompleted( $logger ),
|
||||
new CheckoutPaymentApprovalReversed( $logger ),
|
||||
new PaymentCaptureRefunded( $logger ),
|
||||
|
|
|
@ -9,11 +9,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
|
||||
|
||||
use WC_Checkout;
|
||||
use WC_Order;
|
||||
use WC_Session_Handler;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Session\MemoryWcSession;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||
|
||||
/**
|
||||
* Class CheckoutOrderApproved
|
||||
|
@ -36,15 +43,48 @@ class CheckoutOrderApproved implements RequestHandler {
|
|||
*/
|
||||
private $order_endpoint;
|
||||
|
||||
/**
|
||||
* The Session handler.
|
||||
*
|
||||
* @var SessionHandler
|
||||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* The funding source renderer.
|
||||
*
|
||||
* @var FundingSourceRenderer
|
||||
*/
|
||||
protected $funding_source_renderer;
|
||||
|
||||
/**
|
||||
* The processor for orders.
|
||||
*
|
||||
* @var OrderProcessor
|
||||
*/
|
||||
protected $order_processor;
|
||||
|
||||
/**
|
||||
* CheckoutOrderApproved constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
* @param SessionHandler $session_handler The session handler.
|
||||
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
|
||||
* @param OrderProcessor $order_processor The Order Processor.
|
||||
*/
|
||||
public function __construct( LoggerInterface $logger, OrderEndpoint $order_endpoint ) {
|
||||
$this->logger = $logger;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
OrderEndpoint $order_endpoint,
|
||||
SessionHandler $session_handler,
|
||||
FundingSourceRenderer $funding_source_renderer,
|
||||
OrderProcessor $order_processor
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->funding_source_renderer = $funding_source_renderer;
|
||||
$this->order_processor = $order_processor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,36 +117,93 @@ class CheckoutOrderApproved implements RequestHandler {
|
|||
* @return \WP_REST_Response
|
||||
*/
|
||||
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
|
||||
$custom_ids = $this->get_custom_ids_from_request( $request );
|
||||
if ( empty( $custom_ids ) ) {
|
||||
return $this->no_custom_ids_response( $request );
|
||||
}
|
||||
|
||||
try {
|
||||
$order = isset( $request['resource']['id'] ) ?
|
||||
$this->order_endpoint->order( $request['resource']['id'] ) : null;
|
||||
if ( ! $order ) {
|
||||
$message = sprintf(
|
||||
'No paypal payment for webhook event %s was found.',
|
||||
isset( $request['id'] ) ? $request['id'] : ''
|
||||
);
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
if ( $order->intent() === 'CAPTURE' ) {
|
||||
$order = $this->order_endpoint->capture( $order );
|
||||
}
|
||||
} catch ( RuntimeException $error ) {
|
||||
$message = sprintf(
|
||||
'Could not capture payment for webhook event %s.',
|
||||
isset( $request['id'] ) ? $request['id'] : ''
|
||||
$order_id = isset( $request['resource']['id'] ) ? $request['resource']['id'] : null;
|
||||
if ( ! $order_id ) {
|
||||
return $this->failure_response(
|
||||
sprintf(
|
||||
'No order ID in webhook event %s.',
|
||||
$request['id'] ?: ''
|
||||
)
|
||||
);
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$wc_orders = $this->get_wc_orders_from_custom_ids( $custom_ids );
|
||||
if ( ! $wc_orders ) {
|
||||
return $this->no_wc_orders_response( $request );
|
||||
$order = $this->order_endpoint->order( $order_id );
|
||||
|
||||
if ( $order->status()->is( OrderStatus::COMPLETED ) ) {
|
||||
return $this->success_response();
|
||||
}
|
||||
|
||||
$wc_orders = array();
|
||||
|
||||
$custom_ids = $this->get_wc_order_ids_from_request( $request );
|
||||
if ( empty( $custom_ids ) ) {
|
||||
$custom_ids = $this->get_wc_customer_ids_from_request( $request );
|
||||
if ( empty( $custom_ids ) ) {
|
||||
return $this->no_custom_ids_response( $request );
|
||||
}
|
||||
|
||||
$customer_id = $custom_ids[0];
|
||||
|
||||
$wc_session = new WC_Session_Handler();
|
||||
|
||||
$session_data = $wc_session->get_session( $customer_id );
|
||||
if ( ! is_array( $session_data ) ) {
|
||||
return $this->failure_response( "Failed to get session data {$customer_id}" );
|
||||
}
|
||||
|
||||
MemoryWcSession::replace_session_handler( $session_data, $customer_id );
|
||||
|
||||
wc_load_cart();
|
||||
WC()->cart->get_cart_from_session();
|
||||
WC()->cart->calculate_shipping();
|
||||
|
||||
$form = $this->session_handler->checkout_form();
|
||||
|
||||
$checkout = new WC_Checkout();
|
||||
$wc_order_id = $checkout->create_order( $form );
|
||||
$wc_order = wc_get_order( $wc_order_id );
|
||||
if ( ! $wc_order instanceof WC_Order ) {
|
||||
return $this->failure_response(
|
||||
sprintf(
|
||||
'Failed to create WC order in webhook event %s.',
|
||||
$request['id'] ?: ''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$funding_source = $this->session_handler->funding_source();
|
||||
if ( $funding_source ) {
|
||||
$wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $funding_source ) );
|
||||
}
|
||||
|
||||
if ( is_numeric( $customer_id ) ) {
|
||||
$wc_order->set_customer_id( (int) $customer_id );
|
||||
}
|
||||
|
||||
$wc_order->save();
|
||||
|
||||
$wc_orders[] = $wc_order;
|
||||
|
||||
add_action(
|
||||
'shutdown',
|
||||
function () use ( $customer_id ): void {
|
||||
$session = WC()->session;
|
||||
assert( $session instanceof WC_Session_Handler );
|
||||
|
||||
/**
|
||||
* Wrong type-hint.
|
||||
*
|
||||
* @psalm-suppress InvalidScalarArgument
|
||||
*/
|
||||
$session->delete_session( $customer_id );
|
||||
$session->forget_session();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$wc_orders = $this->get_wc_orders_from_custom_ids( $custom_ids );
|
||||
if ( ! $wc_orders ) {
|
||||
return $this->no_wc_orders_response( $request );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $wc_orders as $wc_order ) {
|
||||
|
@ -117,17 +214,20 @@ class CheckoutOrderApproved implements RequestHandler {
|
|||
if ( ! in_array( $wc_order->get_status(), array( 'pending', 'on-hold' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( $order->intent() === 'CAPTURE' ) {
|
||||
$wc_order->payment_complete();
|
||||
} else {
|
||||
$wc_order->update_status(
|
||||
'on-hold',
|
||||
__( 'Payment can be captured.', 'woocommerce-paypal-payments' )
|
||||
|
||||
if ( ! $this->order_processor->process( $wc_order ) ) {
|
||||
return $this->failure_response(
|
||||
sprintf(
|
||||
'Failed to process WC order %s: %s.',
|
||||
(string) $wc_order->get_id(),
|
||||
$this->order_processor->last_error()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Order %s has been updated through PayPal',
|
||||
'WC order %s has been processed after approval in PayPal.',
|
||||
(string) $wc_order->get_id()
|
||||
)
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue