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