← All changes
|
src/PaymentGateways/Gateways/PayPalStandard/Actions/ProcessIpnDonationRefund.php
+83
-39
4.16.2
→
4.18.0.1
View file →
| @@ -1,10 +1,14 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace Give\PaymentGateways\Gateways\PayPalStandard\Actions; |
| 4 | 4 | |
| 5 | -use Give\ValueObjects\Money; | |
| 6 | -use Give_Payment; | |
| 5 | +use Exception; | |
| 6 | +use Give\Donations\Models\Donation; | |
| 7 | +use Give\Donations\Models\DonationNote; | |
| 8 | +use Give\Donations\ValueObjects\DonationStatus; | |
| 9 | +use Give\Framework\Support\ValueObjects\Money; | |
| 10 | +use Give\Log\Log; | |
| 7 | 11 | use stdClass; |
| 8 | 12 | |
| 9 | 13 | /** |
| 10 | 14 | * @since 2.19.0 |
| @@ -11,63 +15,103 @@ | ||
| 11 | 15 | */ |
| 12 | 16 | class ProcessIpnDonationRefund |
| 13 | 17 | { |
| 14 | 18 | /** |
| 19 | + * @since 4.18.0.1 Use the Donation model and skip refunds with a missing, non-negative, or over-total amount. | |
| 15 | 20 | * @since 2.19.0 |
| 16 | 21 | * |
| 17 | - * @param stdClass $ipnEventData | |
| 18 | - * @param int $donationId | |
| 22 | + * @param stdClass $ipnEventData PayPal IPN data. | |
| 23 | + * @param int $donationId ID of the donation being refunded. | |
| 19 | 24 | * |
| 20 | 25 | * @return void |
| 21 | 26 | */ |
| 22 | 27 | public function __invoke(stdClass $ipnEventData, $donationId) |
| 23 | 28 | { |
| 24 | - $donation = new Give_Payment($donationId); | |
| 25 | - if ($this->isPartialRefund($ipnEventData->mc_gross, $donation->currency, $donation->total)) { | |
| 26 | - $donation->add_note( | |
| 27 | - sprintf( /* translators: %s: Paypal parent transaction ID */ | |
| 29 | + $donation = Donation::find($donationId); | |
| 30 | + | |
| 31 | + if ( ! $donation) { | |
| 32 | + return; | |
| 33 | + } | |
| 34 | + | |
| 35 | + $refundedAmount = $this->getRefundedAmount($ipnEventData, $donation->amount->getCurrency()->getCode()); | |
| 36 | + | |
| 37 | + if ( ! $refundedAmount || ! $this->isValidRefundAmount($refundedAmount, $donation->amount)) { | |
| 38 | + Log::error( | |
| 39 | + 'PayPal Standard IPN Error', | |
| 40 | + [ | |
| 41 | + 'Message' => sprintf( | |
| 42 | + 'Refund amount (%s) for donation #%d is not valid.', | |
| 43 | + $ipnEventData->mc_gross ?? '(not set)', | |
| 44 | + $donationId | |
| 45 | + ), | |
| 46 | + 'Event Data' => $ipnEventData, | |
| 47 | + ] | |
| 48 | + ); | |
| 49 | + | |
| 50 | + return; | |
| 51 | + } | |
| 52 | + | |
| 53 | + if ($refundedAmount->absolute()->lessThan($donation->amount)) { | |
| 54 | + DonationNote::create([ | |
| 55 | + 'donationId' => $donation->id, | |
| 56 | + 'content' => sprintf( /* translators: %s: Paypal parent transaction ID */ | |
| 28 | 57 | __('Partial PayPal refund processed: %s', 'give'), |
| 29 | 58 | $ipnEventData->parent_txn_id |
| 30 | - ) | |
| 31 | - ); | |
| 32 | - } else { | |
| 33 | - $donation->add_note( | |
| 34 | - sprintf( /* translators: 1: Paypal parent transaction ID 2. Paypal reason code */ | |
| 35 | - __('PayPal Payment #%1$s Refunded for reason: %2$s', 'give'), | |
| 36 | - $ipnEventData->parent_txn_id, | |
| 37 | - $ipnEventData->reason_code | |
| 38 | - ) | |
| 39 | - ); | |
| 59 | + ), | |
| 60 | + ]); | |
| 40 | 61 | |
| 41 | - $donation->add_note( | |
| 42 | - sprintf( /* translators: %s: Paypal transaction ID */ | |
| 43 | - __('PayPal Refund Transaction ID: %s', 'give'), | |
| 44 | - $ipnEventData->txn_id | |
| 45 | - ) | |
| 46 | - ); | |
| 62 | + return; | |
| 63 | + } | |
| 47 | 64 | |
| 48 | - $donation->update_status('refunded'); | |
| 65 | + DonationNote::create([ | |
| 66 | + 'donationId' => $donation->id, | |
| 67 | + 'content' => sprintf( /* translators: 1: Paypal parent transaction ID 2. Paypal reason code */ | |
| 68 | + __('PayPal Payment #%1$s Refunded for reason: %2$s', 'give'), | |
| 69 | + $ipnEventData->parent_txn_id, | |
| 70 | + $ipnEventData->reason_code | |
| 71 | + ), | |
| 72 | + ]); | |
| 73 | + | |
| 74 | + DonationNote::create([ | |
| 75 | + 'donationId' => $donation->id, | |
| 76 | + 'content' => sprintf( /* translators: %s: Paypal transaction ID */ | |
| 77 | + __('PayPal Refund Transaction ID: %s', 'give'), | |
| 78 | + $ipnEventData->txn_id | |
| 79 | + ), | |
| 80 | + ]); | |
| 81 | + | |
| 82 | + $donation->status = DonationStatus::REFUNDED(); | |
| 83 | + $donation->save(); | |
| 84 | + } | |
| 85 | + | |
| 86 | + /** | |
| 87 | + * @since 4.18.0.1 | |
| 88 | + * | |
| 89 | + * @param stdClass $ipnEventData PayPal IPN data. | |
| 90 | + * @param string $currency Donation currency code. | |
| 91 | + * | |
| 92 | + * @return Money|null The IPN mc_gross in the donation currency, or null when it is missing or not a decimal amount. | |
| 93 | + */ | |
| 94 | + private function getRefundedAmount(stdClass $ipnEventData, string $currency): ?Money | |
| 95 | + { | |
| 96 | + try { | |
| 97 | + return Money::fromDecimal($ipnEventData->mc_gross ?? '', $currency); | |
| 98 | + } catch (Exception $e) { | |
| 99 | + return null; | |
| 49 | 100 | } |
| 50 | 101 | } |
| 51 | 102 | |
| 52 | 103 | /** |
| 53 | - * @since 2.19.0 | |
| 104 | + * PayPal Standard sends refunds as a negative amount that cannot exceed the donation total. | |
| 54 | 105 | * |
| 55 | - * @param string $refundedAmount | |
| 56 | - * @param $currency | |
| 57 | - * @param $donationAmount | |
| 106 | + * @since 4.18.0.1 | |
| 58 | 107 | * |
| 108 | + * @param Money $refundedAmount IPN mc_gross amount. | |
| 109 | + * @param Money $donationAmount Donation total. | |
| 110 | + * | |
| 59 | 111 | * @return bool |
| 60 | 112 | */ |
| 61 | - protected function isPartialRefund($refundedAmount, $currency, $donationAmount) | |
| 113 | + private function isValidRefundAmount(Money $refundedAmount, Money $donationAmount): bool | |
| 62 | 114 | { |
| 63 | - $donationAmount = Money::of($donationAmount, $currency); | |
| 64 | - $refundedAmountOnPayPal = Money::of( | |
| 65 | - // PayPal Standard sends negative amount when refund payment. | |
| 66 | - // Check details https://developer.paypal.com/api/nvp-soap/ipn/IPNandPDTVariables/ | |
| 67 | - $refundedAmount * -1, | |
| 68 | - $currency | |
| 69 | - ); | |
| 70 | - | |
| 71 | - return $refundedAmountOnPayPal->getMinorAmount() < $donationAmount->getMinorAmount(); | |
| 115 | + return $refundedAmount->isNegative() && $refundedAmount->absolute()->lessThanOrEqual($donationAmount); | |
| 72 | 116 | } |
| 73 | 117 | } |