Skip already handled refunds in webhook

When we add a refund from WC, we still receive a webhook with it. And for some partial refunds the duplicated refund can be added in WC.
This commit is contained in:
Alex P 2022-04-28 16:20:05 +03:00
parent 13e04b009c
commit 35f058870a
5 changed files with 74 additions and 10 deletions

View file

@ -198,11 +198,11 @@ class PaymentsEndpoint {
*
* @param Refund $refund The refund to be processed.
*
* @return void
* @return string Refund ID.
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function refund( Refund $refund ) : void {
public function refund( Refund $refund ) : string {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/payments/captures/' . $refund->for_capture()->id() . '/refund';
$args = array(
@ -223,12 +223,15 @@ class PaymentsEndpoint {
}
$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( 201 !== $status_code ) {
$json = json_decode( $response['body'] );
if ( 201 !== $status_code || ! is_object( $json ) ) {
throw new PayPalApiException(
$json,
$status_code
);
}
return $json->id;
}
/**

View file

@ -39,6 +39,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode';
const ORDER_PAYMENT_SOURCE_META_KEY = '_ppcp_paypal_payment_source';
const FEES_META_KEY = '_ppcp_paypal_fees';
const REFUNDS_META_KEY = '_ppcp_refunds';
/**
* The Settings Renderer.

View file

@ -0,0 +1,47 @@
<?php
/**
* Operations with refund metadata.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Processor
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use WC_Order;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Trait RefundMetaTrait.
*/
trait RefundMetaTrait {
/**
* Adds a refund ID to the order metadata.
*
* @param WC_Order $wc_order The WC order to which metadata will be added.
* @param string $refund_id The refund ID to be added.
*/
protected function add_refund_to_meta( WC_Order $wc_order, string $refund_id ): void {
$refunds = $this->get_refunds_meta( $wc_order );
$refunds[] = $refund_id;
$wc_order->update_meta_data( PayPalGateway::REFUNDS_META_KEY, $refunds );
$wc_order->save();
}
/**
* Returns refund IDs from the order metadata.
*
* @param WC_Order $wc_order The WC order.
*
* @return string[]
*/
protected function get_refunds_meta( WC_Order $wc_order ): array {
$refunds = $wc_order->get_meta( PayPalGateway::REFUNDS_META_KEY );
if ( ! is_array( $refunds ) ) {
$refunds = array();
}
return $refunds;
}
}

View file

@ -26,6 +26,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
* Class RefundProcessor
*/
class RefundProcessor {
use RefundMetaTrait;
private const REFUND_MODE_REFUND = 'refund';
private const REFUND_MODE_VOID = 'void';
@ -113,8 +114,8 @@ class RefundProcessor {
throw new RuntimeException( 'No capture.' );
}
$capture = $captures[0];
$refund = new Refund(
$capture = $captures[0];
$refund = new Refund(
$capture,
$capture->invoice_id(),
$reason,
@ -122,7 +123,10 @@ class RefundProcessor {
new Money( $amount, $wc_order->get_currency() )
)
);
$this->payments_endpoint->refund( $refund );
$refund_id = $this->payments_endpoint->refund( $refund );
$this->add_refund_to_meta( $wc_order, $refund_id );
break;
case self::REFUND_MODE_VOID:
$voidable_authorizations = array_filter(

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
/**
@ -17,7 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
*/
class PaymentCaptureRefunded implements RequestHandler {
use PrefixTrait, TransactionIdHandlingTrait;
use PrefixTrait, TransactionIdHandlingTrait, RefundMetaTrait;
/**
* The logger.
@ -68,6 +69,7 @@ class PaymentCaptureRefunded implements RequestHandler {
$response = array( 'success' => false );
$order_id = isset( $request['resource']['custom_id'] ) ?
$this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
$refund_id = (string) ( $request['resource']['id'] ?? '' );
if ( ! $order_id ) {
$message = sprintf(
// translators: %s is the PayPal webhook Id.
@ -93,7 +95,7 @@ class PaymentCaptureRefunded implements RequestHandler {
$message = sprintf(
// translators: %s is the PayPal refund Id.
__( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ),
isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
$refund_id
);
$this->logger->log(
'warning',
@ -106,6 +108,12 @@ class PaymentCaptureRefunded implements RequestHandler {
return rest_ensure_response( $response );
}
$already_added_refunds = $this->get_refunds_meta( $wc_order );
if ( in_array( $refund_id, $already_added_refunds, true ) ) {
$this->logger->info( "Refund {$refund_id} is already handled." );
return new WP_REST_Response( $response );
}
/**
* The WooCommerce order.
*
@ -152,8 +160,9 @@ class PaymentCaptureRefunded implements RequestHandler {
)
);
if ( is_array( $request['resource'] ) && isset( $request['resource']['id'] ) ) {
$this->update_transaction_id( $request['resource']['id'], $wc_order, $this->logger );
if ( $refund_id ) {
$this->update_transaction_id( $refund_id, $wc_order, $this->logger );
$this->add_refund_to_meta( $wc_order, $refund_id );
}
$response['success'] = true;