PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.18.0.1
GiveWP – Donation Plugin and Fundraising Platform v4.18.0.1
4.18.0.1 4.18.0 4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 All 258 releases
← All changes | src/PaymentGateways/Gateways/PayPalStandard/Actions/ProcessIpnDonationRefund.php +83 -39 4.18.0 → 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 }