PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 3.19.4
GiveWP – Donation Plugin and Fundraising Platform v3.19.4
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 4 years ago Exceptions 3 years ago Migrations 2 years ago Models 2 years ago PayPalCheckoutSdk 2 years ago Repositories 2 years ago Webhooks 2 years ago AccountAdminNotices.php 4 years ago AdminSettingFields.php 1 year ago AdvancedCardFields.php 4 years ago AjaxRequestHandler.php 1 year ago DonationDetailsPage.php 4 years ago DonationFormPaymentMethod.php 2 years ago PayPalClient.php 2 years ago PayPalCommerce.php 2 years ago RefreshToken.php 2 years ago RefundPaymentHandler.php 4 years ago ScriptLoader.php 2 years ago Utils.php 2 years ago onBoardingRedirectHandler.php 1 year ago
onBoardingRedirectHandler.php
522 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 2.9.0
219 */
220 private function redirectAccountConnected()
221 {
222 $this->refreshAccountStatus();
223
224 wp_redirect(
225 add_query_arg(
226 [
227 'post_type' => 'give_forms',
228 'page' => 'give-settings',
229 'tab' => 'gateways',
230 'section' => 'paypal',
231 'group' => 'paypal-commerce',
232 'paypal-commerce-account-connected' => '1'
233 ],
234 admin_url('edit.php')
235 )
236 );
237
238 exit();
239 }
240
241 /**
242 * Sets up the webhook for the connected account
243 *
244 * @since 2.32.0 Remove second argument from createWebhook function.
245 * @since 2.9.0
246 *
247 * @param MerchantDetail $merchant_details
248 */
249 private function setUpWebhook(MerchantDetail $merchant_details)
250 {
251 if (! is_ssl()) {
252 return;
253 }
254
255 try {
256 $webhookConfig = $this->webhooksRepository->createWebhook();
257 $this->webhooksRepository->saveWebhookConfig($webhookConfig);
258 } catch (Exception $ex) {
259 $errors[] = esc_html__(
260 'There was a problem with creating webhook on PayPal. A gateway error log also added to get details information about PayPal response.',
261 'give'
262 );
263
264 $this->merchantRepository->saveAccountErrors($errors);
265 $this->redirectWhenOnBoardingFail();
266 }
267 }
268
269 /**
270 * Delete temp data
271 *
272 * @since 2.9.0
273 * @return void
274 */
275 private function deleteTempOptions()
276 {
277 $this->settings->deletePartnerLinkDetails();
278 $this->settings->deleteAccessToken();
279 }
280
281 /**
282 * Register notice if account connect success fully.
283 *
284 * @since 2.9.0
285 */
286 private function registerPayPalAccountConnectedNotice()
287 {
288 Give_Admin_Settings::add_message(
289 'paypal-commerce-account-connected',
290 esc_html__('PayPal account connected successfully.', 'give')
291 );
292 }
293
294 /**
295 * Returns whether or not the current request is for refreshing the account status
296 *
297 * @since 2.9.0
298 *
299 * @return bool
300 */
301 private function isStatusRefresh()
302 {
303 return isset($_GET['paypalStatusCheck']) && Give_Admin_Settings::is_setting_page('gateways', 'paypal');
304 }
305
306 /**
307 * Return whether or not PayPal user redirect to GiveWP setting page after successful onboarding.
308 *
309 * @since 2.9.0
310 *
311 * @return bool
312 */
313 private function isPayPalUserRedirected()
314 {
315 return isset($_GET['merchantIdInPayPal']) && Give_Admin_Settings::is_setting_page('gateways', 'paypal');
316 }
317
318 /**
319 * Return whether or not PayPal account details saved.
320 *
321 * @since 2.9.0
322 *
323 * @return bool
324 */
325 private function isPayPalAccountDetailsSaved()
326 {
327 return isset($_GET['paypal-commerce-account-connected']) && Give_Admin_Settings::is_setting_page(
328 'gateways',
329 'paypal'
330 );
331 }
332
333 /**
334 * validate rest api credential.
335 *
336 * @since 2.9.0
337 *
338 * @param array $array
339 *
340 */
341 private function didWeGetValidSellerRestApiCredentials($array)
342 {
343 $required = ['client_id', 'client_secret'];
344 $array = array_filter($array); // Remove empty values.
345
346 if (array_diff($required, array_keys($array))) {
347 $errors[] = [
348 'type' => 'json',
349 'message' => esc_html__('PayPal client access token API request response is:', 'give'),
350 'value' => wp_json_encode($this->settings->getAccessToken()),
351 ];
352
353 $errors[] = [
354 'type' => 'json',
355 'message' => esc_html__('PayPal client rest api credentials API request response is:', 'give'),
356 'value' => wp_json_encode($array),
357 ];
358
359 $errors[] = esc_html__(
360 'There was a problem with PayPal client rest API request and we could not find valid client id and secret.',
361 'give'
362 );
363
364 $this->merchantRepository->saveAccountErrors($errors);
365 $this->redirectWhenOnBoardingFail();
366 }
367 }
368
369 /**
370 * Handles the request for refreshing the account status
371 *
372 * @since 3.0.0 Make function publicly accessible.
373 * @since 2.9.0
374 */
375 public function refreshAccountStatus()
376 {
377 $merchantDetails = $this->merchantRepository->getDetails();
378
379 $statusErrors = $this->isAdminSuccessfullyOnBoarded(
380 $merchantDetails->merchantIdInPayPal,
381 $merchantDetails->accessToken,
382 $merchantDetails->supportsCustomPayments
383 );
384 if ($statusErrors !== true) {
385 $merchantDetails->accountIsReady = false;
386 $this->merchantRepository->saveAccountErrors($statusErrors);
387 } else {
388 $merchantDetails->accountIsReady = true;
389 $this->merchantRepository->deleteAccountErrors();
390 }
391
392 $this->merchantRepository->save($merchantDetails);
393 }
394
395 /**
396 * Validate seller on Boarding status
397 *
398 * @since 2.29.0 Validate only primary capabilities during PayPal donations on-boarding.
399 * @since 2.9.0
400 *
401 * @param string $merchantId
402 * @param string $accessToken
403 * @param bool $usesCustomPayments
404 *
405 * @return true|string[]
406 */
407 private function isAdminSuccessfullyOnBoarded($merchantId, $accessToken, $usesCustomPayments)
408 {
409 $onBoardedData = (array)$this->payPalAuth->getSellerOnBoardingDetailsFromPayPal($merchantId, $accessToken);
410 $onBoardedData = array_filter($onBoardedData); // Remove empty values.
411 $errorMessages[] = [
412 'type' => 'json',
413 'message' => esc_html__('PayPal merchant status check API request response is:', 'give'),
414 'value' => wp_json_encode($onBoardedData),
415 ];
416
417 if (! is_ssl()) {
418 $errorMessages[] = esc_html__(
419 'A valid SSL certificate is required to accept donations and set up your PayPal account. Once a
420 certificate is installed and the site is using https, please disconnect and reconnect your account.',
421 'give'
422 );
423 }
424
425 if (array_diff(['payments_receivable', 'primary_email_confirmed'], array_keys($onBoardedData))) {
426 $errorMessages[] = __(
427 '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.',
428 'give'
429 );
430
431 // Return here since the rest of the validations will definitely fail
432 return $errorMessages;
433 }
434
435 if (! $onBoardedData['payments_receivable']) {
436 $errorMessages[] = esc_html__('An banking account needs to be connected to your PayPal account and verified', 'give');
437 }
438
439 if (! $onBoardedData['primary_email_confirmed']) {
440 $errorMessages[] = esc_html__('Your primary email address needs to be confirmed', 'give');
441 }
442
443 // This error message is only for the case when the user is using custom payments.
444 // Host card fields are supported only for specific countries and PayPal seller account of PPCP type.
445 if ($usesCustomPayments) {
446 $sellerCapabilities = array_key_exists('capabilities', $onBoardedData)
447 ? wp_list_pluck($onBoardedData['capabilities'], 'name')
448 : [];
449 $requiredCapability = 'CUSTOM_CARD_PROCESSING';
450 $customCardProcessingCapabilityIndex = array_search($requiredCapability, $sellerCapabilities, true);
451 $hasCustomCardProcessingCapability = false !== $customCardProcessingCapabilityIndex;
452
453 // If the capability is found then check if it is active.
454 if (false !== $customCardProcessingCapabilityIndex) {
455 $customCardProcessingCapability = $onBoardedData['capabilities'][$customCardProcessingCapabilityIndex];
456 $hasCustomCardProcessingCapability = 'ACTIVE' === $customCardProcessingCapability['status'];
457 }
458
459 if (! $hasCustomCardProcessingCapability) {
460 $errorMessages[] = __(
461 '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.',
462 'give'
463 );
464 }
465 }
466
467 // If there were errors then redirect the user with notices
468 return count($errorMessages) > 1 ? $errorMessages : true;
469 }
470
471 /**
472 * Redirect admin to setting section with error.
473 *
474 * @since 2.9.0
475 */
476 private function redirectWhenOnBoardingFail()
477 {
478 wp_redirect(
479 add_query_arg(
480 [
481 'post_type' => 'give_forms',
482 'page' => 'give-settings',
483 'tab' => 'gateways',
484 'section' => 'paypal',
485 'group' => 'paypal-commerce',
486 'paypal-error' => '1',
487 ],
488 admin_url('edit.php')
489 )
490 );
491
492 exit();
493 }
494
495 /**
496 * Displays a notice of the site is not using SSL
497 *
498 * @since 2.9.0
499 */
500 private function registerPayPalSSLNotice()
501 {
502 if (is_ssl() && empty($this->webhooksRepository->getWebhookConfig())) {
503 $logLink = sprintf(
504 '<a href="%1$s">%2$s</a>',
505 admin_url('/edit.php?post_type=give_forms&page=give-tools&tab=logs'),
506 esc_html__('logs data', 'give')
507 );
508
509 Give_Admin_Settings::add_error(
510 'paypal-webhook-error',
511 sprintf(
512 esc_html__(
513 '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',
514 'give'
515 ),
516 $logLink
517 )
518 );
519 }
520 }
521 }
522