laipower/wp-content/plugins/easy-digital-downloads/includes/gateways/paypal/refunds.php

243 lines
7.0 KiB
PHP

<?php
/**
* PayPal Commerce Refunds
*
* @package easy-digital-downloads
* @subpackage Gateways\PayPal
* @copyright Copyright (c) 2021, Sandhills Development, LLC
* @license GPL2+
* @since 2.11
*/
namespace EDD\Gateways\PayPal;
use EDD\Gateways\PayPal\Exceptions\API_Exception;
use EDD\Gateways\PayPal\Exceptions\Authentication_Exception;
use EDD\Orders\Order;
/**
* Shows a checkbox to automatically refund payments in PayPal.
*
* @param Order $order
*
* @since 3.0
*/
add_action( 'edd_after_submit_refund_table', function( Order $order ) {
if ( 'paypal_commerce' !== $order->gateway ) {
return;
}
$mode = ( 'live' === $order->mode ) ? API::MODE_LIVE : API::MODE_SANDBOX;
try {
new API( $mode );
} catch ( Exceptions\Authentication_Exception $e ) {
// If we don't have credentials.
return;
}
?>
<div class="edd-form-group edd-paypal-refund-transaction">
<div class="edd-form-group__control">
<input type="checkbox" id="edd-paypal-commerce-refund" name="edd-paypal-commerce-refund" class="edd-form-group__input" value="1">
<label for="edd-paypal-commerce-refund" class="edd-form-group__label">
<?php esc_html_e( 'Refund transaction in PayPal', 'easy-digital-downloads' ); ?>
</label>
</div>
</div>
<?php
} );
/**
* If selected, refunds a transaction in PayPal when creating a new refund record.
*
* @param int $order_id ID of the order we're processing a refund for.
* @param int $refund_id ID of the newly created refund record.
* @param bool $all_refunded Whether or not this was a full refund.
*
* @since 3.0
*/
add_action( 'edd_refund_order', function( $order_id, $refund_id, $all_refunded ) {
if ( ! current_user_can( 'edit_shop_payments', $order_id ) ) {
return;
}
if ( empty( $_POST['data'] ) ) {
return;
}
$order = edd_get_order( $order_id );
if ( empty( $order->gateway ) || 'paypal_commerce' !== $order->gateway ) {
return;
}
// Get our data out of the serialized string.
parse_str( $_POST['data'], $form_data );
if ( empty( $form_data['edd-paypal-commerce-refund'] ) ) {
edd_add_note( array(
'object_id' => $order_id,
'object_type' => 'order',
'user_id' => is_admin() ? get_current_user_id() : 0,
'content' => __( 'Transaction not refunded in PayPal, as checkbox was not selected.', 'easy-digital-downloads' )
) );
return;
}
$refund = edd_get_order( $refund_id );
if ( empty( $refund->total ) ) {
return;
}
try {
refund_transaction( $order, $refund );
} catch ( \Exception $e ) {
edd_debug_log( sprintf(
'Failure when processing refund #%d. Message: %s',
$refund->id,
$e->getMessage()
), true );
edd_add_note( array(
'object_id' => $order->id,
'object_type' => 'order',
'user_id' => is_admin() ? get_current_user_id() : 0,
'content' => sprintf(
/* Translators: %d - ID of the refund; %s - error message from PayPal */
__( 'Failure when processing PayPal refund #%d: %s', 'easy-digital-downloads' ),
$refund->id,
$e->getMessage()
)
) );
}
}, 10, 3 );
/**
* Refunds a transaction in PayPal.
*
* @link https://developer.paypal.com/docs/api/payments/v2/#captures_refund
*
* @param \EDD_Payment|Order $payment_or_order
* @param Order|null $refund_object
*
* @since 2.11
* @throws Authentication_Exception
* @throws API_Exception
* @throws \Exception
*/
function refund_transaction( $payment_or_order, Order $refund_object = null ) {
/*
* Internally we want to work with an Order object, but we also need
* an EDD_Payment object for backwards compatibility in the hooks.
*/
$order = $payment = false;
if ( $payment_or_order instanceof Order ) {
$order = $payment_or_order;
$payment = edd_get_payment( $order->id );
} elseif ( $payment_or_order instanceof \EDD_Payment ) {
$payment = $payment_or_order;
$order = edd_get_order( $payment->ID );
}
if ( empty( $order ) || ! $order instanceof Order ) {
return;
}
$transaction_id = $order->get_transaction_id();
if ( empty( $transaction_id ) ) {
throw new \Exception( __( 'Missing transaction ID.', 'easy-digital-downloads' ) );
}
$mode = ( 'live' === $order->mode ) ? API::MODE_LIVE : API::MODE_SANDBOX;
$api = new API( $mode );
$args = $refund_object instanceof Order ? array( 'invoice_id' => $refund_object->id ) : array();
if ( $refund_object instanceof Order && abs( $refund_object->total ) !== abs( $order->total ) ) {
$args['amount'] = array(
'value' => abs( $refund_object->total ),
'currency_code' => $refund_object->currency,
);
}
$response = $api->make_request(
'v2/payments/captures/' . urlencode( $transaction_id ) . '/refund',
$args,
array(
'Prefer' => 'return=representation',
)
);
if ( 201 !== $api->last_response_code ) {
throw new API_Exception( sprintf(
/* Translators: %d - The HTTP response code; %s - Full API response from PayPal */
__( 'Unexpected response code: %d. Response: %s', 'easy-digital-downloads' ),
$api->last_response_code,
json_encode( $response )
), $api->last_response_code );
}
if ( empty( $response->status ) || 'COMPLETED' !== strtoupper( $response->status ) ) {
throw new API_Exception( sprintf(
/* Translators: %s - API response from PayPal */
__( 'Missing or unexpected refund status. Response: %s', 'easy-digital-downloads' ),
json_encode( $response )
) );
}
// At this point we can assume it was successful.
edd_update_order_meta( $order->id, '_edd_paypal_refunded', true );
if ( ! empty( $response->id ) ) {
// Add a note to the original order, and, if provided, the new refund object.
if ( isset( $response->amount->value ) ) {
$note_message = sprintf(
/* Translators: %1$s - amount refunded; %$2$s - transaction ID. */
__( '%1$s refunded in PayPal. Refund transaction ID: %2$s', 'easy-digital-downloads' ),
edd_currency_filter( edd_format_amount( $response->amount->value ), $order->currency ),
esc_html( $response->id )
);
} else {
$note_message = sprintf(
/* Translators: %s - ID of the refund in PayPal */
__( 'Successfully refunded in PayPal. Refund transaction ID: %s', 'easy-digital-downloads' ),
esc_html( $response->id )
);
}
$note_object_ids = array( $order->id );
if ( $refund_object instanceof Order ) {
$note_object_ids[] = $refund_object->id;
}
foreach ( $note_object_ids as $note_object_id ) {
edd_add_note( array(
'object_id' => $note_object_id,
'object_type' => 'order',
'user_id' => is_admin() ? get_current_user_id() : 0,
'content' => $note_message
) );
}
// Add a negative transaction.
if ( $refund_object instanceof Order && isset( $response->amount->value ) ) {
edd_add_order_transaction( array(
'object_id' => $refund_object->id,
'object_type' => 'order',
'transaction_id' => sanitize_text_field( $response->id ),
'gateway' => 'paypal_commerce',
'status' => 'complete',
'total' => edd_negate_amount( $response->amount->value ),
) );
}
}
/**
* Triggers after a successful refund.
*
* @param \EDD_Payment $payment
*/
do_action( 'edd_paypal_refund_purchase', $payment );
}