2020-08-27 11:50:10 +03:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Handles subscription renewals.
|
|
|
|
*
|
2020-09-11 14:11:10 +03:00
|
|
|
* @package WooCommerce\PayPalCommerce\Subscription
|
2020-08-27 11:50:10 +03:00
|
|
|
*/
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-09-11 14:11:10 +03:00
|
|
|
namespace WooCommerce\PayPalCommerce\Subscription;
|
2020-08-27 11:50:10 +03:00
|
|
|
|
2020-09-11 14:11:10 +03:00
|
|
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
2022-06-29 16:52:19 +04:00
|
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
2020-09-11 14:11:10 +03:00
|
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
2022-10-04 17:43:58 +02:00
|
|
|
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
2020-09-11 14:11:10 +03:00
|
|
|
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
|
|
|
|
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
2022-07-05 14:25:55 +03:00
|
|
|
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
|
2022-01-05 15:25:22 +02:00
|
|
|
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
2021-09-17 17:11:36 +02:00
|
|
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
2020-08-27 11:50:10 +03:00
|
|
|
use Psr\Log\LoggerInterface;
|
2022-06-29 16:52:19 +04:00
|
|
|
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
2021-10-14 15:45:57 +02:00
|
|
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
2022-01-05 15:25:22 +02:00
|
|
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
|
|
|
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
|
|
|
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
|
2022-06-29 16:52:19 +04:00
|
|
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
2020-08-27 11:50:10 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Class RenewalHandler
|
|
|
|
*/
|
|
|
|
class RenewalHandler {
|
|
|
|
|
2022-01-05 15:25:22 +02:00
|
|
|
use OrderMetaTrait;
|
|
|
|
use TransactionIdHandlingTrait;
|
|
|
|
use PaymentsStatusHandlingTrait;
|
|
|
|
|
2020-08-27 11:50:10 +03:00
|
|
|
/**
|
|
|
|
* The logger.
|
|
|
|
*
|
|
|
|
* @var LoggerInterface
|
|
|
|
*/
|
|
|
|
private $logger;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The payment token repository.
|
|
|
|
*
|
|
|
|
* @var PaymentTokenRepository
|
|
|
|
*/
|
|
|
|
private $repository;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The order endpoint.
|
|
|
|
*
|
|
|
|
* @var OrderEndpoint
|
|
|
|
*/
|
|
|
|
private $order_endpoint;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The purchase unit factory.
|
|
|
|
*
|
|
|
|
* @var PurchaseUnitFactory
|
|
|
|
*/
|
|
|
|
private $purchase_unit_factory;
|
|
|
|
|
2022-07-05 14:25:55 +03:00
|
|
|
/**
|
|
|
|
* The shipping_preference factory.
|
|
|
|
*
|
|
|
|
* @var ShippingPreferenceFactory
|
|
|
|
*/
|
|
|
|
private $shipping_preference_factory;
|
|
|
|
|
2020-08-27 11:50:10 +03:00
|
|
|
/**
|
|
|
|
* The payer factory.
|
|
|
|
*
|
|
|
|
* @var PayerFactory
|
|
|
|
*/
|
|
|
|
private $payer_factory;
|
|
|
|
|
2022-01-05 15:25:22 +02:00
|
|
|
/**
|
|
|
|
* The environment.
|
|
|
|
*
|
|
|
|
* @var Environment
|
|
|
|
*/
|
|
|
|
protected $environment;
|
|
|
|
|
2022-06-29 16:52:19 +04:00
|
|
|
/**
|
|
|
|
* The settings
|
|
|
|
*
|
|
|
|
* @var Settings
|
|
|
|
*/
|
|
|
|
protected $settings;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The processor for authorized payments.
|
|
|
|
*
|
|
|
|
* @var AuthorizedPaymentsProcessor
|
|
|
|
*/
|
|
|
|
protected $authorized_payments_processor;
|
|
|
|
|
2020-08-27 11:50:10 +03:00
|
|
|
/**
|
|
|
|
* RenewalHandler constructor.
|
|
|
|
*
|
2022-07-08 19:52:22 +04:00
|
|
|
* @param LoggerInterface $logger The logger.
|
|
|
|
* @param PaymentTokenRepository $repository The payment token repository.
|
|
|
|
* @param OrderEndpoint $order_endpoint The order endpoint.
|
|
|
|
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
|
|
|
|
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory.
|
|
|
|
* @param PayerFactory $payer_factory The payer factory.
|
|
|
|
* @param Environment $environment The environment.
|
2022-06-29 16:52:19 +04:00
|
|
|
* @param Settings $settings The Settings.
|
|
|
|
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
|
2020-08-27 11:50:10 +03:00
|
|
|
*/
|
|
|
|
public function __construct(
|
|
|
|
LoggerInterface $logger,
|
|
|
|
PaymentTokenRepository $repository,
|
|
|
|
OrderEndpoint $order_endpoint,
|
|
|
|
PurchaseUnitFactory $purchase_unit_factory,
|
2022-07-05 14:25:55 +03:00
|
|
|
ShippingPreferenceFactory $shipping_preference_factory,
|
2022-01-05 15:25:22 +02:00
|
|
|
PayerFactory $payer_factory,
|
2022-06-29 16:52:19 +04:00
|
|
|
Environment $environment,
|
|
|
|
Settings $settings,
|
|
|
|
AuthorizedPaymentsProcessor $authorized_payments_processor
|
2020-08-27 11:50:10 +03:00
|
|
|
) {
|
|
|
|
|
2022-06-29 16:52:19 +04:00
|
|
|
$this->logger = $logger;
|
|
|
|
$this->repository = $repository;
|
|
|
|
$this->order_endpoint = $order_endpoint;
|
|
|
|
$this->purchase_unit_factory = $purchase_unit_factory;
|
2022-07-08 19:52:22 +04:00
|
|
|
$this->shipping_preference_factory = $shipping_preference_factory;
|
2022-06-29 16:52:19 +04:00
|
|
|
$this->payer_factory = $payer_factory;
|
|
|
|
$this->environment = $environment;
|
|
|
|
$this->settings = $settings;
|
|
|
|
$this->authorized_payments_processor = $authorized_payments_processor;
|
2020-08-27 11:50:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Renew an order.
|
|
|
|
*
|
2020-09-03 07:05:50 +03:00
|
|
|
* @param \WC_Order $wc_order The WooCommerce order.
|
2020-08-27 11:50:10 +03:00
|
|
|
*/
|
|
|
|
public function renew( \WC_Order $wc_order ) {
|
|
|
|
try {
|
|
|
|
$this->process_order( $wc_order );
|
|
|
|
} catch ( \Exception $error ) {
|
2022-10-04 17:43:58 +02:00
|
|
|
$error_details = $error->getMessage();
|
|
|
|
if ( is_a( $error, PayPalApiException::class ) ) {
|
|
|
|
$details = '';
|
|
|
|
foreach ( $error->details() as $detail ) {
|
|
|
|
$details .= $detail->issue . ' ' . $detail->description;
|
|
|
|
}
|
|
|
|
if ( $details ) {
|
|
|
|
$error_details = $details;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$error_message = sprintf(
|
|
|
|
'An error occurred while trying to renew the subscription for order %1$d: %2$s',
|
|
|
|
$wc_order->get_id(),
|
|
|
|
$error_details
|
2020-08-27 11:50:10 +03:00
|
|
|
);
|
2021-03-01 12:37:13 +01:00
|
|
|
|
2022-10-04 17:43:58 +02:00
|
|
|
$wc_order->update_status(
|
|
|
|
'failed',
|
|
|
|
$error_details
|
|
|
|
);
|
|
|
|
$this->logger->error( $error_message );
|
|
|
|
|
2020-08-27 11:50:10 +03:00
|
|
|
return;
|
|
|
|
}
|
2022-10-04 17:43:58 +02:00
|
|
|
|
2022-01-04 17:30:26 +02:00
|
|
|
$this->logger->info(
|
2020-08-27 11:50:10 +03:00
|
|
|
sprintf(
|
2022-01-04 17:30:26 +02:00
|
|
|
'Renewal for order %d is completed.',
|
|
|
|
$wc_order->get_id()
|
2020-08-27 11:50:10 +03:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-03 07:05:50 +03:00
|
|
|
* Process a WooCommerce order.
|
2020-08-27 11:50:10 +03:00
|
|
|
*
|
2020-09-03 07:05:50 +03:00
|
|
|
* @param \WC_Order $wc_order The WooCommerce order.
|
2020-08-27 11:50:10 +03:00
|
|
|
*
|
|
|
|
* @throws \Exception If customer cannot be read/found.
|
|
|
|
*/
|
2022-07-11 09:19:27 +03:00
|
|
|
private function process_order( \WC_Order $wc_order ): void {
|
2020-08-27 11:50:10 +03:00
|
|
|
|
|
|
|
$user_id = (int) $wc_order->get_customer_id();
|
|
|
|
$customer = new \WC_Customer( $user_id );
|
|
|
|
$token = $this->get_token_for_customer( $customer, $wc_order );
|
|
|
|
if ( ! $token ) {
|
|
|
|
return;
|
|
|
|
}
|
2022-07-05 14:25:55 +03:00
|
|
|
$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,
|
|
|
|
'renewal'
|
|
|
|
);
|
2021-03-01 12:37:13 +01:00
|
|
|
|
|
|
|
$order = $this->order_endpoint->create(
|
2020-08-27 11:50:10 +03:00
|
|
|
array( $purchase_unit ),
|
2022-07-05 14:25:55 +03:00
|
|
|
$shipping_preference,
|
2020-08-27 11:50:10 +03:00
|
|
|
$payer,
|
2021-03-01 12:37:13 +01:00
|
|
|
$token
|
2020-08-27 11:50:10 +03:00
|
|
|
);
|
2021-03-01 12:37:13 +01:00
|
|
|
|
2022-01-05 15:25:22 +02:00
|
|
|
$this->add_paypal_meta( $wc_order, $order, $this->environment );
|
|
|
|
|
2022-01-04 17:30:26 +02:00
|
|
|
if ( $order->intent() === 'AUTHORIZE' ) {
|
2022-01-05 15:25:22 +02:00
|
|
|
$order = $this->order_endpoint->authorize( $order );
|
2022-01-04 17:30:26 +02:00
|
|
|
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' );
|
|
|
|
}
|
2022-01-05 15:25:22 +02:00
|
|
|
|
|
|
|
$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 );
|
2022-06-29 16:52:19 +04:00
|
|
|
|
2022-09-28 11:45:00 +03:00
|
|
|
if ( $this->capture_authorized_downloads( $order ) ) {
|
|
|
|
$this->authorized_payments_processor->capture_authorized_payment( $wc_order );
|
2022-06-29 16:52:19 +04:00
|
|
|
}
|
2020-08-27 11:50:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a payment token for a customer.
|
|
|
|
*
|
|
|
|
* @param \WC_Customer $customer The customer.
|
2020-09-03 07:05:50 +03:00
|
|
|
* @param \WC_Order $wc_order The current WooCommerce order we want to process.
|
2020-08-27 11:50:10 +03:00
|
|
|
*
|
|
|
|
* @return PaymentToken|null
|
|
|
|
*/
|
2020-09-11 13:38:02 +03:00
|
|
|
private function get_token_for_customer( \WC_Customer $customer, \WC_Order $wc_order ) {
|
2022-02-17 18:19:55 +02:00
|
|
|
/**
|
|
|
|
* Returns a payment token for a customer, or null.
|
|
|
|
*/
|
2021-07-16 13:59:42 -05:00
|
|
|
$token = apply_filters( 'woocommerce_paypal_payments_subscriptions_get_token_for_customer', null, $customer, $wc_order );
|
|
|
|
if ( null !== $token ) {
|
|
|
|
return $token;
|
|
|
|
}
|
2020-08-27 11:50:10 +03:00
|
|
|
|
2021-04-23 12:22:29 +02:00
|
|
|
$tokens = $this->repository->all_for_user_id( (int) $customer->get_id() );
|
|
|
|
if ( ! $tokens ) {
|
2021-04-28 12:13:29 +02:00
|
|
|
|
|
|
|
$error_message = sprintf(
|
2022-01-04 17:30:26 +02:00
|
|
|
'Payment failed. No payment tokens found for customer %d.',
|
|
|
|
$customer->get_id()
|
2021-04-28 12:13:29 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
$wc_order->update_status(
|
|
|
|
'failed',
|
|
|
|
$error_message
|
|
|
|
);
|
|
|
|
|
2022-01-04 17:30:26 +02:00
|
|
|
$this->logger->error( $error_message );
|
2020-08-27 11:50:10 +03:00
|
|
|
}
|
2021-04-23 12:22:29 +02:00
|
|
|
|
2021-05-21 11:19:00 +02:00
|
|
|
$subscription = function_exists( 'wcs_get_subscription' ) ? wcs_get_subscription( $wc_order->get_meta( '_subscription_renewal' ) ) : null;
|
2021-05-10 17:12:46 +02:00
|
|
|
if ( $subscription ) {
|
|
|
|
$subscription_id = $subscription->get_id();
|
|
|
|
$token_id = get_post_meta( $subscription_id, 'payment_token_id', true );
|
|
|
|
if ( $token_id ) {
|
|
|
|
foreach ( $tokens as $token ) {
|
|
|
|
if ( $token_id === $token->id() ) {
|
|
|
|
return $token;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-23 12:22:29 +02:00
|
|
|
return current( $tokens );
|
2020-08-27 11:50:10 +03:00
|
|
|
}
|
2022-06-29 16:52:19 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns if an order should be captured immediately.
|
|
|
|
*
|
|
|
|
* @param Order $order The PayPal order.
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
* @throws NotFoundException When a setting was not found.
|
|
|
|
*/
|
|
|
|
protected function capture_authorized_downloads( Order $order ): bool {
|
|
|
|
if (
|
|
|
|
! $this->settings->has( 'capture_for_virtual_only' )
|
|
|
|
|| ! $this->settings->get( 'capture_for_virtual_only' )
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $order->intent() === 'CAPTURE' ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We fetch the order again as the authorize endpoint (from which the Order derives)
|
|
|
|
* drops the item's category, making it impossible to check, if purchase units contain
|
|
|
|
* physical goods.
|
|
|
|
*/
|
|
|
|
$order = $this->order_endpoint->order( $order->id() );
|
|
|
|
|
|
|
|
foreach ( $order->purchase_units() as $unit ) {
|
|
|
|
if ( $unit->contains_physical_goods() ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2020-08-27 11:50:10 +03:00
|
|
|
}
|