PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.16.8
GiveWP – Donation Plugin and Fundraising Platform v4.16.8
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 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 3 years ago DataTransferObjects 1 year ago Exceptions 3 years ago Migrations 3 years ago Models 11 months ago PayPalCheckoutSdk 1 year ago Repositories 1 year ago Webhooks 2 years ago AccountAdminNotices.php 4 years ago AdminSettingFields.php 8 months ago AdvancedCardFields.php 4 years ago AjaxRequestHandler.php 1 week ago DonationDetailsPage.php 4 years ago DonationFormPaymentMethod.php 2 years ago PayPalClient.php 3 years ago PayPalCommerce.php 1 year ago RefreshToken.php 3 years ago RefundPaymentHandler.php 4 years ago ScriptLoader.php 1 year ago Utils.php 2 years ago onBoardingRedirectHandler.php 8 months ago
AjaxRequestHandler.php
618 lines
1 <?php
2
3 namespace Give\PaymentGateways\PayPalCommerce;
4
5 use Give\DonationForms\Actions\ValidateDonationFormRequest;
6 use Give\DonationForms\Exceptions\DonationFormFieldErrorsException;
7 use Give\DonationForms\Exceptions\DonationFormForbidden;
8 use Give\Log\Log;
9 use Give\PaymentGateways\PayPalCommerce\Models\MerchantDetail;
10 use Give\PaymentGateways\PayPalCommerce\PayPalCheckoutSdk\ProcessorResponseError;
11 use Give\PaymentGateways\PayPalCommerce\Repositories\MerchantDetails;
12 use Give\PaymentGateways\PayPalCommerce\Repositories\PayPalAuth;
13 use Give\PaymentGateways\PayPalCommerce\Repositories\PayPalOrder;
14 use Give\PaymentGateways\PayPalCommerce\Repositories\Settings;
15 use Give\PaymentGateways\PayPalCommerce\Repositories\Webhooks;
16 use Give\Helpers\Form\Utils as FormUtils;
17
18 /**
19 * Class AjaxRequestHandler
20 * @package Give\PaymentGateways\PaypalCommerce
21 *
22 * @sicne 2.9.0
23 */
24 class AjaxRequestHandler
25 {
26 /**
27 * @since 2.9.0
28 *
29 * @var Webhooks
30 */
31 private $webhooksRepository;
32
33 /**
34 * @since 2.9.0
35 *
36 * @var MerchantDetail
37 */
38 private $merchantDetails;
39
40 /**
41 * @since 2.9.0
42 *
43 * @var PayPalAuth
44 */
45 private $payPalAuth;
46
47 /**
48 * @since 2.9.0
49 *
50 * @var MerchantDetails
51 */
52 private $merchantRepository;
53
54 /**
55 * @since 2.9.0
56 *
57 * @var RefreshToken
58 */
59 private $refreshToken;
60
61 /**
62 * @since 2.9.0
63 *
64 * @var Settings
65 */
66 private $settings;
67
68 /**
69 * AjaxRequestHandler constructor.
70 *
71 * @since 2.9.0
72 *
73 * @param Webhooks $webhooksRepository
74 * @param MerchantDetail $merchantDetails
75 * @param MerchantDetails $merchantRepository
76 * @param RefreshToken $refreshToken
77 * @param Settings $settings
78 * @param PayPalAuth $payPalAuth
79 */
80 public function __construct(
81 Webhooks $webhooksRepository,
82 MerchantDetail $merchantDetails,
83 MerchantDetails $merchantRepository,
84 RefreshToken $refreshToken,
85 Settings $settings,
86 PayPalAuth $payPalAuth
87 ) {
88 $this->webhooksRepository = $webhooksRepository;
89 $this->merchantDetails = $merchantDetails;
90 $this->merchantRepository = $merchantRepository;
91 $this->refreshToken = $refreshToken;
92 $this->settings = $settings;
93 $this->payPalAuth = $payPalAuth;
94 }
95
96 /**
97 * give_paypal_commerce_user_onboarded ajax action handler
98 *
99 * @since 2.32.0 Return error response on exception when fetch access token from authorization code.
100 * @since 2.9.0
101 */
102 public function onBoardedUserAjaxRequestHandler()
103 {
104 $this->validateAdminRequest();
105
106 if (empty($_GET['mode']) || ! in_array($_GET['mode'], ['sandbox', 'live'])) {
107 wp_send_json_error('Must include valid mode');
108 }
109
110 $mode = sanitize_text_field(wp_unslash($_GET['mode']));
111
112 // Set PayPal client mode.
113 give(PayPalClient::class)->setMode($mode);
114
115 $partnerLinkInfo = $this->settings->getPartnerLinkDetails();
116
117 try {
118 $payPalResponse = $this->payPalAuth->getTokenFromAuthorizationCode(
119 give_clean($_GET['authCode']),
120 give_clean($_GET['sharedId']),
121 $partnerLinkInfo['nonce']
122 );
123 } catch (\Exception $exception) {
124 wp_send_json_error();
125 }
126
127 $this->settings->updateAccessToken($payPalResponse);
128
129 // Set cron job to refresh token.
130 $refreshToken = give(RefreshToken::class);
131 $refreshToken->setMode($mode);
132 $refreshToken->registerCronJobToRefreshToken($payPalResponse['expiresIn']);
133
134 wp_send_json_success();
135 }
136
137 /**
138 * This function handle ajax request with give_paypal_commerce_get_partner_url action.
139 *
140 * @since 3.0.0 Add support for accountType. This param is required to get partner link.
141 * @since 2.30.0 Add support for mode param.
142 * @since 2.9.0
143 */
144 public function onGetPartnerUrlAjaxRequestHandler()
145 {
146 $this->validateAdminRequest();
147
148 if (empty($accountType = $_GET['accountType']) || ! in_array($accountType, ScriptLoader::$accountTypes, true)) {
149 wp_send_json_error('Must include valid account type');
150 }
151
152 if (empty($country = $_GET['countryCode']) || ! isset(give_get_country_list()[$country])) {
153 wp_send_json_error('Must include valid 2-character country code');
154 }
155
156 if (empty($_GET['mode']) || ! in_array($_GET['mode'], ['sandbox', 'live'])) {
157 wp_send_json_error('Must include valid mode');
158 }
159
160 $country = sanitize_text_field(wp_unslash($_GET['countryCode']));
161 $accountType = sanitize_text_field(wp_unslash($_GET['accountType']));
162 $mode = sanitize_text_field(wp_unslash($_GET['mode']));
163
164 // Generate a unique state token for CSRF protection on PayPal callback.
165 $stateToken = wp_generate_password(32, false);
166 set_transient('give_paypal_onboarding_state_' . $mode, $stateToken, HOUR_IN_SECONDS);
167
168 $redirectUrl = add_query_arg(
169 [
170 'tab' => 'gateways',
171 'section' => 'paypal',
172 'group' => 'paypal-commerce',
173 'mode' => $mode,
174 'give_paypal_state' => $stateToken,
175 ],
176 admin_url('edit.php?post_type=give_forms&page=give-settings')
177 );
178
179 // Set PayPal client mode.
180 give(PayPalClient::class)->setMode($mode);
181
182 $data = $this->payPalAuth->getSellerPartnerLink($redirectUrl, $accountType);
183
184 if (! $data) {
185 wp_send_json_error();
186 }
187
188 $this->settings->updateAccountCountry($country);
189 $this->settings->updatePartnerLinkDetails($data);
190
191 wp_send_json_success($data);
192 }
193
194 /**
195 * give_paypal_commerce_disconnect_account ajax request handler.
196 *
197 * @since 3.16.0 added security nonce check
198 * @since 3.13.0 Add new $keepWebhooks option
199 * @since 2.30.0 Add support for mode param.
200 * @since 2.25.0 Remove merchant seller token.
201 * @since 2.9.0
202 */
203 public function removePayPalAccount()
204 {
205 check_ajax_referer( 'give_paypal_commerce_disconnect_account');
206
207 if (! current_user_can('manage_give_settings')) {
208 wp_send_json_error(['error' => esc_html__('You are not allowed to perform this action.', 'give')]);
209 }
210
211 try {
212 $mode = give_clean($_POST['mode']);
213 $keepWebhooks = rest_sanitize_boolean($_POST['keep-webhooks']);
214 $this->webhooksRepository->setMode($mode);
215 $this->merchantRepository->setMode($mode);
216 $this->refreshToken->setMode($mode);
217 $this->settings->setMode($mode);
218
219 $this->validateAdminRequest();
220
221 // Remove the webhook from PayPal if there is one
222 if ( ! $keepWebhooks && $webhookConfig = $this->webhooksRepository->getWebhookConfig()) {
223 $this->webhooksRepository->deleteWebhook($this->merchantDetails->accessToken, $webhookConfig->id);
224 $this->webhooksRepository->deleteWebhookConfig();
225 }
226
227 $this->merchantRepository->delete();
228 $this->merchantRepository->deleteAccountErrors();
229 $this->merchantRepository->deleteClientToken();
230 $this->settings->deleteSellerAccessToken();
231 $this->refreshToken->deleteRefreshTokenCronJob();
232
233 wp_send_json_success();
234 } catch (\Exception $exception) {
235 wp_send_json_error(['error' => $exception->getMessage()]);
236 }
237 }
238
239 /**
240 * Create order.
241 *
242 * @todo: handle payment create error on frontend.
243 *
244 * @since 3.1.0 Remove unused variable from createOrder argument.
245 * @since 2.9.0
246 */
247 public function createOrder()
248 {
249 $this->validateFrontendRequest();
250 $data = $this->getOrderData();
251
252 try {
253 $result = give(PayPalOrder::class)->createOrder($data);
254
255 wp_send_json_success(
256 [
257 'id' => $result,
258 ]
259 );
260 } catch (\Exception $ex) {
261 wp_send_json_error(
262 [
263 'error' => json_decode($ex->getMessage(), true),
264 ]
265 );
266 }
267 }
268
269 /**
270 * @since 4.16.7.1 Validate the request through the form layer before building order data. v3 forms must
271 * also send a total at least as large as the amount the form validated; v2 forms are
272 * checked on the final, post-filter amount.
273 * @since 4.14.4 Validate donation amount before creating or updating an order.
274 * @since 4.2.1 Only filter amount for v2 forms.
275 * @since 3.4.2
276 */
277 private function getOrderData(): array
278 {
279 $postData = give_clean($_POST);
280 $formId = absint($postData['give-form-id']);
281 $donorAddress = $this->getDonorAddressFromPostedDataForPaypalOrder($postData);
282 $isV3Form = FormUtils::isV3Form($formId);
283
284 if (!$isV3Form) {
285 $this->skipLegacyCardFieldRequirements();
286 }
287
288 $this->validateDonationFormRequest($formId, $postData);
289
290 if ($isV3Form) {
291 /*
292 * v3 forms send the form's own amount field as "amount" and the total, with fee recovery
293 * already included, as "give-amount". The total is what the donor approves in the PayPal
294 * popup; PayPalCommerce::createPayment() reconciles the order to the validated donation
295 * before capturing, so all this has to guarantee is that the total never drops below the
296 * amount the form just validated.
297 */
298 $validatedAmount = isset($postData['amount']) ? (float)$postData['amount'] : 0.0;
299 $amount = isset($postData['give-amount']) ? give_clean($postData['give-amount']) : '0.00';
300
301 if ($validatedAmount <= 0 || (float)$amount < $validatedAmount) {
302 wp_send_json_error(['error' => __('Invalid donation amount.', 'give')]);
303 }
304 } else {
305 $amount = isset($postData['give-amount']) ?
306 (float)apply_filters(
307 'give_donation_total',
308 give_maybe_sanitize_amount(
309 $postData['give-amount'],
310 ['currency' => give_get_currency($formId)]
311 )
312 ) :
313 '0.00';
314
315 $this->validateDonationAmount($amount, $formId);
316 }
317
318 return [
319 'formId' => $formId,
320 'formTitle' => give_payment_gateway_item_title(['post_data' => $postData], 127),
321 'donationAmount' => $amount,
322 'payer' => [
323 'firstName' => $postData['give_first'],
324 'lastName' => $postData['give_last'],
325 'email' => $postData['give_email'],
326 'address' => $donorAddress,
327 ],
328 ];
329 }
330
331 /**
332 * Approve order.
333 *
334 * @todo: handle payment capture error on frontend.
335 *
336 * @since 4.16.7.1 Refuse v3 forms; their capture happens in PayPalCommerce::createPayment(). Validate
337 * the posted form before every capture, not only when the amount changed.
338 * @since 4.14.4 Validate donation amount before approving an order.
339 * @since 3.2.0 Discover error by checking capture status.
340 * @since 2.9.0
341 */
342 public function approveOrder()
343 {
344 $this->validateFrontendRequest();
345 $this->rejectV3FormRequest();
346
347 $orderId = give_clean($_GET['order']);
348 $updateAmount = filter_var(give_clean($_GET['update_amount']), FILTER_VALIDATE_BOOLEAN);
349
350 try {
351 $orderData = $this->getOrderData();
352
353 if ($updateAmount) {
354 $this->validateOrderAmountNotDecreased($orderId, $orderData['donationAmount']);
355 give(PayPalOrder::class)->updateOrderAmount($orderId, $orderData);
356 }
357
358 $result = give(PayPalOrder::class)->approveOrder($orderId);
359 // PayPal does not return error in case of invalid cvv. So we need to check capture status and return error.
360 // ref - https://feedback.givewp.com/bug-reports/p/paypal-credit-card-donations-can-generate-a-fatal-error
361 $this->returnErrorOnFailedApproveOrderResponse($result);
362 wp_send_json_success(['order' => $result,]);
363 } catch (\Exception $ex) {
364 wp_send_json_error(['error' => json_decode($ex->getMessage(), true),]);
365 }
366 }
367
368 /**
369 * @since 4.16.7.1 Refuse v3 forms; PayPalCommerce::createPayment() reconciles their order amount.
370 * @since 4.14.4 Validate donation amount before updating an order amount.
371 * @since 3.4.2
372 */
373 public function updateOrderAmount()
374 {
375 $this->validateFrontendRequest();
376 $this->rejectV3FormRequest();
377
378 $orderId = give_clean($_GET['order']);
379
380 try {
381 $orderData = $this->getOrderData();
382 $this->validateOrderAmountNotDecreased($orderId, $orderData['donationAmount']);
383 give(PayPalOrder::class)->updateOrderAmount($orderId, $orderData);
384
385 wp_send_json_success(['order' => $orderId,]);
386 } catch (\Exception $ex) {
387 wp_send_json_error(['error' => json_decode($ex->getMessage(), true),]);
388 }
389 }
390
391 /**
392 * Return on boarding trouble notice.
393 *
394 * @since 2.9.6
395 */
396 public function onBoardingTroubleNotice()
397 {
398 if (! current_user_can('manage_give_settings')) {
399 wp_die();
400 }
401
402 /* @var AdminSettingFields $adminSettingFields */
403 $adminSettingFields = give(AdminSettingFields::class);
404
405 $actionList = sprintf(
406 '<ol><li>%1$s</li><li>%2$s</li><li>%3$s</li></ol>',
407 esc_html__(
408 'Make sure to complete the entire PayPal process. Do not close the window until you have finished the process.',
409 'give'
410 ),
411 esc_html__(
412 '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.',
413 'give'
414 ),
415 esc_html__(
416 'If you’re still having problems connecting: ',
417 'give'
418 ) . $adminSettingFields->getAdminGuidanceNotice(false)
419 );
420
421 $standardError = sprintf(
422 '<div id="give-paypal-onboarding-trouble-notice" class="give-hidden"><p class="error-message">%1$s</p><p>%2$s</p></div>',
423 esc_html__('Having trouble connecting to PayPal?', 'give'),
424 $actionList
425 );
426
427 wp_send_json_success($standardError);
428 }
429
430 /**
431 * Validate admin ajax request.
432 *
433 * @since 2.9.0
434 */
435 private function validateAdminRequest()
436 {
437 if (! current_user_can('manage_give_settings')) {
438 wp_die();
439 }
440 }
441
442 /**
443 * Validate frontend ajax request.
444 *
445 * @since 2.9.0
446 */
447 private function validateFrontendRequest()
448 {
449 $formId = absint($_POST['give-form-id']);
450
451 if (! $formId || ! give_verify_donation_form_nonce(give_clean($_POST['give-form-hash']), $formId)) {
452 wp_die();
453 }
454 }
455
456 /**
457 * Hold the request to the form's own rules before anything reaches PayPal. The form layer owns
458 * the rules: amount limits, required fields, and whatever else it validates for this form
459 * version; this handler only acts on the verdict.
460 *
461 * @since 4.16.7.1
462 */
463 private function validateDonationFormRequest(int $formId, array $request): void
464 {
465 try {
466 give(ValidateDonationFormRequest::class)($formId, $request);
467 } catch (DonationFormFieldErrorsException $exception) {
468 wp_send_json_error(['error' => implode(' ', $exception->getError()->get_error_messages())]);
469 } catch (DonationFormForbidden $exception) {
470 wp_send_json_error(['error' => $exception->getMessage()], 403);
471 } catch (\Exception $exception) {
472 /*
473 * Anything else the form layer throws (a spam detection, for one) still means "do not
474 * create this order". Same handling as the validate route, log entry included.
475 */
476 Log::error('PayPal Commerce order request rejected', [
477 'formId' => $formId,
478 'exception' => get_class($exception),
479 'message' => $exception->getMessage(),
480 ]);
481
482 wp_send_json_error(['error' => $exception->getMessage()]);
483 }
484 }
485
486 /**
487 * The v2 form posts its card inputs along with everything else, but for this gateway those inputs
488 * are PayPal-hosted fields (SmartButtons.js strips them before its own validation call for the
489 * same reason), so the legacy validator must not require them here.
490 *
491 * @since 4.16.7.1
492 */
493 private function skipLegacyCardFieldRequirements(): void
494 {
495 add_filter('give_donation_form_required_fields', static function ($requiredFields) {
496 return array_diff_key(
497 (array)$requiredFields,
498 array_flip(['card_name', 'card_number', 'card_cvc', 'card_expiry'])
499 );
500 });
501 }
502
503 /**
504 * The legacy validator checks the raw posted amount. The amount that actually reaches PayPal for
505 * a v2 form has been through the give_donation_total filter (fee recovery), so it is checked
506 * again here: positive and within the form's maximum. v3 amounts are validated by the form layer.
507 *
508 * @since 4.16.7.1 Applies to v2 forms only.
509 * @since 4.14.4
510 *
511 * @param float|string $amount
512 */
513 private function validateDonationAmount($amount, int $formId): void
514 {
515 $amount = (float)$amount;
516
517 if ($amount <= 0) {
518 wp_send_json_error(['error' => __('Invalid donation amount.', 'give')]);
519 }
520
521 $maxAmount = (float)give_get_form_maximum_price($formId);
522 if ($maxAmount > 0 && $amount > $maxAmount) {
523 wp_send_json_error([
524 'error' => sprintf(
525 /* translators: %s: maximum donation amount */
526 __('Donation amount must not exceed %s.', 'give'),
527 give_currency_filter(give_format_amount($maxAmount, ['sanitize' => false]))
528 ),
529 ]);
530 }
531 }
532
533 /**
534 * Visual Form Builder (v3) forms never call the approve and update-amount endpoints: their order
535 * is reconciled and captured in PayPalCommerce::createPayment(), after the donation exists.
536 * Refusing them here keeps these endpoints from capturing outside donation processing.
537 *
538 * @since 4.16.7.1
539 */
540 private function rejectV3FormRequest(): void
541 {
542 if (FormUtils::isV3Form(absint($_POST['give-form-id']))) {
543 wp_send_json_error(
544 ['error' => __('This request is not supported for this donation form.', 'give')],
545 403
546 );
547 }
548 }
549
550 /**
551 * Validate that the new donation amount is not less than the original PayPal order amount.
552 *
553 * @since 4.14.4
554 *
555 * @param string $orderId
556 * @param float|string $newAmount
557 */
558 private function validateOrderAmountNotDecreased(string $orderId, $newAmount): void
559 {
560 $newAmount = (float)$newAmount;
561
562 $currentOrder = give(PayPalOrder::class)->getApprovedOrder($orderId);
563 $currentAmount = (float)$currentOrder->purchase_units[0]->amount->value;
564
565 if ($newAmount < $currentAmount) {
566 wp_send_json_error([
567 'error' => __('Donation amount cannot be decreased.', 'give'),
568 ]);
569 }
570 }
571
572 /**
573 * This function should return address array in PayPal rest api accepted format.
574 *
575 * @since 3.1.0 Return address only if setting enabled and has valida country in PayPal accepted formatted.
576 * @since 2.11.1
577 */
578 private function getDonorAddressFromPostedDataForPaypalOrder(array $postedData): array
579 {
580 if (empty($postedData['billing_country'])) {
581 return [];
582 }
583
584 $address['address_line_1'] = ! empty($postedData['card_address']) ? $postedData['card_address'] : '';
585 $address['address_line_2'] = ! empty($postedData['card_address_2']) ? $postedData['card_address_2'] : '';
586 $address['admin_area_2'] = ! empty($postedData['card_city']) ? $postedData['card_city'] : '';
587 $address['admin_area_1'] = ! empty($postedData['card_state']) ? $postedData['card_state'] : '';
588 $address['postal_code'] = ! empty($postedData['card_zip']) ? $postedData['card_zip'] : '';
589 $address['country_code'] = ! empty($postedData['billing_country']) ? $postedData['billing_country'] : '';
590
591 return $address;
592 }
593
594 /**
595 * This function should validate PayPal ApproveOrder response and respond to ajax request on error.
596 *
597 * @since 3.2.0
598 */
599 private function returnErrorOnFailedApproveOrderResponse(\stdClass $response)
600 {
601 // Get capture.
602 // ref - https://developer.paypal.com/docs/api/orders/v2/#orders_capture
603 $capture = $response->purchase_units[0]->payments->captures[0];
604
605 // Check if capture status is failed or declined.
606 if (
607 in_array($capture->status, ['FAILED', 'DECLINED'])
608 && property_exists($capture, 'processor_response')
609 ) {
610 $error = ProcessorResponseError::getError($capture->processor_response);
611
612 if ($error) {
613 wp_send_json_error(['error' => $error]);
614 }
615 }
616 }
617 }
618