PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.16.3
GiveWP – Donation Plugin and Fundraising Platform v4.16.3
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 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / PaymentGateways / PayPalCommerce / PayPalCommerce.php
give / src / PaymentGateways / PayPalCommerce Last commit date
Banners 2 years ago DataTransferObjects 1 year ago Exceptions 3 years ago Migrations 2 years ago Models 9 months ago PayPalCheckoutSdk 1 year ago Repositories 1 year ago Webhooks 2 years ago AccountAdminNotices.php 4 years ago AdminSettingFields.php 7 months ago AdvancedCardFields.php 4 years ago AjaxRequestHandler.php 3 months ago DonationDetailsPage.php 4 years ago DonationFormPaymentMethod.php 2 years ago PayPalClient.php 2 years ago PayPalCommerce.php 10 months ago RefreshToken.php 2 years ago RefundPaymentHandler.php 4 years ago ScriptLoader.php 1 year ago Utils.php 2 years ago onBoardingRedirectHandler.php 7 months ago
PayPalCommerce.php
400 lines
1 <?php
2
3 namespace Give\PaymentGateways\PayPalCommerce;
4
5 use Exception;
6 use Give\Donations\Models\Donation;
7 use Give\Donations\Models\DonationNote;
8 use Give\Framework\PaymentGateways\Commands\GatewayCommand;
9 use Give\Framework\PaymentGateways\Commands\PaymentComplete;
10 use Give\Framework\PaymentGateways\Commands\PaymentRefunded;
11 use Give\Framework\PaymentGateways\Contracts\PaymentGatewayRefundable;
12 use Give\Framework\PaymentGateways\Exceptions\PaymentGatewayException;
13 use Give\Framework\PaymentGateways\PaymentGateway;
14 use Give\Framework\Support\ValueObjects\Money;
15 use Give\Log\Log;
16 use Give\PaymentGateways\PayPalCommerce\Models\MerchantDetail;
17
18 /**
19 * Class PayPalCommerce
20 *
21 * Boots the PayPalCommerce gateway and provides its basic registration properties
22 *
23 * @since 2.9.0
24 */
25 class PayPalCommerce extends PaymentGateway implements PaymentGatewayRefundable
26 {
27 /**
28 * @deprecated
29 *
30 * Use getId or id function to access payment gateway id.
31 *
32 */
33 const GATEWAY_ID = 'paypal-commerce';
34
35 /**
36 * @since 2.19.0
37 */
38 public function getLegacyFormFieldMarkup(int $formId, array $args): string
39 {
40 return give(AdvancedCardFields::class)->addCreditCardForm($formId);
41 }
42
43 /**
44 * @since 2.19.0
45 *
46 * @return string
47 */
48 public static function id(): string
49 {
50 return 'paypal-commerce';
51 }
52
53 /**
54 * @since 2.19.0
55 *
56 * @return string
57 */
58 public function getId(): string
59 {
60 return self::id();
61 }
62
63 /**
64 * @since 2.19.0
65 *
66 * @return string
67 */
68 public function getName(): string
69 {
70 return esc_html__('PayPal Donations', 'give');
71 }
72
73 /**
74 * @since 2.19.0
75 *
76 * @return string
77 */
78 public function getPaymentMethodLabel(): string
79 {
80 return esc_html__('Credit Card', 'give');
81 }
82
83 /**
84 * @since 4.2.1 updated to use updateOrderFromDonation
85 * @since 4.1.0 updated to include 3D Secure validation
86 * @since 4.0.0 updated to update and capture payment
87 * @since 2.19.0
88 *
89 * @param array{payPalOrderId: string|null, payPalAuthorizationId: string|null} $gatewayData
90 * @throws \Exception
91 */
92 public function createPayment(Donation $donation, $gatewayData): GatewayCommand
93 {
94 $payPalOrderId = $gatewayData['payPalOrderId'];
95
96 /** @var Repositories\PayPalOrder $payPalOrderRepository */
97 $payPalOrderRepository = give(Repositories\PayPalOrder::class);
98
99 $payPalOrder = $payPalOrderRepository->getApprovedOrder($payPalOrderId);
100
101 if ($payPalOrder->status === 'COMPLETED') {
102 $this->validatePayPalOrder($payPalOrder);
103
104 $transactionId = $payPalOrder->purchase_units[0]->payments->captures[0]->id;
105
106 } elseif ($payPalOrder->status === 'APPROVED' || $payPalOrder->status === 'CREATED') {
107 $this->validate3dSecure($payPalOrder);
108
109 if ($this->shouldUpdateOrder($donation, $payPalOrder)){
110 $payPalOrderRepository->updateOrderFromDonation($payPalOrderId, $donation);
111 }
112
113 // ready to capture order, response is the updated PayPal order.
114 $response = $payPalOrderRepository->approveOrder($payPalOrderId);
115
116 $this->validatePayPalOrder($response);
117
118 $transactionId = $response->purchase_units[0]->payments->captures[0]->id;
119 } else {
120 throw new PaymentGatewayException('PayPal Order status is not found.');
121 }
122
123 give()->payment_meta->update_meta(
124 $donation->id,
125 '_give_order_id',
126 $payPalOrderId
127 );
128
129 return PaymentComplete::make($transactionId)
130 ->setPaymentNotes(
131 sprintf(
132 __('Transaction Successful. PayPal Transaction ID: %1$s PayPal Order ID: %2$s', 'give'),
133 $transactionId,
134 $payPalOrderId
135 )
136 );
137 }
138
139 /**
140 * @since 4.5.0 Add Accept Credit Card (Smart Buttons Only) setting.
141 * @since 3.0.0 Conditionally add "Transaction Type" setting.
142 * @since 2.33.0 Register new payment field type setting.
143 * @since 2.27.3 Enable Venmo payment method by default.
144 * @since 2.16.2 Add setting "Transaction type".
145 */
146 public function getOptions()
147 {
148 /* @var MerchantDetail $merchantDetails */
149 $merchantDetails = give(MerchantDetail::class);
150
151 $settings = [
152 [
153 'type' => 'title',
154 'id' => 'give_gateway_settings_1',
155 'table_html' => false,
156 ],
157 [
158 'id' => 'paypal_commerce_introduction',
159 'type' => 'paypal_commerce_introduction',
160 ],
161 [
162 'type' => 'sectionend',
163 'id' => 'give_gateway_settings_1',
164 'table_html' => false,
165 ],
166 [
167 'type' => 'title',
168 'id' => 'give_gateway_settings_2',
169 ],
170 [
171 'name' => esc_html__('Account Country', 'give'),
172 'id' => 'paypal_commerce_account_country',
173 'type' => 'paypal_commerce_account_country',
174 ],
175 [
176 'name' => esc_html__('Connect With Paypal', 'give'),
177 'id' => 'paypal_commerce_account_manger',
178 'type' => 'paypal_commerce_account_manger',
179 ],
180 [
181 'name' => esc_html__('Accept Venmo', 'give'),
182 'id' => 'paypal_commerce_accept_venmo',
183 'type' => 'radio_inline',
184 'desc' => esc_html__(
185 'Displays a button allowing Donors to pay with Venmo (US-only). Donations still come into your PayPal account and are subject to normal PayPal transaction fees.',
186 'give'
187 ),
188 'default' => 'enabled',
189 'options' => [
190 'enabled' => esc_html__('Enabled', 'give'),
191 'disabled' => esc_html__('Disabled', 'give'),
192 ],
193 ],
194 [
195 'name' => esc_html__('Payment Field Type', 'give'),
196 'desc' => sprintf(
197 esc_html__(
198 '"Auto" provides the most payment options to the donor as possible, based on your account. "Smart Buttons Only" shows only the payment buttons. There is no "Hosted Only" option at this time due to limitations with PayPal\'s hosted fields. Not sure what this means? %1$sRead here%2$s.',
199 'give'
200 ),
201 '<a href="https://docs.givewp.com/paypal-settings" target="_blank">',
202 '</a>'
203 ),
204 'id' => 'paypal_payment_field_type',
205 'type' => 'radio_inline',
206 'options' => [
207 'auto' => esc_html__('Auto', 'give'),
208 'smart-buttons' => esc_html__('Smart Buttons Only', 'give'),
209 ],
210 'default' => 'auto',
211 ],
212 [
213 'name' => esc_html__('Accept Credit Card (Smart Buttons Only)', 'give'),
214 'id' => 'paypal_commerce_accept_credit_card',
215 'type' => 'radio_inline',
216 'desc' => esc_html__(
217 'Displays a button allowing Donors to pay with Credit Card. This option is only available if "Smart Buttons Only" is selected on Payment Field Type.',
218 'give'
219 ),
220 'default' => 'enabled',
221 'options' => [
222 'enabled' => esc_html__('Enabled', 'give'),
223 'disabled' => esc_html__('Disabled', 'give'),
224 ],
225 ],
226 [
227 'name' => esc_html__('PayPal Donations Gateway Settings Docs Link', 'give'),
228 'id' => 'paypal_commerce_gateway_settings_docs_link',
229 'url' => esc_url('http://docs.givewp.com/paypal-donations'),
230 'title' => esc_html__('PayPal Donations Gateway Settings', 'give'),
231 'type' => 'give_docs_link',
232 ],
233 [
234 'type' => 'sectionend',
235 'id' => 'give_gateway_settings_2',
236 ],
237 ];
238
239 if (Utils::isDonationTransactionTypeSupported($merchantDetails->accountCountry ?: '')) {
240 $settings = give_settings_array_insert(
241 $settings,
242 'paypal_commerce_accept_venmo',
243 [
244 [
245 'name' => esc_html__('Transaction Type', 'give'),
246 'desc' => esc_html__(
247 'Nonprofits must verify their status to withdraw donations they receive via PayPal. PayPal users that are not verified nonprofits must demonstrate how their donations will be used, once they raise more than $10,000. By default, GiveWP transactions are sent to PayPal as donations. You may change the transaction type using this option if you feel you may not meet PayPal\'s donation requirements.',
248 'give'
249 ),
250 'id' => 'paypal_commerce_transaction_type',
251 'type' => 'radio_inline',
252 'options' => [
253 'donation' => esc_html__('Donation', 'give'),
254 'standard' => esc_html__('Standard Transaction', 'give'),
255 ],
256 'default' => 'donation',
257 ],
258 ]
259 );
260 }
261
262 if ($merchantDetails->accountIsReady) {
263 $settings = give_settings_array_insert(
264 $settings,
265 'paypal_commerce_gateway_settings_docs_link',
266 [
267 [
268 'name' => esc_html__('Collect Billing Details', 'give'),
269 'id' => 'paypal_commerce_collect_billing_details',
270 'type' => 'radio_inline',
271 'desc' => esc_html__(
272 'If enabled, required billing address fields are added to PayPal Donations Donation forms. These fields are required to process the transaction when enabled. Billing address details are added to both the donation and donor record in GiveWP.',
273 'give'
274 ),
275 'default' => 'disabled',
276 'options' => [
277 'enabled' => esc_html__('Enabled', 'give'),
278 'disabled' => esc_html__('Disabled', 'give'),
279 ],
280 ],
281 ]
282 );
283 }
284
285 /**
286 * filter the settings
287 *
288 * @since 2.9.6
289 */
290 return apply_filters('give_get_settings_paypal_commerce', $settings);
291 }
292
293 /**
294 * @since 4.0.0
295 */
296 private function shouldUpdateOrder(Donation $donation, $payPalOrder): bool
297 {
298 $orderAmount = $payPalOrder->purchase_units[0]->amount->value;
299 $orderCurrency = $payPalOrder->purchase_units[0]->amount->currency_code;
300 $currentOrderAmount = Money::fromDecimal($orderAmount, $orderCurrency);
301
302 if (!$currentOrderAmount->equals($donation->amount)) {
303 Log::error(
304 sprintf(
305 'Initial PayPal Order amount does not match donation amount. PayPal Order ID: %s, Donation ID: %s',
306 $payPalOrder->id,
307 $donation->id
308 )
309 );
310
311 return true;
312 }
313
314 return false;
315 }
316
317 /**
318 * @throws PaymentGatewayException
319 */
320 private function validatePayPalOrder(object $payPalOrder): void
321 {
322 $transaction = $payPalOrder->purchase_units[0]->payments->captures[0];
323
324 $errors = property_exists($payPalOrder, 'details') ? $payPalOrder->details[0] : [];
325
326 if (!$transaction) {
327 throw new PaymentGatewayException('PayPal Order does not have a transaction.');
328 }
329
330 if ($transaction->status === "DECLINED") {
331 $errorMessage = sprintf(
332 __('PayPal Order has been declined. Transaction status:: %s', 'give'),
333 $transaction->status
334 );
335
336 throw new PaymentGatewayException($errorMessage);
337 }
338
339 if (!empty($errors)) {
340 $errorMessage = sprintf(
341 __('PayPal Order has an error: %s', 'give'),
342 $errors->issue[0]->description
343 );
344
345 throw new PaymentGatewayException($errorMessage);
346 }
347
348 $this->validate3dSecure($payPalOrder);
349 }
350
351 /**
352 * @since 4.1.0
353 *
354 * @throws PaymentGatewayException
355 */
356 private function validate3dSecure(object $payPalOrder): void
357 {
358 // Check if the order is not ready for 3D Secure authentication
359 if (isset($payPalOrder->payment_source->card->authentication_result->liability_shift) && !in_array($payPalOrder->payment_source->card->authentication_result->liability_shift, ['POSSIBLE', 'YES'])) {
360 throw new PaymentGatewayException('Card type and issuing bank are not ready to complete a 3D Secure authentication.');
361 }
362 }
363
364 /**
365 * @since 4.7.0
366 *
367 * @throws Exception
368 */
369 public function refundDonation(Donation $donation): PaymentRefunded
370 {
371 try {
372 $payPalOrderRepository = give(Repositories\PayPalOrder::class);
373 $payPalOrderRepository->refundPayment($donation->gatewayTransactionId);
374
375 DonationNote::create([
376 'donationId' => $donation->id,
377 'content' => sprintf(
378 __('Donation refunded in PayPal for transaction ID: %s', 'give'),
379 $donation->gatewayTransactionId
380 ),
381 ]);
382
383 return new PaymentRefunded();
384 } catch (Exception $e) {
385 DonationNote::create([
386 'donationId' => $donation->id,
387 'content' => sprintf(
388 __(
389 'Error! Donation %s was NOT refunded. Find more details on the error in the logs at Donations > Tools > Logs. To refund the donation, use the PayPal dashboard tools.',
390 'give'
391 ),
392 $donation->id
393 ),
394 ]);
395
396 throw new PaymentGatewayException(sprintf(__('PayPal API error: %s', 'give'), $e->getMessage()));
397 }
398 }
399 }
400