getRefundedAmount($ipnEventData, $donation->amount->getCurrency()->getCode()); if ( ! $refundedAmount || ! $this->isValidRefundAmount($refundedAmount, $donation->amount)) { Log::error( 'PayPal Standard IPN Error', [ 'Message' => sprintf( 'Refund amount (%s) for donation #%d is not valid.', $ipnEventData->mc_gross ?? '(not set)', $donationId ), 'Event Data' => $ipnEventData, ] ); return; } if ($refundedAmount->absolute()->lessThan($donation->amount)) { DonationNote::create([ 'donationId' => $donation->id, 'content' => sprintf( /* translators: %s: Paypal parent transaction ID */ __('Partial PayPal refund processed: %s', 'give'), $ipnEventData->parent_txn_id ), ]); return; } DonationNote::create([ 'donationId' => $donation->id, 'content' => sprintf( /* translators: 1: Paypal parent transaction ID 2. Paypal reason code */ __('PayPal Payment #%1$s Refunded for reason: %2$s', 'give'), $ipnEventData->parent_txn_id, $ipnEventData->reason_code ), ]); DonationNote::create([ 'donationId' => $donation->id, 'content' => sprintf( /* translators: %s: Paypal transaction ID */ __('PayPal Refund Transaction ID: %s', 'give'), $ipnEventData->txn_id ), ]); $donation->status = DonationStatus::REFUNDED(); $donation->save(); } /** * @since 4.18.0.1 * * @param stdClass $ipnEventData PayPal IPN data. * @param string $currency Donation currency code. * * @return Money|null The IPN mc_gross in the donation currency, or null when it is missing or not a decimal amount. */ private function getRefundedAmount(stdClass $ipnEventData, string $currency): ?Money { try { return Money::fromDecimal($ipnEventData->mc_gross ?? '', $currency); } catch (Exception $e) { return null; } } /** * PayPal Standard sends refunds as a negative amount that cannot exceed the donation total. * * @since 4.18.0.1 * * @param Money $refundedAmount IPN mc_gross amount. * @param Money $donationAmount Donation total. * * @return bool */ private function isValidRefundAmount(Money $refundedAmount, Money $donationAmount): bool { return $refundedAmount->isNegative() && $refundedAmount->absolute()->lessThanOrEqual($donationAmount); } }