PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 2.33.0
GiveWP – Donation Plugin and Fundraising Platform v2.33.0
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 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 2 years ago AdvancedCardFields.php 4 years ago AjaxRequestHandler.php 2 years ago DonationDetailsPage.php 4 years ago DonationFormPaymentMethod.php 4 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 4 years ago onBoardingRedirectHandler.php 2 years ago
onBoardingRedirectHandler.php
504 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 $this->webhooksRepository->setMode($mode);
86 $this->merchantRepository->setMode($mode);
87 give(PayPalClient::class)->setMode($mode);
88 }
89 }
90
91 /**
92 * Bootstrap class
93 *
94 * @since 2.9.0
95 */
96 public function boot()
97 {
98 if ($this->isPayPalUserRedirected()) {
99 $details = $this->savePayPalMerchantDetails();
100 $this->setUpWebhook($details);
101 $this->redirectAccountConnected();
102 }
103
104 if ($this->isPayPalAccountDetailsSaved()) {
105 $this->registerPayPalSSLNotice();
106 $this->registerPayPalAccountConnectedNotice();
107 }
108
109 if ($this->isStatusRefresh()) {
110 $this->refreshAccountStatus();
111 }
112 }
113
114 /**
115 * Save PayPal merchant details
116 *
117 * @since 2.32.0 Remove second argument from updateSellerAccessToken function.
118 * @since 2.25.0 Handle exception.
119 * @since 2.9.0
120 *
121 * @return MerchantDetail
122 */
123 private function savePayPalMerchantDetails()
124 {
125 $paypalGetData = wp_parse_args($_SERVER['QUERY_STRING']);
126 $partnerLinkInfo = $this->settings->getPartnerLinkDetails();
127 $tokenInfo = $this->settings->getAccessToken();
128
129 $allowedPayPalData = [
130 'merchantId',
131 'merchantIdInPayPal',
132 ];
133
134 $payPalAccount = array_intersect_key($paypalGetData, array_flip($allowedPayPalData));
135
136 if (! array_key_exists('merchantIdInPayPal', $payPalAccount) || empty($payPalAccount['merchantIdInPayPal'])) {
137 $errors[] = [
138 'type' => 'url',
139 'message' => esc_html__(
140 'The Merchant ID for PayPal was not found. Try connecting to PayPal again. The PayPal return URL is:',
141 'give'
142 ) . "\n",
143 'value' => urlencode($_SERVER['QUERY_STRING']),
144 ];
145
146 $this->merchantRepository->saveAccountErrors($errors);
147 $this->redirectWhenOnBoardingFail();
148 }
149
150 $restApiCredentials = (array)$this->payPalAuth->getSellerRestAPICredentials(
151 $tokenInfo ? $tokenInfo['accessToken'] : ''
152 );
153 $this->didWeGetValidSellerRestApiCredentials($restApiCredentials);
154
155 try {
156 $tokenInfo = $this->payPalAuth->getTokenFromClientCredentials(
157 $restApiCredentials['client_id'],
158 $restApiCredentials['client_secret']
159 );
160 } catch (GiveException $e) {
161 give(Log::class)->warning(
162 'PayPal Commerce: Error retrieving access token on boarding redirect.',
163 [
164 'category' => 'Payment Gateway',
165 'source' => 'Paypal Commerce',
166 'exception' => $e,
167 ]
168 );
169
170 $errors[] = esc_html__(
171 'There was a problem with retrieving PayPal access token. Please try again or contact support.',
172 'give'
173 );
174
175 $this->merchantRepository->saveAccountErrors($errors);
176 $this->redirectWhenOnBoardingFail();
177 }
178
179 $payPalAccount['clientId'] = $restApiCredentials['client_id'];
180 $payPalAccount['clientSecret'] = $restApiCredentials['client_secret'];
181 $payPalAccount['token'] = $tokenInfo;
182 $payPalAccount['supportsCustomPayments'] = 'PPCP' === $partnerLinkInfo['product'];
183 $payPalAccount['accountIsReady'] = true;
184 $payPalAccount['accountCountry'] = $this->settings->getAccountCountry();
185
186 $merchantDetails = MerchantDetail::fromArray($payPalAccount);
187 $this->merchantRepository->save($merchantDetails);
188
189 // Preserve the seller access token.
190 // This is required to get the merchant rest api credentials.
191 $this->settings->updateSellerAccessToken($this->settings->getAccessToken());
192
193 $this->deleteTempOptions();
194
195 return $merchantDetails;
196 }
197
198 /**
199 * Redirects the user to the account connected url
200 *
201 * @since 2.9.0
202 */
203 private function redirectAccountConnected()
204 {
205 $this->refreshAccountStatus();
206
207 wp_redirect(
208 add_query_arg(
209 [
210 'post_type' => 'give_forms',
211 'page' => 'give-settings',
212 'tab' => 'gateways',
213 'section' => 'paypal',
214 'group' => 'paypal-commerce',
215 'paypal-commerce-account-connected' => '1'
216 ],
217 admin_url('edit.php')
218 )
219 );
220
221 exit();
222 }
223
224 /**
225 * Sets up the webhook for the connected account
226 *
227 * @since 2.32.0 Remove second argument from createWebhook function.
228 * @since 2.9.0
229 *
230 * @param MerchantDetail $merchant_details
231 */
232 private function setUpWebhook(MerchantDetail $merchant_details)
233 {
234 if (! is_ssl()) {
235 return;
236 }
237
238 try {
239 $webhookConfig = $this->webhooksRepository->createWebhook();
240 $this->webhooksRepository->saveWebhookConfig($webhookConfig);
241 } catch (Exception $ex) {
242 $errors[] = esc_html__(
243 'There was a problem with creating webhook on PayPal. A gateway error log also added to get details information about PayPal response.',
244 'give'
245 );
246
247 $this->merchantRepository->saveAccountErrors($errors);
248 $this->redirectWhenOnBoardingFail();
249 }
250 }
251
252 /**
253 * Delete temp data
254 *
255 * @since 2.9.0
256 * @return void
257 */
258 private function deleteTempOptions()
259 {
260 $this->settings->deletePartnerLinkDetails();
261 $this->settings->deleteAccessToken();
262 }
263
264 /**
265 * Register notice if account connect success fully.
266 *
267 * @since 2.9.0
268 */
269 private function registerPayPalAccountConnectedNotice()
270 {
271 Give_Admin_Settings::add_message(
272 'paypal-commerce-account-connected',
273 esc_html__('PayPal account connected successfully.', 'give')
274 );
275 }
276
277 /**
278 * Returns whether or not the current request is for refreshing the account status
279 *
280 * @since 2.9.0
281 *
282 * @return bool
283 */
284 private function isStatusRefresh()
285 {
286 return isset($_GET['paypalStatusCheck']) && Give_Admin_Settings::is_setting_page('gateways', 'paypal');
287 }
288
289 /**
290 * Return whether or not PayPal user redirect to GiveWP setting page after successful onboarding.
291 *
292 * @since 2.9.0
293 *
294 * @return bool
295 */
296 private function isPayPalUserRedirected()
297 {
298 return isset($_GET['merchantIdInPayPal']) && Give_Admin_Settings::is_setting_page('gateways', 'paypal');
299 }
300
301 /**
302 * Return whether or not PayPal account details saved.
303 *
304 * @since 2.9.0
305 *
306 * @return bool
307 */
308 private function isPayPalAccountDetailsSaved()
309 {
310 return isset($_GET['paypal-commerce-account-connected']) && Give_Admin_Settings::is_setting_page(
311 'gateways',
312 'paypal'
313 );
314 }
315
316 /**
317 * validate rest api credential.
318 *
319 * @since 2.9.0
320 *
321 * @param array $array
322 *
323 */
324 private function didWeGetValidSellerRestApiCredentials($array)
325 {
326 $required = ['client_id', 'client_secret'];
327 $array = array_filter($array); // Remove empty values.
328
329 if (array_diff($required, array_keys($array))) {
330 $errors[] = [
331 'type' => 'json',
332 'message' => esc_html__('PayPal client access token API request response is:', 'give'),
333 'value' => wp_json_encode($this->settings->getAccessToken()),
334 ];
335
336 $errors[] = [
337 'type' => 'json',
338 'message' => esc_html__('PayPal client rest api credentials API request response is:', 'give'),
339 'value' => wp_json_encode($array),
340 ];
341
342 $errors[] = esc_html__(
343 'There was a problem with PayPal client rest API request and we could not find valid client id and secret.',
344 'give'
345 );
346
347 $this->merchantRepository->saveAccountErrors($errors);
348 $this->redirectWhenOnBoardingFail();
349 }
350 }
351
352 /**
353 * Handles the request for refreshing the account status
354 *
355 * @since 2.9.0
356 */
357 private function refreshAccountStatus()
358 {
359 $merchantDetails = $this->merchantRepository->getDetails();
360
361 $statusErrors = $this->isAdminSuccessfullyOnBoarded(
362 $merchantDetails->merchantIdInPayPal,
363 $merchantDetails->accessToken,
364 $merchantDetails->supportsCustomPayments
365 );
366 if ($statusErrors !== true) {
367 $merchantDetails->accountIsReady = false;
368 $this->merchantRepository->saveAccountErrors($statusErrors);
369 } else {
370 $merchantDetails->accountIsReady = true;
371 $this->merchantRepository->deleteAccountErrors();
372 }
373
374 $this->merchantRepository->save($merchantDetails);
375 }
376
377 /**
378 * Validate seller on Boarding status
379 *
380 * @since 2.29.0 Validate only primary capabilities during PayPal donations on-boarding.
381 * @since 2.9.0
382 *
383 * @param string $merchantId
384 * @param string $accessToken
385 * @param bool $usesCustomPayments
386 *
387 * @return true|string[]
388 */
389 private function isAdminSuccessfullyOnBoarded($merchantId, $accessToken, $usesCustomPayments)
390 {
391 $onBoardedData = (array)$this->payPalAuth->getSellerOnBoardingDetailsFromPayPal($merchantId, $accessToken);
392 $onBoardedData = array_filter($onBoardedData); // Remove empty values.
393 $errorMessages[] = [
394 'type' => 'json',
395 'message' => esc_html__('PayPal merchant status check API request response is:', 'give'),
396 'value' => wp_json_encode($onBoardedData),
397 ];
398
399 if (! is_ssl()) {
400 $errorMessages[] = esc_html__(
401 'A valid SSL certificate is required to accept donations and set up your PayPal account. Once a
402 certificate is installed and the site is using https, please disconnect and reconnect your account.',
403 'give'
404 );
405 }
406
407 if (array_diff(['payments_receivable', 'primary_email_confirmed'], array_keys($onBoardedData))) {
408 $errorMessages[] = __(
409 '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.',
410 'give'
411 );
412
413 // Return here since the rest of the validations will definitely fail
414 return $errorMessages;
415 }
416
417 if (! $onBoardedData['payments_receivable']) {
418 $errorMessages[] = esc_html__('An banking account needs to be connected to your PayPal account and verified', 'give');
419 }
420
421 if (! $onBoardedData['primary_email_confirmed']) {
422 $errorMessages[] = esc_html__('Your primary email address needs to be confirmed', 'give');
423 }
424
425 // This error message is only for the case when the user is using custom payments.
426 // Host card fields are supported only for specific countries and PayPal seller account of PPCP type.
427 if ($usesCustomPayments) {
428 $sellerCapabilities = array_key_exists('capabilities', $onBoardedData)
429 ? wp_list_pluck($onBoardedData['capabilities'], 'name')
430 : [];
431 $requiredCapability = 'CUSTOM_CARD_PROCESSING';
432 $customCardProcessingCapabilityIndex = array_search($requiredCapability, $sellerCapabilities, true);
433 $hasCustomCardProcessingCapability = false !== $customCardProcessingCapabilityIndex;
434
435 // If the capability is found then check if it is active.
436 if (false !== $customCardProcessingCapabilityIndex) {
437 $customCardProcessingCapability = $onBoardedData['capabilities'][$customCardProcessingCapabilityIndex];
438 $hasCustomCardProcessingCapability = 'ACTIVE' === $customCardProcessingCapability['status'];
439 }
440
441 if (! $hasCustomCardProcessingCapability) {
442 $errorMessages[] = __(
443 '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.',
444 'give'
445 );
446 }
447 }
448
449 // If there were errors then redirect the user with notices
450 return count($errorMessages) > 1 ? $errorMessages : true;
451 }
452
453 /**
454 * Redirect admin to setting section with error.
455 *
456 * @since 2.9.0
457 */
458 private function redirectWhenOnBoardingFail()
459 {
460 wp_redirect(
461 add_query_arg(
462 [
463 'post_type' => 'give_forms',
464 'page' => 'give-settings',
465 'tab' => 'gateways',
466 'section' => 'paypal',
467 'group' => 'paypal-commerce',
468 'paypal-error' => '1',
469 ],
470 admin_url('edit.php')
471 )
472 );
473
474 exit();
475 }
476
477 /**
478 * Displays a notice of the site is not using SSL
479 *
480 * @since 2.9.0
481 */
482 private function registerPayPalSSLNotice()
483 {
484 if (is_ssl() && empty($this->webhooksRepository->getWebhookConfig())) {
485 $logLink = sprintf(
486 '<a href="%1$s">%2$s</a>',
487 admin_url('/edit.php?post_type=give_forms&page=give-tools&tab=logs'),
488 esc_html__('logs data', 'give')
489 );
490
491 Give_Admin_Settings::add_error(
492 'paypal-webhook-error',
493 sprintf(
494 esc_html__(
495 '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',
496 'give'
497 ),
498 $logLink
499 )
500 );
501 }
502 }
503 }
504