- Affected version
- 2.3.13
If you issue a partial refund in Stripe, this is not checked and is instead treated as a full refund. It is crucial that partial refunds do not count as a full refund, because partial refunds are granted if the user was accidentally charged too much.
This is the relevant data as per the payment provider log:
A payment of €59.42 was refunded €10.48.
Suggested fix:
This is the relevant data as per the payment provider log:
Code:
["amount_captured"] => int(5942)
["amount_refunded"] => int(1048)
["eventType"] => string(15) "charge.refunded"
A payment of €59.42 was refunded €10.48.
Suggested fix:
Diff:
--- a/src/XF/Payment/AbstractProvider.php
+++ b/src/XF/Payment/AbstractProvider.php
@@ -232,8 +232,8 @@
break;
default:
- $state->logType = 'info';
- $state->logMessage = 'OK, no action';
+ $state->logType = $state->logType ?: 'info';
+ $state->logMessage = $state->logMessage ?: 'OK, no action';
break;
}
--- a/src/XF/Payment/Stripe.php
+++ b/src/XF/Payment/Stripe.php
@@ -1209,6 +1209,25 @@
break;
case 'charge.refunded':
+ $amountCaptured = $state->event['amount_captured'] ?? $state->event['amount'];
+ $amountRefunded = $state->event['amount_refunded'] ?? $amountCaptured;
+
+ if ($amountRefunded < $amountCaptured)
+ {
+ // a partial refund leaves the purchase in place; a later refund of the remainder reverses it
+ $state->logType = 'info';
+ $state->logMessage = sprintf(
+ 'Partial refund (%d of %d %s refunded), purchase not reversed.',
+ $amountRefunded,
+ $amountCaptured,
+ strtoupper($state->event['currency'])
+ );
+ break;
+ }
+
+ $state->paymentResult = CallbackState::PAYMENT_REVERSED;
+ break;
+
case 'charge.dispute.funds_withdrawn':
$state->paymentResult = CallbackState::PAYMENT_REVERSED;
break;