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 / onBoardingRedirectHandler.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
onBoardingRedirectHandler.php
557 lines
1 <?php
2
3 namespace Give\PaymentGateways\PayPalCommerce;
4
5 use Exception;
6 use Give\Framework\Exceptions\Primitives\Exception as GiveException;
7 use Give\Log\Log;
8 use Give\PaymentGateways\PayPalCommerce\Models\MerchantDetail;
9 use Give\PaymentGateways\PayPalCommerce\Repositories\MerchantDetails;
10 use Give\PaymentGateways\PayPalCommerce\Repositories\PayPalAuth;
11 use Give\PaymentGateways\PayPalCommerce\Repositories\Settings;
12 use Give\PaymentGateways\PayPalCommerce\Repositories\Webhooks;
13 use Give_Admin_Settings;
14
15 /**
16 * Class PayPalOnBoardingRedirectHandler
17 * @package Give\PaymentGateways\PayPalCommerce
18 *
19 * @since 2.9.0
20 */
21 class onBoardingRedirectHandler
22 {
23 /**
24 * @since 2.9.0
25 *
26 * @var PayPalAuth
27 */
28 private $payPalAuth;
29
30 /**
31 * @since 2.9.0
32 *
33 * @var Webhooks
34 */
35 private $webhooksRepository;
36
37 /**
38 * @since 2.9.0
39 *
40 * @var MerchantDetails
41 */
42 private $merchantRepository;
43
44 /**
45 * @since 2.9.0
46 *
47 * @var Settings
48 */
49 private $settings;
50
51 /**
52 * onBoardingRedirectHandler constructor.
53 *
54 * @since 2.9.0
55 *
56 * @param Webhooks $webhooks
57 * @param MerchantDetails $merchantRepository
58 * @param Settings $settings
59 * @param PayPalAuth $payPalAuth
60 */
61 public function __construct(
62 Webhooks $webhooks,
63 MerchantDetails $merchantRepository,
64 Settings $settings,
65 PayPalAuth $payPalAuth
66 ) {
67 $this->webhooksRepository = $webhooks;
68 $this->merchantRepository = $merchantRepository;
69 $this->settings = $settings;
70 $this->payPalAuth = $payPalAuth;
71
72 $this->setModeFromRequest();
73 }
74
75 /**
76 * This function set mode from request.
77 *
78 * @since 2.30.0
79 * @return void
80 */
81 private function setModeFromRequest()
82 {
83 if (isset($_GET['mode']) && in_array($_GET['mode'], ['live', 'sandbox'], true)) {
84 $mode = $_GET['mode'];
85
86 $this->setModeForServices($mode);
87 }
88 }
89
90 /**
91 * Set mode for services.
92 *
93 * Use this function to manually set connection mode. Values can be 'live' or 'sandbox'.
94 * Services are classes which depends upon connection mode and used to interact with PayPal API or core logic.
95 *
96 * @since 3.0.0
97 *
98 * @return void
99 */
100 public function setModeForServices(string $mode)
101 {
102 $this->webhooksRepository->setMode($mode);
103 $this->merchantRepository->setMode($mode);
104 give(PayPalClient::class)->setMode($mode);
105 }
106
107 /**
108 * Bootstrap class
109 *
110 * @since 2.9.0
111 */
112 public function boot()
113 {
114 if ($this->isPayPalUserRedirected()) {
115 $details = $this->savePayPalMerchantDetails();
116 $this->setUpWebhook($details);
117 $this->redirectAccountConnected();
118 }
119
120 if ($this->isPayPalAccountDetailsSaved()) {
121 $this->registerPayPalSSLNotice();
122 $this->registerPayPalAccountConnectedNotice();
123 }
124
125 if ($this->isStatusRefresh()) {
126 $this->refreshAccountStatus();
127 }
128 }
129
130 /**
131 * Save PayPal merchant details
132 *
133 * @since 3.19.1 fix parsing email with special characters
134 * @since 2.32.0 Remove second argument from updateSellerAccessToken function.
135 * @since 2.25.0 Handle exception.
136 * @since 2.9.0
137 *
138 * @return MerchantDetail
139 */
140 private function savePayPalMerchantDetails()
141 {
142 $paypalGetData = wp_parse_args(str_replace("+", rawurlencode("+"), $_SERVER['QUERY_STRING']));
143 $partnerLinkInfo = $this->settings->getPartnerLinkDetails();
144 $tokenInfo = $this->settings->getAccessToken();
145
146 $allowedPayPalData = [
147 'merchantId',
148 'merchantIdInPayPal',
149 ];
150
151 $payPalAccount = array_intersect_key($paypalGetData, array_flip($allowedPayPalData));
152
153 if (! array_key_exists('merchantIdInPayPal', $payPalAccount) || empty($payPalAccount['merchantIdInPayPal'])) {
154 $errors[] = [
155 'type' => 'url',
156 'message' => esc_html__(
157 'The Merchant ID for PayPal was not found. Try connecting to PayPal again. The PayPal return URL is:',
158 'give'
159 ) . "\n",
160 'value' => urlencode($_SERVER['QUERY_STRING']),
161 ];
162
163 $this->merchantRepository->saveAccountErrors($errors);
164 $this->redirectWhenOnBoardingFail();
165 }
166
167 $restApiCredentials = (array)$this->payPalAuth->getSellerRestAPICredentials(
168 $tokenInfo ? $tokenInfo['accessToken'] : ''
169 );
170 $this->didWeGetValidSellerRestApiCredentials($restApiCredentials);
171
172 try {
173 $tokenInfo = $this->payPalAuth->getTokenFromClientCredentials(
174 $restApiCredentials['client_id'],
175 $restApiCredentials['client_secret']
176 );
177 } catch (GiveException $e) {
178 give(Log::class)->warning(
179 'PayPal Commerce: Error retrieving access token on boarding redirect.',
180 [
181 'category' => 'Payment Gateway',
182 'source' => 'Paypal Commerce',
183 'exception' => $e,
184 ]
185 );
186
187 $errors[] = esc_html__(
188 'There was a problem with retrieving PayPal access token. Please try again or contact support.',
189 'give'
190 );
191
192 $this->merchantRepository->saveAccountErrors($errors);
193 $this->redirectWhenOnBoardingFail();
194 }
195
196 $payPalAccount['clientId'] = $restApiCredentials['client_id'];
197 $payPalAccount['clientSecret'] = $restApiCredentials['client_secret'];
198 $payPalAccount['token'] = $tokenInfo;
199 $payPalAccount['supportsCustomPayments'] = 'PPCP' === $partnerLinkInfo['product'];
200 $payPalAccount['accountIsReady'] = true;
201 $payPalAccount['accountCountry'] = $this->settings->getAccountCountry();
202
203 $merchantDetails = MerchantDetail::fromArray($payPalAccount);
204 $this->merchantRepository->save($merchantDetails);
205
206 // Preserve the seller access token.
207 // This is required to get the merchant rest api credentials.
208 $this->settings->updateSellerAccessToken($this->settings->getAccessToken());
209
210 $this->deleteTempOptions();
211
212 return $merchantDetails;
213 }
214
215 /**
216 * Redirects the user to the account connected url
217 *
218 * @since 4.13.2 Add nonce to redirect URL for CSRF protection.
219 * @since 2.9.0
220 */
221 private function redirectAccountConnected()
222 {
223 $this->refreshAccountStatus();
224
225 wp_redirect(
226 wp_nonce_url(
227 add_query_arg(
228 [
229 'post_type' => 'give_forms',
230 'page' => 'give-settings',
231 'tab' => 'gateways',
232 'section' => 'paypal',
233 'group' => 'paypal-commerce',
234 'paypal-commerce-account-connected' => '1'
235 ],
236 admin_url('edit.php')
237 ),
238 'give_paypal_account_connected'
239 )
240 );
241
242 exit();
243 }
244
245 /**
246 * Sets up the webhook for the connected account
247 *
248 * @since 2.32.0 Remove second argument from createWebhook function.
249 * @since 2.9.0
250 *
251 * @param MerchantDetail $merchant_details
252 */
253 private function setUpWebhook(MerchantDetail $merchant_details)
254 {
255 if (! is_ssl()) {
256 return;
257 }
258
259 try {
260 $webhookConfig = $this->webhooksRepository->createWebhook();
261 $this->webhooksRepository->saveWebhookConfig($webhookConfig);
262 } catch (Exception $ex) {
263 $errors[] = esc_html__(
264 'There was a problem with creating webhook on PayPal. A gateway error log also added to get details information about PayPal response.',
265 'give'
266 );
267
268 $this->merchantRepository->saveAccountErrors($errors);
269 $this->redirectWhenOnBoardingFail();
270 }
271 }
272
273 /**
274 * Delete temp data
275 *
276 * @since 2.9.0
277 * @return void
278 */
279 private function deleteTempOptions()
280 {
281 $this->settings->deletePartnerLinkDetails();
282 $this->settings->deleteAccessToken();
283 }
284
285 /**
286 * Register notice if account connect success fully.
287 *
288 * @since 2.9.0
289 */
290 private function registerPayPalAccountConnectedNotice()
291 {
292 Give_Admin_Settings::add_message(
293 'paypal-commerce-account-connected',
294 esc_html__('PayPal account connected successfully.', 'give')
295 );
296 }
297
298 /**
299 * Returns whether or not the current request is for refreshing the account status
300 *
301 * @since 4.13.2 Add nonce verification for CSRF protection.
302 * @since 2.9.0
303 *
304 * @return bool
305 */
306 private function isStatusRefresh()
307 {
308 if (!isset($_GET['paypalStatusCheck']) || !Give_Admin_Settings::is_setting_page('gateways', 'paypal')) {
309 return false;
310 }
311
312 if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'give_paypal_status_refresh')) {
313 return false;
314 }
315
316 return true;
317 }
318
319 /**
320 * Return whether or not PayPal user redirect to GiveWP setting page after successful onboarding.
321 *
322 * @since 4.13.2 Add state parameter verification for CSRF protection.
323 * @since 2.9.0
324 *
325 * @return bool
326 */
327 private function isPayPalUserRedirected()
328 {
329 if (!isset($_GET['merchantIdInPayPal']) || !Give_Admin_Settings::is_setting_page('gateways', 'paypal')) {
330 return false;
331 }
332
333 // Verify the state parameter to protect against CSRF attacks.
334 $mode = isset($_GET['mode']) && in_array($_GET['mode'], ['live', 'sandbox'], true) ? $_GET['mode'] : 'live';
335 $storedState = get_transient('give_paypal_onboarding_state_' . $mode);
336
337 if (!isset($_GET['give_paypal_state']) || !$storedState || !hash_equals($storedState, $_GET['give_paypal_state'])) {
338 return false;
339 }
340
341 // Delete the transient after successful verification to prevent replay attacks.
342 delete_transient('give_paypal_onboarding_state_' . $mode);
343
344 return true;
345 }
346
347 /**
348 * Return whether or not PayPal account details saved.
349 *
350 * @since 4.13.2 Add nonce verification for CSRF protection.
351 * @since 2.9.0
352 *
353 * @return bool
354 */
355 private function isPayPalAccountDetailsSaved()
356 {
357 if (!isset($_GET['paypal-commerce-account-connected']) || !Give_Admin_Settings::is_setting_page('gateways', 'paypal')) {
358 return false;
359 }
360
361 if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'give_paypal_account_connected')) {
362 return false;
363 }
364
365 return true;
366 }
367
368 /**
369 * validate rest api credential.
370 *
371 * @since 2.9.0
372 *
373 * @param array $array
374 *
375 */
376 private function didWeGetValidSellerRestApiCredentials($array)
377 {
378 $required = ['client_id', 'client_secret'];
379 $array = array_filter($array); // Remove empty values.
380
381 if (array_diff($required, array_keys($array))) {
382 $errors[] = [
383 'type' => 'json',
384 'message' => esc_html__('PayPal client access token API request response is:', 'give'),
385 'value' => wp_json_encode($this->settings->getAccessToken()),
386 ];
387
388 $errors[] = [
389 'type' => 'json',
390 'message' => esc_html__('PayPal client rest api credentials API request response is:', 'give'),
391 'value' => wp_json_encode($array),
392 ];
393
394 $errors[] = esc_html__(
395 'There was a problem with PayPal client rest API request and we could not find valid client id and secret.',
396 'give'
397 );
398
399 $this->merchantRepository->saveAccountErrors($errors);
400 $this->redirectWhenOnBoardingFail();
401 }
402 }
403
404 /**
405 * Handles the request for refreshing the account status
406 *
407 * @since 3.0.0 Make function publicly accessible.
408 * @since 2.9.0
409 */
410 public function refreshAccountStatus()
411 {
412 $merchantDetails = $this->merchantRepository->getDetails();
413
414 $statusErrors = $this->isAdminSuccessfullyOnBoarded(
415 $merchantDetails->merchantIdInPayPal,
416 $merchantDetails->accessToken,
417 $merchantDetails->supportsCustomPayments
418 );
419 if ($statusErrors !== true) {
420 $merchantDetails->accountIsReady = false;
421 $this->merchantRepository->saveAccountErrors($statusErrors);
422 } else {
423 $merchantDetails->accountIsReady = true;
424 $this->merchantRepository->deleteAccountErrors();
425 }
426
427 $this->merchantRepository->save($merchantDetails);
428 }
429
430 /**
431 * Validate seller on Boarding status
432 *
433 * @since 2.29.0 Validate only primary capabilities during PayPal donations on-boarding.
434 * @since 2.9.0
435 *
436 * @param string $merchantId
437 * @param string $accessToken
438 * @param bool $usesCustomPayments
439 *
440 * @return true|string[]
441 */
442 private function isAdminSuccessfullyOnBoarded($merchantId, $accessToken, $usesCustomPayments)
443 {
444 $onBoardedData = (array)$this->payPalAuth->getSellerOnBoardingDetailsFromPayPal($merchantId, $accessToken);
445 $onBoardedData = array_filter($onBoardedData); // Remove empty values.
446 $errorMessages[] = [
447 'type' => 'json',
448 'message' => esc_html__('PayPal merchant status check API request response is:', 'give'),
449 'value' => wp_json_encode($onBoardedData),
450 ];
451
452 if (! is_ssl()) {
453 $errorMessages[] = esc_html__(
454 'A valid SSL certificate is required to accept donations and set up your PayPal account. Once a
455 certificate is installed and the site is using https, please disconnect and reconnect your account.',
456 'give'
457 );
458 }
459
460 if (array_diff(['payments_receivable', 'primary_email_confirmed'], array_keys($onBoardedData))) {
461 $errorMessages[] = __(
462 'Your account is not fully set up and ready to receive payments. Please log into your PayPal account at <a href="https://paypal.com">paypal.com</a> and address the following issues. Reach out to PayPal support if you need help setting up your account.',
463 'give'
464 );
465
466 // Return here since the rest of the validations will definitely fail
467 return $errorMessages;
468 }
469
470 if (! $onBoardedData['payments_receivable']) {
471 $errorMessages[] = esc_html__('An banking account needs to be connected to your PayPal account and verified', 'give');
472 }
473
474 if (! $onBoardedData['primary_email_confirmed']) {
475 $errorMessages[] = esc_html__('Your primary email address needs to be confirmed', 'give');
476 }
477
478 // This error message is only for the case when the user is using custom payments.
479 // Host card fields are supported only for specific countries and PayPal seller account of PPCP type.
480 if ($usesCustomPayments) {
481 $sellerCapabilities = array_key_exists('capabilities', $onBoardedData)
482 ? wp_list_pluck($onBoardedData['capabilities'], 'name')
483 : [];
484 $requiredCapability = 'CUSTOM_CARD_PROCESSING';
485 $customCardProcessingCapabilityIndex = array_search($requiredCapability, $sellerCapabilities, true);
486 $hasCustomCardProcessingCapability = false !== $customCardProcessingCapabilityIndex;
487
488 // If the capability is found then check if it is active.
489 if (false !== $customCardProcessingCapabilityIndex) {
490 $customCardProcessingCapability = $onBoardedData['capabilities'][$customCardProcessingCapabilityIndex];
491 $hasCustomCardProcessingCapability = 'ACTIVE' === $customCardProcessingCapability['status'];
492 }
493
494 if (! $hasCustomCardProcessingCapability) {
495 $errorMessages[] = __(
496 'Advance card processing is not active on your PayPal account. That capability is required in order to display card fields directly on your website. To accept donations with card fields directly on your site, called <a href="https://developer.paypal.com/docs/checkout/advanced/#enable-your-account" title="Link to PayPal Docs">hosted fields</a>, you\'ll need to enable custom card processing. This is something PayPal support can help with, and depends on factors outside of GiveWP\'s control. You can still accept donations with <a href="https://developer.paypal.com/docs/checkout/" title="Link to PayPal Docs">PayPal smart buttons</a>, which allow donors to log into PayPal and complete the donation in a modal window, in the meantime.',
497 'give'
498 );
499 }
500 }
501
502 // If there were errors then redirect the user with notices
503 return count($errorMessages) > 1 ? $errorMessages : true;
504 }
505
506 /**
507 * Redirect admin to setting section with error.
508 *
509 * @since 2.9.0
510 */
511 private function redirectWhenOnBoardingFail()
512 {
513 wp_redirect(
514 add_query_arg(
515 [
516 'post_type' => 'give_forms',
517 'page' => 'give-settings',
518 'tab' => 'gateways',
519 'section' => 'paypal',
520 'group' => 'paypal-commerce',
521 'paypal-error' => '1',
522 ],
523 admin_url('edit.php')
524 )
525 );
526
527 exit();
528 }
529
530 /**
531 * Displays a notice of the site is not using SSL
532 *
533 * @since 2.9.0
534 */
535 private function registerPayPalSSLNotice()
536 {
537 if (is_ssl() && empty($this->webhooksRepository->getWebhookConfig())) {
538 $logLink = sprintf(
539 '<a href="%1$s">%2$s</a>',
540 admin_url('/edit.php?post_type=give_forms&page=give-tools&tab=logs'),
541 esc_html__('logs data', 'give')
542 );
543
544 Give_Admin_Settings::add_error(
545 'paypal-webhook-error',
546 sprintf(
547 esc_html__(
548 'There was a problem setting up the webhooks for your PayPal account. Please try disconnecting and reconnecting your PayPal account. If the problem persists, please contact support and provide them with the latest %1$s',
549 'give'
550 ),
551 $logLink
552 )
553 );
554 }
555 }
556 }
557