mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 09:08:09 +08:00
269 lines
6.9 KiB
PHP
269 lines
6.9 KiB
PHP
<?php
|
|
/**
|
|
* Processes refunds started in the WooCommerce environment.
|
|
*
|
|
* @package WooCommerce\PayPalCommerce\WcGateway\Processor
|
|
*/
|
|
|
|
declare( strict_types=1 );
|
|
|
|
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
|
|
|
|
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\Amount;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundCapture;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
|
|
|
|
/**
|
|
* Class RefundProcessor
|
|
*/
|
|
class RefundProcessor {
|
|
use RefundMetaTrait;
|
|
|
|
private const REFUND_MODE_REFUND = 'refund';
|
|
private const REFUND_MODE_VOID = 'void';
|
|
private const REFUND_MODE_UNKNOWN = 'unknown';
|
|
|
|
/**
|
|
* The order endpoint.
|
|
*
|
|
* @var OrderEndpoint
|
|
*/
|
|
private $order_endpoint;
|
|
|
|
/**
|
|
* The payments endpoint.
|
|
*
|
|
* @var PaymentsEndpoint
|
|
*/
|
|
private $payments_endpoint;
|
|
|
|
/**
|
|
* The logger.
|
|
*
|
|
* @var LoggerInterface
|
|
*/
|
|
private $logger;
|
|
|
|
/**
|
|
* The prefix.
|
|
*
|
|
* @var string
|
|
*/
|
|
private $prefix;
|
|
|
|
/**
|
|
* The refund fees updater.
|
|
*
|
|
* @var RefundFeesUpdater
|
|
*/
|
|
private $refund_fees_updater;
|
|
|
|
/**
|
|
* RefundProcessor constructor.
|
|
*
|
|
* @param OrderEndpoint $order_endpoint The order endpoint.
|
|
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
|
|
* @param RefundFeesUpdater $refund_fees_updater The refund fees updater.
|
|
* @param string $prefix The prefix.
|
|
* @param LoggerInterface $logger The logger.
|
|
*/
|
|
public function __construct(
|
|
OrderEndpoint $order_endpoint,
|
|
PaymentsEndpoint $payments_endpoint,
|
|
RefundFeesUpdater $refund_fees_updater,
|
|
string $prefix,
|
|
LoggerInterface $logger
|
|
) {
|
|
|
|
$this->order_endpoint = $order_endpoint;
|
|
$this->payments_endpoint = $payments_endpoint;
|
|
$this->refund_fees_updater = $refund_fees_updater;
|
|
$this->prefix = $prefix;
|
|
$this->logger = $logger;
|
|
}
|
|
|
|
/**
|
|
* Processes a refund.
|
|
*
|
|
* @param WC_Order $wc_order The WooCommerce order.
|
|
* @param float|null $amount The refund amount.
|
|
* @param string $reason The reason for the refund.
|
|
*
|
|
* @return bool
|
|
*
|
|
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.Missing
|
|
*/
|
|
public function process( WC_Order $wc_order, float $amount = null, string $reason = '' ) : bool {
|
|
try {
|
|
$allowed_refund_payment_methods = apply_filters(
|
|
'woocommerce_paypal_payments_allowed_refund_payment_methods',
|
|
array( PayPalGateway::ID, CreditCardGateway::ID, CardButtonGateway::ID, PayUponInvoiceGateway::ID )
|
|
);
|
|
if ( ! in_array( $wc_order->get_payment_method(), $allowed_refund_payment_methods, true ) ) {
|
|
return true;
|
|
}
|
|
|
|
$order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
|
|
if ( ! $order_id ) {
|
|
throw new RuntimeException( 'PayPal order ID not found in meta.' );
|
|
}
|
|
|
|
$order = $this->order_endpoint->order( $order_id );
|
|
|
|
$payments = $this->get_payments( $order );
|
|
|
|
$this->logger->debug(
|
|
sprintf(
|
|
'Trying to refund/void order %1$s, payments: %2$s.',
|
|
$order->id(),
|
|
wp_json_encode( $payments->to_array() )
|
|
)
|
|
);
|
|
|
|
$mode = $this->determine_refund_mode( $payments );
|
|
|
|
switch ( $mode ) {
|
|
case self::REFUND_MODE_REFUND:
|
|
$refund_id = $this->refund( $order, $wc_order, $amount, $reason );
|
|
|
|
$this->add_refund_to_meta( $wc_order, $refund_id );
|
|
$this->refund_fees_updater->update( $wc_order );
|
|
|
|
break;
|
|
case self::REFUND_MODE_VOID:
|
|
$this->void( $order );
|
|
|
|
$wc_order->set_status( 'refunded' );
|
|
$wc_order->save();
|
|
|
|
break;
|
|
default:
|
|
throw new RuntimeException( 'Nothing to refund/void.' );
|
|
}
|
|
|
|
return true;
|
|
} catch ( Exception $error ) {
|
|
$this->logger->error( 'Refund failed: ' . $error->getMessage() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a refund to the PayPal order.
|
|
*
|
|
* @param Order $order The PayPal order.
|
|
* @param WC_Order $wc_order The WooCommerce order.
|
|
* @param float $amount The refund amount.
|
|
* @param string $reason The reason for the refund.
|
|
*
|
|
* @throws RuntimeException When operation fails.
|
|
* @return string The PayPal refund ID.
|
|
*/
|
|
public function refund(
|
|
Order $order,
|
|
WC_Order $wc_order,
|
|
float $amount,
|
|
string $reason = ''
|
|
): string {
|
|
$payments = $this->get_payments( $order );
|
|
|
|
$captures = $payments->captures();
|
|
if ( ! $captures ) {
|
|
throw new RuntimeException( 'No capture.' );
|
|
}
|
|
|
|
$capture = $captures[0];
|
|
$refund = new RefundCapture(
|
|
$capture,
|
|
$capture->invoice_id() ?: $this->prefix . $wc_order->get_order_number(),
|
|
$reason,
|
|
new Amount(
|
|
new Money( $amount, $wc_order->get_currency() )
|
|
)
|
|
);
|
|
|
|
return $this->payments_endpoint->refund( $refund );
|
|
}
|
|
|
|
/**
|
|
* Voids the authorization.
|
|
*
|
|
* @param Order $order The PayPal order.
|
|
* @throws RuntimeException When operation fails.
|
|
*/
|
|
public function void( Order $order ): void {
|
|
$payments = $this->get_payments( $order );
|
|
|
|
$voidable_authorizations = array_filter(
|
|
$payments->authorizations(),
|
|
function ( Authorization $authorization ): bool {
|
|
return $authorization->is_voidable();
|
|
}
|
|
);
|
|
if ( ! $voidable_authorizations ) {
|
|
throw new RuntimeException( 'No voidable authorizations.' );
|
|
}
|
|
|
|
foreach ( $voidable_authorizations as $authorization ) {
|
|
$this->payments_endpoint->void( $authorization );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines the refunding mode.
|
|
*
|
|
* @param Payments $payments The order payments state.
|
|
*
|
|
* @return string One of the REFUND_MODE_ constants.
|
|
*/
|
|
private function determine_refund_mode( Payments $payments ): string {
|
|
$authorizations = $payments->authorizations();
|
|
if ( $authorizations ) {
|
|
foreach ( $authorizations as $authorization ) {
|
|
if ( $authorization->is_voidable() ) {
|
|
return self::REFUND_MODE_VOID;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $payments->captures() ) {
|
|
return self::REFUND_MODE_REFUND;
|
|
}
|
|
|
|
return self::REFUND_MODE_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* Returns the payments object or throws.
|
|
*
|
|
* @param Order $order The order.
|
|
* @throws RuntimeException When payment not available.
|
|
*/
|
|
protected function get_payments( Order $order ): Payments {
|
|
$purchase_units = $order->purchase_units();
|
|
if ( ! $purchase_units ) {
|
|
throw new RuntimeException( 'No purchase units.' );
|
|
}
|
|
|
|
$payments = $purchase_units[0]->payments();
|
|
if ( ! $payments ) {
|
|
throw new RuntimeException( 'No payments.' );
|
|
}
|
|
|
|
return $payments;
|
|
}
|
|
}
|