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 / AjaxRequestHandler.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
AjaxRequestHandler.php
522 lines
1 <?php
2
3 namespace Give\PaymentGateways\PayPalCommerce;
4
5 use Give\PaymentGateways\PayPalCommerce\Models\MerchantDetail;
6 use Give\PaymentGateways\PayPalCommerce\PayPalCheckoutSdk\ProcessorResponseError;
7 use Give\PaymentGateways\PayPalCommerce\Repositories\MerchantDetails;
8 use Give\PaymentGateways\PayPalCommerce\Repositories\PayPalAuth;
9 use Give\PaymentGateways\PayPalCommerce\Repositories\PayPalOrder;
10 use Give\PaymentGateways\PayPalCommerce\Repositories\Settings;
11 use Give\PaymentGateways\PayPalCommerce\Repositories\Webhooks;
12 use Give\Helpers\Form\Utils as FormUtils;
13
14 /**
15 * Class AjaxRequestHandler
16 * @package Give\PaymentGateways\PaypalCommerce
17 *
18 * @sicne 2.9.0
19 */
20 class AjaxRequestHandler
21 {
22 /**
23 * @since 2.9.0
24 *
25 * @var Webhooks
26 */
27 private $webhooksRepository;
28
29 /**
30 * @since 2.9.0
31 *
32 * @var MerchantDetail
33 */
34 private $merchantDetails;
35
36 /**
37 * @since 2.9.0
38 *
39 * @var PayPalAuth
40 */
41 private $payPalAuth;
42
43 /**
44 * @since 2.9.0
45 *
46 * @var MerchantDetails
47 */
48 private $merchantRepository;
49
50 /**
51 * @since 2.9.0
52 *
53 * @var RefreshToken
54 */
55 private $refreshToken;
56
57 /**
58 * @since 2.9.0
59 *
60 * @var Settings
61 */
62 private $settings;
63
64 /**
65 * AjaxRequestHandler constructor.
66 *
67 * @since 2.9.0
68 *
69 * @param Webhooks $webhooksRepository
70 * @param MerchantDetail $merchantDetails
71 * @param MerchantDetails $merchantRepository
72 * @param RefreshToken $refreshToken
73 * @param Settings $settings
74 * @param PayPalAuth $payPalAuth
75 */
76 public function __construct(
77 Webhooks $webhooksRepository,
78 MerchantDetail $merchantDetails,
79 MerchantDetails $merchantRepository,
80 RefreshToken $refreshToken,
81 Settings $settings,
82 PayPalAuth $payPalAuth
83 ) {
84 $this->webhooksRepository = $webhooksRepository;
85 $this->merchantDetails = $merchantDetails;
86 $this->merchantRepository = $merchantRepository;
87 $this->refreshToken = $refreshToken;
88 $this->settings = $settings;
89 $this->payPalAuth = $payPalAuth;
90 }
91
92 /**
93 * give_paypal_commerce_user_onboarded ajax action handler
94 *
95 * @since 2.32.0 Return error response on exception when fetch access token from authorization code.
96 * @since 2.9.0
97 */
98 public function onBoardedUserAjaxRequestHandler()
99 {
100 $this->validateAdminRequest();
101
102 if (empty($_GET['mode']) || ! in_array($_GET['mode'], ['sandbox', 'live'])) {
103 wp_send_json_error('Must include valid mode');
104 }
105
106 $mode = sanitize_text_field(wp_unslash($_GET['mode']));
107
108 // Set PayPal client mode.
109 give(PayPalClient::class)->setMode($mode);
110
111 $partnerLinkInfo = $this->settings->getPartnerLinkDetails();
112
113 try {
114 $payPalResponse = $this->payPalAuth->getTokenFromAuthorizationCode(
115 give_clean($_GET['authCode']),
116 give_clean($_GET['sharedId']),
117 $partnerLinkInfo['nonce']
118 );
119 } catch (\Exception $exception) {
120 wp_send_json_error();
121 }
122
123 $this->settings->updateAccessToken($payPalResponse);
124
125 // Set cron job to refresh token.
126 $refreshToken = give(RefreshToken::class);
127 $refreshToken->setMode($mode);
128 $refreshToken->registerCronJobToRefreshToken($payPalResponse['expiresIn']);
129
130 wp_send_json_success();
131 }
132
133 /**
134 * This function handle ajax request with give_paypal_commerce_get_partner_url action.
135 *
136 * @since 3.0.0 Add support for accountType. This param is required to get partner link.
137 * @since 2.30.0 Add support for mode param.
138 * @since 2.9.0
139 */
140 public function onGetPartnerUrlAjaxRequestHandler()
141 {
142 $this->validateAdminRequest();
143
144 if (empty($accountType = $_GET['accountType']) || ! in_array($accountType, ScriptLoader::$accountTypes, true)) {
145 wp_send_json_error('Must include valid account type');
146 }
147
148 if (empty($country = $_GET['countryCode']) || ! isset(give_get_country_list()[$country])) {
149 wp_send_json_error('Must include valid 2-character country code');
150 }
151
152 if (empty($_GET['mode']) || ! in_array($_GET['mode'], ['sandbox', 'live'])) {
153 wp_send_json_error('Must include valid mode');
154 }
155
156 $country = sanitize_text_field(wp_unslash($_GET['countryCode']));
157 $accountType = sanitize_text_field(wp_unslash($_GET['accountType']));
158 $mode = sanitize_text_field(wp_unslash($_GET['mode']));
159
160 // Generate a unique state token for CSRF protection on PayPal callback.
161 $stateToken = wp_generate_password(32, false);
162 set_transient('give_paypal_onboarding_state_' . $mode, $stateToken, HOUR_IN_SECONDS);
163
164 $redirectUrl = add_query_arg(
165 [
166 'tab' => 'gateways',
167 'section' => 'paypal',
168 'group' => 'paypal-commerce',
169 'mode' => $mode,
170 'give_paypal_state' => $stateToken,
171 ],
172 admin_url('edit.php?post_type=give_forms&page=give-settings')
173 );
174
175 // Set PayPal client mode.
176 give(PayPalClient::class)->setMode($mode);
177
178 $data = $this->payPalAuth->getSellerPartnerLink($redirectUrl, $accountType);
179
180 if (! $data) {
181 wp_send_json_error();
182 }
183
184 $this->settings->updateAccountCountry($country);
185 $this->settings->updatePartnerLinkDetails($data);
186
187 wp_send_json_success($data);
188 }
189
190 /**
191 * give_paypal_commerce_disconnect_account ajax request handler.
192 *
193 * @since 3.16.0 added security nonce check
194 * @since 3.13.0 Add new $keepWebhooks option
195 * @since 2.30.0 Add support for mode param.
196 * @since 2.25.0 Remove merchant seller token.
197 * @since 2.9.0
198 */
199 public function removePayPalAccount()
200 {
201 check_ajax_referer( 'give_paypal_commerce_disconnect_account');
202
203 if (! current_user_can('manage_give_settings')) {
204 wp_send_json_error(['error' => esc_html__('You are not allowed to perform this action.', 'give')]);
205 }
206
207 try {
208 $mode = give_clean($_POST['mode']);
209 $keepWebhooks = rest_sanitize_boolean($_POST['keep-webhooks']);
210 $this->webhooksRepository->setMode($mode);
211 $this->merchantRepository->setMode($mode);
212 $this->refreshToken->setMode($mode);
213 $this->settings->setMode($mode);
214
215 $this->validateAdminRequest();
216
217 // Remove the webhook from PayPal if there is one
218 if ( ! $keepWebhooks && $webhookConfig = $this->webhooksRepository->getWebhookConfig()) {
219 $this->webhooksRepository->deleteWebhook($this->merchantDetails->accessToken, $webhookConfig->id);
220 $this->webhooksRepository->deleteWebhookConfig();
221 }
222
223 $this->merchantRepository->delete();
224 $this->merchantRepository->deleteAccountErrors();
225 $this->merchantRepository->deleteClientToken();
226 $this->settings->deleteSellerAccessToken();
227 $this->refreshToken->deleteRefreshTokenCronJob();
228
229 wp_send_json_success();
230 } catch (\Exception $exception) {
231 wp_send_json_error(['error' => $exception->getMessage()]);
232 }
233 }
234
235 /**
236 * Create order.
237 *
238 * @todo: handle payment create error on frontend.
239 *
240 * @since 3.1.0 Remove unused variable from createOrder argument.
241 * @since 2.9.0
242 */
243 public function createOrder()
244 {
245 $this->validateFrontendRequest();
246 $data = $this->getOrderData();
247
248 try {
249 $result = give(PayPalOrder::class)->createOrder($data);
250
251 wp_send_json_success(
252 [
253 'id' => $result,
254 ]
255 );
256 } catch (\Exception $ex) {
257 wp_send_json_error(
258 [
259 'error' => json_decode($ex->getMessage(), true),
260 ]
261 );
262 }
263 }
264
265 /**
266 * @since 4.14.4 Validate donation amount before creating or updating an order.
267 * @since 4.2.1 Only filter amount for v2 forms.
268 * @since 3.4.2
269 */
270 private function getOrderData(): array
271 {
272 $postData = give_clean($_POST);
273 $formId = absint($postData['give-form-id']);
274 $donorAddress = $this->getDonorAddressFromPostedDataForPaypalOrder($postData);
275 $isV3Form = FormUtils::isV3Form($formId);
276
277 if ($isV3Form) {
278 // if coming from v3 forms, fee recovery is already included in the amount and should not be filtered.
279 $amount = isset($postData['give-amount']) ? give_clean($postData['give-amount']) : '0.00';
280 } else {
281 $amount = isset($postData['give-amount']) ?
282 (float)apply_filters(
283 'give_donation_total',
284 give_maybe_sanitize_amount(
285 $postData['give-amount'],
286 ['currency' => give_get_currency($formId)]
287 )
288 ) :
289 '0.00';
290 }
291
292 $this->validateDonationAmount($amount, $formId);
293
294 return [
295 'formId' => $formId,
296 'formTitle' => give_payment_gateway_item_title(['post_data' => $postData], 127),
297 'donationAmount' => $amount,
298 'payer' => [
299 'firstName' => $postData['give_first'],
300 'lastName' => $postData['give_last'],
301 'email' => $postData['give_email'],
302 'address' => $donorAddress,
303 ],
304 ];
305 }
306
307 /**
308 * Approve order.
309 *
310 * @todo: handle payment capture error on frontend.
311 *
312 * @since 4.14.4 Validate donation amount before approving an order.
313 * @since 3.2.0 Discover error by checking capture status.
314 * @since 2.9.0
315 */
316 public function approveOrder()
317 {
318 $this->validateFrontendRequest();
319
320 $orderId = give_clean($_GET['order']);
321 $updateAmount = filter_var(give_clean($_GET['update_amount']), FILTER_VALIDATE_BOOLEAN);
322
323 try {
324 if ($updateAmount) {
325 $orderData = $this->getOrderData();
326 $this->validateOrderAmountNotDecreased($orderId, $orderData['donationAmount']);
327 give(PayPalOrder::class)->updateOrderAmount($orderId, $orderData);
328 }
329
330 $result = give(PayPalOrder::class)->approveOrder($orderId);
331 // PayPal does not return error in case of invalid cvv. So we need to check capture status and return error.
332 // ref - https://feedback.givewp.com/bug-reports/p/paypal-credit-card-donations-can-generate-a-fatal-error
333 $this->returnErrorOnFailedApproveOrderResponse($result);
334 wp_send_json_success(['order' => $result,]);
335 } catch (\Exception $ex) {
336 wp_send_json_error(['error' => json_decode($ex->getMessage(), true),]);
337 }
338 }
339
340 /**
341 * @since 4.14.4 Validate donation amount before updating an order amount.
342 * @since 3.4.2
343 */
344 public function updateOrderAmount()
345 {
346 $this->validateFrontendRequest();
347
348 $orderId = give_clean($_GET['order']);
349
350 try {
351 $orderData = $this->getOrderData();
352 $this->validateOrderAmountNotDecreased($orderId, $orderData['donationAmount']);
353 give(PayPalOrder::class)->updateOrderAmount($orderId, $orderData);
354
355 wp_send_json_success(['order' => $orderId,]);
356 } catch (\Exception $ex) {
357 wp_send_json_error(['error' => json_decode($ex->getMessage(), true),]);
358 }
359 }
360
361 /**
362 * Return on boarding trouble notice.
363 *
364 * @since 2.9.6
365 */
366 public function onBoardingTroubleNotice()
367 {
368 if (! current_user_can('manage_give_settings')) {
369 wp_die();
370 }
371
372 /* @var AdminSettingFields $adminSettingFields */
373 $adminSettingFields = give(AdminSettingFields::class);
374
375 $actionList = sprintf(
376 '<ol><li>%1$s</li><li>%2$s</li><li>%3$s</li></ol>',
377 esc_html__(
378 'Make sure to complete the entire PayPal process. Do not close the window until you have finished the process.',
379 'give'
380 ),
381 esc_html__(
382 'The last screen of the PayPal connect process includes a button to be sent back to your site. It is important you click this and do not close the window yourself.',
383 'give'
384 ),
385 esc_html__(
386 'If you’re still having problems connecting: ',
387 'give'
388 ) . $adminSettingFields->getAdminGuidanceNotice(false)
389 );
390
391 $standardError = sprintf(
392 '<div id="give-paypal-onboarding-trouble-notice" class="give-hidden"><p class="error-message">%1$s</p><p>%2$s</p></div>',
393 esc_html__('Having trouble connecting to PayPal?', 'give'),
394 $actionList
395 );
396
397 wp_send_json_success($standardError);
398 }
399
400 /**
401 * Validate admin ajax request.
402 *
403 * @since 2.9.0
404 */
405 private function validateAdminRequest()
406 {
407 if (! current_user_can('manage_give_settings')) {
408 wp_die();
409 }
410 }
411
412 /**
413 * Validate frontend ajax request.
414 *
415 * @since 2.9.0
416 */
417 private function validateFrontendRequest()
418 {
419 $formId = absint($_POST['give-form-id']);
420
421 if (! $formId || ! give_verify_donation_form_nonce(give_clean($_POST['give-form-hash']), $formId)) {
422 wp_die();
423 }
424 }
425
426 /**
427 * Validate the donation amount against the form's configured maximum and that it is positive.
428 *
429 * @since 4.14.4
430 *
431 * @param float|string $amount
432 * @param int $formId
433 */
434 private function validateDonationAmount($amount, int $formId): void
435 {
436 $amount = (float)$amount;
437
438 if ($amount <= 0) {
439 wp_send_json_error(['error' => __('Invalid donation amount.', 'give')]);
440 }
441
442 $maxAmount = (float)give_get_form_maximum_price($formId);
443 if ($maxAmount > 0 && $amount > $maxAmount) {
444 wp_send_json_error([
445 'error' => sprintf(
446 /* translators: %s: maximum donation amount */
447 __('Donation amount must not exceed %s.', 'give'),
448 give_currency_filter(give_format_amount($maxAmount, ['sanitize' => false]))
449 ),
450 ]);
451 }
452 }
453
454 /**
455 * Validate that the new donation amount is not less than the original PayPal order amount.
456 *
457 * @since 4.14.4
458 *
459 * @param string $orderId
460 * @param float|string $newAmount
461 */
462 private function validateOrderAmountNotDecreased(string $orderId, $newAmount): void
463 {
464 $newAmount = (float)$newAmount;
465
466 $currentOrder = give(PayPalOrder::class)->getApprovedOrder($orderId);
467 $currentAmount = (float)$currentOrder->purchase_units[0]->amount->value;
468
469 if ($newAmount < $currentAmount) {
470 wp_send_json_error([
471 'error' => __('Donation amount cannot be decreased.', 'give'),
472 ]);
473 }
474 }
475
476 /**
477 * This function should return address array in PayPal rest api accepted format.
478 *
479 * @since 3.1.0 Return address only if setting enabled and has valida country in PayPal accepted formatted.
480 * @since 2.11.1
481 */
482 private function getDonorAddressFromPostedDataForPaypalOrder(array $postedData): array
483 {
484 if (empty($postedData['billing_country'])) {
485 return [];
486 }
487
488 $address['address_line_1'] = ! empty($postedData['card_address']) ? $postedData['card_address'] : '';
489 $address['address_line_2'] = ! empty($postedData['card_address_2']) ? $postedData['card_address_2'] : '';
490 $address['admin_area_2'] = ! empty($postedData['card_city']) ? $postedData['card_city'] : '';
491 $address['admin_area_1'] = ! empty($postedData['card_state']) ? $postedData['card_state'] : '';
492 $address['postal_code'] = ! empty($postedData['card_zip']) ? $postedData['card_zip'] : '';
493 $address['country_code'] = ! empty($postedData['billing_country']) ? $postedData['billing_country'] : '';
494
495 return $address;
496 }
497
498 /**
499 * This function should validate PayPal ApproveOrder response and respond to ajax request on error.
500 *
501 * @since 3.2.0
502 */
503 private function returnErrorOnFailedApproveOrderResponse(\stdClass $response)
504 {
505 // Get capture.
506 // ref - https://developer.paypal.com/docs/api/orders/v2/#orders_capture
507 $capture = $response->purchase_units[0]->payments->captures[0];
508
509 // Check if capture status is failed or declined.
510 if (
511 in_array($capture->status, ['FAILED', 'DECLINED'])
512 && property_exists($capture, 'processor_response')
513 ) {
514 $error = ProcessorResponseError::getError($capture->processor_response);
515
516 if ($error) {
517 wp_send_json_error(['error' => $error]);
518 }
519 }
520 }
521 }
522