Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions inc/gateways/class-paypal-oauth-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,23 +260,34 @@ public function handle_oauth_return(): void {
wu_save_setting('paypal_rest_connection_mode', $mode_prefix);

// Store additional status info if available
if (! empty($merchant_status['paymentsReceivable'])) {
wu_save_setting("paypal_rest_{$mode_prefix}_payments_receivable", $merchant_status['paymentsReceivable']);
}
$payments_receivable = isset($merchant_status['paymentsReceivable']) ? (bool) $merchant_status['paymentsReceivable'] : true;
$email_confirmed = isset($merchant_status['emailConfirmed']) ? (bool) $merchant_status['emailConfirmed'] : true;

if (! empty($merchant_status['emailConfirmed'])) {
wu_save_setting("paypal_rest_{$mode_prefix}_email_confirmed", $merchant_status['emailConfirmed']);
}
wu_save_setting("paypal_rest_{$mode_prefix}_payments_receivable", $payments_receivable);
wu_save_setting("paypal_rest_{$mode_prefix}_email_confirmed", $email_confirmed);

// Clean up the tracking transient
delete_site_transient('wu_paypal_onboarding_' . $tracking_id);

wu_log_add('paypal', sprintf('PayPal OAuth completed. Merchant ID: %s, Mode: %s', $merchant_id, $mode_prefix));
wu_log_add('paypal', sprintf('PayPal OAuth completed. Merchant ID: %s, Mode: %s, payments_receivable: %s, email_confirmed: %s', $merchant_id, $mode_prefix, $payments_receivable ? 'true' : 'false', $email_confirmed ? 'true' : 'false'));

// Automatically install webhooks for the connected account
$this->install_webhook_after_oauth($mode_prefix);

$this->add_oauth_notice('success', __('PayPal account connected successfully!', 'ultimate-multisite'));
// Show required PayPal error messages when merchant status is incomplete
if (! $payments_receivable) {
$this->add_oauth_notice(
'error',
__('Your PayPal account is not yet able to receive payments. Please complete your PayPal account setup and try connecting again.', 'ultimate-multisite')
);
} elseif (! $email_confirmed) {
$this->add_oauth_notice(
'error',
__('Your PayPal account email address is not confirmed. Please confirm your email with PayPal and try connecting again.', 'ultimate-multisite')
);
} else {
$this->add_oauth_notice('success', __('PayPal account connected successfully!', 'ultimate-multisite'));
}

// Redirect to remove query parameters
wp_safe_redirect(
Expand Down
94 changes: 71 additions & 23 deletions inc/gateways/class-paypal-rest-gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ public function hooks(): void {
// Hide PayPal from checkout when currency is not supported
add_filter('wu_get_active_gateways', [$this, 'maybe_remove_for_unsupported_currency']);

// Hide PayPal from checkout when merchant cannot receive payments
add_filter('wu_get_active_gateways', [$this, 'maybe_remove_for_invalid_merchant_status']);

// Register PayPal checkout scripts (button branding)
add_action('wu_checkout_scripts', [$this, 'register_scripts']);

Expand Down Expand Up @@ -246,6 +249,37 @@ public function maybe_remove_for_unsupported_currency(array $gateways): array {
return $gateways;
}

/**
* Removes PayPal from the active gateways list when the merchant cannot receive payments.
*
* PayPal requires that merchants with `payments_receivable=false` or
* `email_confirmed=false` are blocked from processing payments until
* their account setup is complete.
*
* Hooked to 'wu_get_active_gateways'.
*
* @since 2.0.0
* @param array $gateways The registered active gateways.
* @return array
*/
public function maybe_remove_for_invalid_merchant_status(array $gateways): array {

// Only applies when connected via OAuth
if (empty($this->merchant_id)) {
return $gateways;
}

$mode_prefix = $this->test_mode ? 'sandbox' : 'live';
$payments_receivable = wu_get_setting("paypal_rest_{$mode_prefix}_payments_receivable", true);
$email_confirmed = wu_get_setting("paypal_rest_{$mode_prefix}_email_confirmed", true);

if (! $payments_receivable || ! $email_confirmed) {
unset($gateways['paypal-rest']);
}

return $gateways;
}

/**
* Returns a branded HTML label for the PayPal option in the checkout gateway selector.
*
Expand Down Expand Up @@ -678,6 +712,12 @@ protected function api_request(string $endpoint, array $data = [], string $metho
return $response;
}

// Log PayPal-Debug-Id for every response to aid support and review submissions.
$debug_id = wp_remote_retrieve_header($response, 'paypal-debug-id');
if ($debug_id) {
$this->log(sprintf('PayPal-Debug-Id: %s [%s %s]', $debug_id, $method, $endpoint));
}

$body = json_decode(wp_remote_retrieve_body($response), true);
$code = wp_remote_retrieve_response_code($response);

Expand Down Expand Up @@ -1010,6 +1050,35 @@ protected function create_order($payment, $membership, $customer, $cart, $type):
$currency = $this->get_payment_currency_code($payment);
$description = $this->get_subscription_description($cart);

$purchase_unit = [
'reference_id' => $payment->get_hash(),
'description' => substr($description, 0, 127),
'custom_id' => sprintf('%s|%s|%s', $payment->get_id(), $membership->get_id(), $customer->get_id()),
'amount' => [
'currency_code' => $currency,
'value' => $this->format_amount($payment->get_total(), $currency),
'breakdown' => [
'item_total' => [
'currency_code' => $currency,
'value' => $this->format_amount($payment->get_subtotal(), $currency),
],
'tax_total' => [
'currency_code' => $currency,
'value' => $this->format_amount($payment->get_tax_total(), $currency),
],
],
],
'items' => $this->build_order_items($cart, $currency),
];

// Include payee.merchant_id when connected via OAuth so PayPal routes
// the payment to the correct merchant account (required for PPCP compliance).
if (! empty($this->merchant_id)) {
$purchase_unit['payee'] = [
'merchant_id' => $this->merchant_id,
];
}

$order_data = [
'intent' => 'CAPTURE',
'payment_source' => [
Expand All @@ -1025,28 +1094,7 @@ protected function create_order($payment, $membership, $customer, $cart, $type):
'email_address' => $customer->get_email_address(),
],
],
'purchase_units' => [
[
'reference_id' => $payment->get_hash(),
'description' => substr($description, 0, 127),
'custom_id' => sprintf('%s|%s|%s', $payment->get_id(), $membership->get_id(), $customer->get_id()),
'amount' => [
'currency_code' => $currency,
'value' => $this->format_amount($payment->get_total(), $currency),
'breakdown' => [
'item_total' => [
'currency_code' => $currency,
'value' => $this->format_amount($payment->get_subtotal(), $currency),
],
'tax_total' => [
'currency_code' => $currency,
'value' => $this->format_amount($payment->get_tax_total(), $currency),
],
],
],
'items' => $this->build_order_items($cart, $currency),
],
],
'purchase_units' => [ $purchase_unit ],
];

/**
Expand Down Expand Up @@ -1890,7 +1938,7 @@ function () use ($nonce, $sandbox) {

$(".wu-paypal-disconnect").on("click", function(e) {
e.preventDefault();
if (!confirm(<?php echo wp_json_encode(__('Are you sure you want to disconnect PayPal?', 'ultimate-multisite')); ?>)) return;
if (!confirm(<?php echo wp_json_encode(__('Disconnecting your PayPal account will prevent you from offering PayPal services and products on your website. Do you wish to continue?', 'ultimate-multisite')); ?>)) return;

var $btn = $(this);
$btn.prop("disabled", true);
Expand Down
Loading