Cleanup gateways, move process_payment

This commit is contained in:
Alex P 2022-07-13 09:51:23 +03:00
parent 0ce3af03c3
commit 1a5eb500a5
5 changed files with 342 additions and 370 deletions

View file

@ -59,7 +59,6 @@ return array(
$order_processor = $container->get( 'wcgateway.order-processor' );
$settings_renderer = $container->get( 'wcgateway.settings.render' );
$funding_source_renderer = $container->get( 'wcgateway.funding-source.renderer' );
$authorized_payments = $container->get( 'wcgateway.processor.authorized-payments' );
$settings = $container->get( 'wcgateway.settings' );
$session_handler = $container->get( 'session.handler' );
$refund_processor = $container->get( 'wcgateway.processor.refunds' );
@ -68,8 +67,6 @@ return array(
$subscription_helper = $container->get( 'subscription.helper' );
$page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' );
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
$payments_endpoint = $container->get( 'api.endpoint.payments' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$environment = $container->get( 'onboarding.environment' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$api_shop_country = $container->get( 'api.shop.country' );
@ -77,7 +74,6 @@ return array(
$settings_renderer,
$funding_source_renderer,
$order_processor,
$authorized_payments,
$settings,
$session_handler,
$refund_processor,
@ -87,10 +83,7 @@ return array(
$page_id,
$environment,
$payment_token_repository,
$container->get( 'api.factory.shipping-preference' ),
$logger,
$payments_endpoint,
$order_endpoint,
$api_shop_country
);
},

View file

@ -9,20 +9,30 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
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\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use Psr\Container\ContainerInterface;
@ -31,7 +41,7 @@ use Psr\Container\ContainerInterface;
*/
class CreditCardGateway extends \WC_Payment_Gateway_CC {
use ProcessPaymentTrait;
use ProcessPaymentTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait;
const ID = 'ppcp-credit-card-gateway';
@ -203,15 +213,25 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
Environment $environment,
PaymentsEndpoint $payments_endpoint
) {
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
$this->order_processor = $order_processor;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->settings_renderer = $settings_renderer;
$this->config = $config;
$this->module_url = $module_url;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->state = $state;
$this->transaction_url_provider = $transaction_url_provider;
$this->payment_token_repository = $payment_token_repository;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->payer_factory = $payer_factory;
$this->order_endpoint = $order_endpoint;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
$this->environment = $environment;
$this->payments_endpoint = $payments_endpoint;
if ( $state->current_state() === State::STATE_ONBOARDED ) {
$this->supports = array( 'refunds' );
@ -261,18 +281,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
'process_admin_options',
)
);
$this->module_url = $module_url;
$this->payment_token_repository = $payment_token_repository;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->payer_factory = $payer_factory;
$this->order_endpoint = $order_endpoint;
$this->transaction_url_provider = $transaction_url_provider;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
$this->payments_endpoint = $payments_endpoint;
$this->state = $state;
}
/**
@ -301,7 +309,6 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
* @return string
*/
public function generate_ppcp_html(): string {
ob_start();
$this->settings_renderer->render();
$content = ob_get_contents();
@ -409,6 +416,166 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
return $this->is_enabled();
}
/**
* Process payment for a WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
null,
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
);
}
/**
* If customer has chosen a saved credit card payment.
*/
$saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING );
$change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING );
if ( $saved_credit_card && ! isset( $change_payment ) ) {
$user_id = (int) $wc_order->get_customer_id();
$customer = new \WC_Customer( $user_id );
$tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() );
$selected_token = null;
foreach ( $tokens as $token ) {
if ( $token->id() === $saved_credit_card ) {
$selected_token = $token;
break;
}
}
if ( ! $selected_token ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( 'Saved card token not found.' ) )
);
}
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payer = $this->payer_factory->from_customer( $customer );
$shipping_preference = $this->shipping_preference_factory->from_state(
$purchase_unit,
''
);
try {
$order = $this->order_endpoint->create(
array( $purchase_unit ),
$shipping_preference,
$payer,
$selected_token
);
$this->add_paypal_meta( $wc_order, $order, $this->environment() );
if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( "Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}." ) )
);
}
if ( ! in_array(
$order->intent(),
array( 'CAPTURE', 'AUTHORIZE' ),
true
) ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( "Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}." ) )
);
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' );
}
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$this->update_transaction_id( $transaction_id, $wc_order );
}
$this->handle_new_order_status( $order, $wc_order );
if ( $this->is_free_trial_order( $wc_order ) ) {
$this->authorized_payments_processor->void_authorizations( $order );
$wc_order->payment_complete();
} elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) {
$this->authorized_payments_processor->capture_authorized_payment( $wc_order );
}
return $this->handle_payment_success( $wc_order );
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* If customer has chosen change Subscription payment.
*/
if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) {
if ( $saved_credit_card ) {
update_post_meta( $order_id, 'payment_token_id', $saved_credit_card );
return $this->handle_payment_success( $wc_order );
}
}
/**
* If the WC_Order is paid through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return $this->handle_payment_success( $wc_order );
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
if ( ! $this->order_processor->process( $wc_order ) ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
$this->order_processor->last_error()
)
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
as_schedule_single_action(
time() + ( 1 * MINUTE_IN_SECONDS ),
'woocommerce_paypal_payments_check_saved_payment',
array(
'order_id' => $order_id,
'customer_id' => $wc_order->get_customer_id(),
'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '',
)
);
}
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* Process refund.

View file

@ -9,18 +9,21 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
@ -32,7 +35,7 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
*/
class PayPalGateway extends \WC_Payment_Gateway {
use ProcessPaymentTrait;
use ProcessPaymentTrait, FreeTrialHandlerTrait;
const ID = 'ppcp-gateway';
const INTENT_META_KEY = '_ppcp_paypal_intent';
@ -63,13 +66,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
protected $order_processor;
/**
* The processor for authorized payments.
*
* @var AuthorizedPaymentsProcessor
*/
protected $authorized_payments_processor;
/**
* The settings.
*
@ -119,27 +115,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
protected $payment_token_repository;
/**
* The shipping_preference factory.
*
* @var ShippingPreferenceFactory
*/
private $shipping_preference_factory;
/**
* The payments endpoint
*
* @var PaymentsEndpoint
*/
protected $payments_endpoint;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
protected $order_endpoint;
/**
* Whether the plugin is in onboarded state.
*
@ -178,30 +153,25 @@ class PayPalGateway extends \WC_Payment_Gateway {
/**
* PayPalGateway constructor.
*
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param OrderProcessor $order_processor The Order Processor.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
* @param ContainerInterface $config The settings.
* @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param State $state The state.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param Environment $environment The environment.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory.
* @param LoggerInterface $logger The logger.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param string $api_shop_country The api shop country.
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param OrderProcessor $order_processor The Order Processor.
* @param ContainerInterface $config The settings.
* @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param State $state The state.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param Environment $environment The environment.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param LoggerInterface $logger The logger.
* @param string $api_shop_country The api shop country.
*/
public function __construct(
SettingsRenderer $settings_renderer,
FundingSourceRenderer $funding_source_renderer,
OrderProcessor $order_processor,
AuthorizedPaymentsProcessor $authorized_payments_processor,
ContainerInterface $config,
SessionHandler $session_handler,
RefundProcessor $refund_processor,
@ -211,37 +181,25 @@ class PayPalGateway extends \WC_Payment_Gateway {
string $page_id,
Environment $environment,
PaymentTokenRepository $payment_token_repository,
ShippingPreferenceFactory $shipping_preference_factory,
LoggerInterface $logger,
PaymentsEndpoint $payments_endpoint,
OrderEndpoint $order_endpoint,
string $api_shop_country
) {
$this->id = self::ID;
$this->order_processor = $order_processor;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->settings_renderer = $settings_renderer;
$this->funding_source_renderer = $funding_source_renderer;
$this->config = $config;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->page_id = $page_id;
$this->environment = $environment;
$this->onboarded = $state->current_state() === State::STATE_ONBOARDED;
$this->id = self::ID;
$this->order_processor = $order_processor;
$this->authorized_payments = $authorized_payments_processor;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->settings_renderer = $settings_renderer;
$this->config = $config;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->page_id = $page_id;
$this->environment = $environment;
$this->logger = $logger;
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
$this->funding_source_renderer = $funding_source_renderer;
$this->order_processor = $order_processor;
$this->config = $config;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->state = $state;
$this->transaction_url_provider = $transaction_url_provider;
$this->subscription_helper = $subscription_helper;
$this->page_id = $page_id;
$this->environment = $environment;
$this->onboarded = $state->current_state() === State::STATE_ONBOARDED;
$this->payment_token_repository = $payment_token_repository;
$this->logger = $logger;
$this->api_shop_country = $api_shop_country;
if ( $this->onboarded ) {
$this->supports = array( 'refunds' );
@ -291,13 +249,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
'process_admin_options',
)
);
$this->subscription_helper = $subscription_helper;
$this->payment_token_repository = $payment_token_repository;
$this->logger = $logger;
$this->payments_endpoint = $payments_endpoint;
$this->order_endpoint = $order_endpoint;
$this->state = $state;
$this->api_shop_country = $api_shop_country;
}
/**
@ -306,7 +257,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
* @return bool
*/
public function needs_setup(): bool {
return ! $this->onboarded;
}
@ -340,9 +290,8 @@ class PayPalGateway extends \WC_Payment_Gateway {
* @return string
*/
public function generate_ppcp_html(): string {
ob_start();
$this->settings_renderer->render( false );
$this->settings_renderer->render();
$content = ob_get_contents();
ob_end_clean();
return $content;
@ -450,6 +399,126 @@ class PayPalGateway extends \WC_Payment_Gateway {
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
/**
* Process payment for a WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
null,
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
);
}
$funding_source = filter_input( INPUT_POST, 'ppcp-funding-source', FILTER_SANITIZE_STRING );
if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) {
$user_id = (int) $wc_order->get_customer_id();
$tokens = $this->payment_token_repository->all_for_user_id( $user_id );
if ( ! array_filter(
$tokens,
function ( PaymentToken $token ): bool {
return isset( $token->source()->paypal );
}
) ) {
return $this->handle_payment_failure( $wc_order, new Exception( 'No saved PayPal account.' ) );
}
$wc_order->payment_complete();
return $this->handle_payment_success( $wc_order );
}
/**
* If customer has chosen change Subscription payment.
*/
if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) {
$saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING );
if ( $saved_paypal_payment ) {
update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment );
return $this->handle_payment_success( $wc_order );
}
}
/**
* If the WC_Order is paid through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return $this->handle_payment_success( $wc_order );
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
if ( ! $this->order_processor->process( $wc_order ) ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
$this->order_processor->last_error()
)
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
as_schedule_single_action(
time() + ( 1 * MINUTE_IN_SECONDS ),
'woocommerce_paypal_payments_check_saved_payment',
array(
'order_id' => $order_id,
'customer_id' => $wc_order->get_customer_id(),
'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '',
)
);
}
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) {
$wc_order->update_status(
'failed',
__( 'Instrument declined. ', 'woocommerce-paypal-payments' ) . $error->details()[0]->description ?? ''
);
$this->session_handler->increment_insufficient_funding_tries();
if ( $this->session_handler->insufficient_funding_tries() >= 3 ) {
return $this->handle_payment_failure(
null,
new Exception(
__( 'Please use a different payment method.', 'woocommerce-paypal-payments' ),
$error->getCode(),
$error
)
);
}
$host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ?
'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/';
$url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id();
return array(
'result' => 'success',
'redirect' => $url,
);
}
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* Process refund.
*

View file

@ -12,243 +12,12 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Throwable;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
/**
* Trait ProcessPaymentTrait
*/
trait ProcessPaymentTrait {
use OrderMetaTrait, PaymentsStatusHandlingTrait, TransactionIdHandlingTrait, FreeTrialHandlerTrait;
/**
* Process a payment for an WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*
* @throws RuntimeException When processing payment fails.
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
null,
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
);
}
$payment_method = filter_input( INPUT_POST, 'payment_method', FILTER_SANITIZE_STRING );
$funding_source = filter_input( INPUT_POST, 'ppcp-funding-source', FILTER_SANITIZE_STRING );
/**
* If customer has chosen a saved credit card payment.
*/
$saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING );
$change_payment = filter_input( INPUT_POST, 'woocommerce_change_payment', FILTER_SANITIZE_STRING );
if ( CreditCardGateway::ID === $payment_method && $saved_credit_card && ! isset( $change_payment ) ) {
$user_id = (int) $wc_order->get_customer_id();
$customer = new \WC_Customer( $user_id );
$tokens = $this->payment_token_repository->all_for_user_id( (int) $customer->get_id() );
$selected_token = null;
foreach ( $tokens as $token ) {
if ( $token->id() === $saved_credit_card ) {
$selected_token = $token;
break;
}
}
if ( ! $selected_token ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( 'Saved card token not found.' ) )
);
}
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payer = $this->payer_factory->from_customer( $customer );
$shipping_preference = $this->shipping_preference_factory->from_state(
$purchase_unit,
''
);
try {
$order = $this->order_endpoint->create(
array( $purchase_unit ),
$shipping_preference,
$payer,
$selected_token
);
$this->add_paypal_meta( $wc_order, $order, $this->environment() );
if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( "Unexpected status for order {$order->id()} using a saved card: {$order->status()->name()}." ) )
);
}
if ( ! in_array(
$order->intent(),
array( 'CAPTURE', 'AUTHORIZE' ),
true
) ) {
return $this->handle_payment_failure(
$wc_order,
new GatewayGenericException( new Exception( "Could neither capture nor authorize order {$order->id()} using a saved card. Status: {$order->status()->name()}. Intent: {$order->intent()}." ) )
);
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' );
}
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$this->update_transaction_id( $transaction_id, $wc_order );
}
$this->handle_new_order_status( $order, $wc_order );
if ( $this->is_free_trial_order( $wc_order ) ) {
$this->authorized_payments_processor->void_authorizations( $order );
$wc_order->payment_complete();
} elseif ( $this->config->has( 'intent' ) && strtoupper( (string) $this->config->get( 'intent' ) ) === 'CAPTURE' ) {
$this->authorized_payments_processor->capture_authorized_payment( $wc_order );
}
return $this->handle_payment_success( $wc_order );
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
if ( PayPalGateway::ID === $payment_method && 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) {
$user_id = (int) $wc_order->get_customer_id();
$tokens = $this->payment_token_repository->all_for_user_id( $user_id );
if ( ! array_filter(
$tokens,
function ( PaymentToken $token ): bool {
return isset( $token->source()->paypal );
}
) ) {
return $this->handle_payment_failure( $wc_order, new Exception( 'No saved PayPal account.' ) );
}
$wc_order->payment_complete();
return $this->handle_payment_success( $wc_order );
}
/**
* If customer has chosen change Subscription payment.
*/
if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) {
if ( 'ppcp-credit-card-gateway' === $this->id && $saved_credit_card ) {
update_post_meta( $order_id, 'payment_token_id', $saved_credit_card );
return $this->handle_payment_success( $wc_order );
}
$saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING );
if ( 'ppcp-gateway' === $this->id && $saved_paypal_payment ) {
update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment );
return $this->handle_payment_success( $wc_order );
}
}
/**
* If the WC_Order is paid through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return $this->handle_payment_success( $wc_order );
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
if ( $this->order_processor->process( $wc_order ) ) {
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
as_schedule_single_action(
time() + ( 1 * MINUTE_IN_SECONDS ),
'woocommerce_paypal_payments_check_saved_payment',
array(
'order_id' => $order_id,
'customer_id' => $wc_order->get_customer_id(),
'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '',
)
);
}
WC()->cart->empty_cart(); // Probably redundant.
return $this->handle_payment_success( $wc_order );
}
} catch ( PayPalApiException $error ) {
if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) {
$wc_order->update_status(
'failed',
__( 'Instrument declined. ', 'woocommerce-paypal-payments' ) . $error->details()[0]->description ?? ''
);
$this->session_handler->increment_insufficient_funding_tries();
if ( $this->session_handler->insufficient_funding_tries() >= 3 ) {
return $this->handle_payment_failure(
null,
new Exception(
__( 'Please use a different payment method.', 'woocommerce-paypal-payments' ),
$error->getCode(),
$error
)
);
}
$host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ?
'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/';
$url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id();
return array(
'result' => 'success',
'redirect' => $url,
);
}
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( RuntimeException $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
return $this->handle_payment_failure(
$wc_order,
new Exception(
$this->order_processor->last_error()
)
);
}
/**
* Checks if PayPal or Credit Card gateways are enabled.
*
@ -341,11 +110,4 @@ trait ProcessPaymentTrait {
}
return $output . ' ' . $this->format_exception( $prev );
}
/**
* Returns the environment.
*
* @return Environment
*/
abstract protected function environment(): Environment;
}