mirror of
https://gh.wpcy.net/https://github.com/superdav42/wp-update-server-plugin.git
synced 2026-04-29 02:32:17 +08:00
* chore: add wp-cli.yml and local dev environment docs
Point wp-cli at shared WordPress 7.0-RC2 multisite dev install at
../wordpress (wordpress.local:8080). Documents reset workflow and
WP-CLI usage in AGENTS.md.
* feat(paypal): add comprehensive logging to PayPal Connect proxy
Previously only the deauthorize endpoint logged anything. All other
endpoints (oauth/init, oauth/verify, partner-token) were silent, making
it impossible to debug failed PayPal API calls in production.
This commit adds:
- Protected log() helper with consistent '[PayPal Connect]' prefix and
JSON-encoded context data
- get_debug_id() helper to extract the PayPal-Debug-Id response header
from every PayPal API call (the key value PayPal support and the
integration review team ask for by name)
- Request-received and outcome log entries on all handlers
- Error logging with PayPal-Debug-Id on all failed PayPal API calls:
- /v1/oauth2/token (partner access token)
- /v2/customer/partner-referrals
- /v1/customer/partners/{partner_id}/merchant-integrations/{merchant_id}
- Success logging with debug ID and key response fields
Sensitive values (access tokens, client secrets) are never logged.
Context: the PayPal integration review requires debug IDs from test
API calls. Without server-side logging, the only way to get them was
to instrument the plugin side, which misses proxy-side failures entirely.
709 lines
20 KiB
PHP
709 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* PayPal Connect Proxy.
|
|
*
|
|
* Handles PayPal Partner Referrals API on behalf of customer sites,
|
|
* keeping partner credentials secure on the ultimatemultisite.com server.
|
|
*
|
|
* Mirrors the Stripe Connect proxy pattern at /wp-json/stripe-connect/v1.
|
|
*
|
|
* REST API namespace: paypal-connect/v1
|
|
*
|
|
* Endpoints:
|
|
* POST /oauth/init - Create partner referral URL for merchant onboarding
|
|
* POST /oauth/verify - Verify merchant integration status after onboarding
|
|
* POST /partner-token - Get a short-lived partner access token for platform fees
|
|
* POST /deauthorize - Notify proxy that a site has disconnected
|
|
*
|
|
* @package WP_Update_Server_Plugin
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
namespace WP_Update_Server_Plugin;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
/**
|
|
* PayPal Connect proxy class.
|
|
*/
|
|
class PayPal_Connect {
|
|
|
|
/**
|
|
* REST API namespace.
|
|
*
|
|
* @var string
|
|
*/
|
|
const API_NAMESPACE = 'paypal-connect/v1';
|
|
|
|
/**
|
|
* Partner Attribution ID (BN Code).
|
|
*
|
|
* @var string
|
|
*/
|
|
const BN_CODE = 'ULTIMATE_SP_PPCP';
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct() {
|
|
|
|
add_action('rest_api_init', [$this, 'register_routes']);
|
|
}
|
|
|
|
/**
|
|
* Log a PayPal Connect proxy event.
|
|
*
|
|
* Writes to the PHP error log with a consistent prefix so entries are easy
|
|
* to grep. Sensitive values (access tokens, client secrets, credentials)
|
|
* must NEVER be passed to this method — only request metadata, response
|
|
* codes, PayPal debug IDs, and error messages.
|
|
*
|
|
* @param string $message The message to log.
|
|
* @param array $context Optional context data (will be JSON-encoded).
|
|
* @return void
|
|
*/
|
|
protected function log(string $message, array $context = []): void {
|
|
|
|
$line = '[PayPal Connect] ' . $message;
|
|
|
|
if (! empty($context)) {
|
|
$line .= ' ' . wp_json_encode($context);
|
|
}
|
|
|
|
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
error_log($line);
|
|
}
|
|
|
|
/**
|
|
* Extract the PayPal-Debug-Id header from a response.
|
|
*
|
|
* PayPal returns this header on every API response. It is the single most
|
|
* important value for debugging failed calls — PayPal support and the
|
|
* integration review team ask for it by name.
|
|
*
|
|
* @param array|\WP_Error $response The wp_remote_* response.
|
|
* @return string The debug ID, or empty string if not present.
|
|
*/
|
|
protected function get_debug_id($response): string {
|
|
|
|
if (is_wp_error($response)) {
|
|
return '';
|
|
}
|
|
|
|
$debug_id = wp_remote_retrieve_header($response, 'paypal-debug-id');
|
|
|
|
return is_string($debug_id) ? $debug_id : '';
|
|
}
|
|
|
|
/**
|
|
* Register REST API routes.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function register_routes(): void {
|
|
|
|
register_rest_route(
|
|
self::API_NAMESPACE,
|
|
'/oauth/init',
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'handle_oauth_init'],
|
|
'permission_callback' => '__return_true',
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
self::API_NAMESPACE,
|
|
'/oauth/verify',
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'handle_oauth_verify'],
|
|
'permission_callback' => '__return_true',
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
self::API_NAMESPACE,
|
|
'/partner-token',
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'handle_partner_token'],
|
|
'permission_callback' => '__return_true',
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
self::API_NAMESPACE,
|
|
'/deauthorize',
|
|
[
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'handle_deauthorize'],
|
|
'permission_callback' => '__return_true',
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
self::API_NAMESPACE,
|
|
'/status',
|
|
[
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'handle_status'],
|
|
'permission_callback' => '__return_true',
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get PayPal API base URL.
|
|
*
|
|
* @param bool $test_mode Whether to use sandbox.
|
|
* @return string
|
|
*/
|
|
public function get_api_base_url(bool $test_mode): string {
|
|
|
|
return $test_mode ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
|
}
|
|
|
|
/**
|
|
* Get partner credentials for a given mode.
|
|
*
|
|
* Reads from constants defined in wp-config.php on the proxy server.
|
|
*
|
|
* @param bool $test_mode Whether to use sandbox credentials.
|
|
* @return array{client_id: string, client_secret: string, merchant_id: string}
|
|
*/
|
|
public function get_partner_credentials(bool $test_mode): array {
|
|
|
|
if ($test_mode) {
|
|
return [
|
|
'client_id' => defined('WU_PAYPAL_SANDBOX_PARTNER_CLIENT_ID') ? WU_PAYPAL_SANDBOX_PARTNER_CLIENT_ID : '',
|
|
'client_secret' => defined('WU_PAYPAL_SANDBOX_PARTNER_CLIENT_SECRET') ? WU_PAYPAL_SANDBOX_PARTNER_CLIENT_SECRET : '',
|
|
'merchant_id' => defined('WU_PAYPAL_SANDBOX_PARTNER_MERCHANT_ID') ? WU_PAYPAL_SANDBOX_PARTNER_MERCHANT_ID : '',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'client_id' => defined('WU_PAYPAL_PARTNER_CLIENT_ID') ? WU_PAYPAL_PARTNER_CLIENT_ID : '',
|
|
'client_secret' => defined('WU_PAYPAL_PARTNER_CLIENT_SECRET') ? WU_PAYPAL_PARTNER_CLIENT_SECRET : '',
|
|
'merchant_id' => defined('WU_PAYPAL_PARTNER_MERCHANT_ID') ? WU_PAYPAL_PARTNER_MERCHANT_ID : '',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get a partner access token from PayPal.
|
|
*
|
|
* @param bool $test_mode Whether to use sandbox.
|
|
* @return string|\WP_Error
|
|
*/
|
|
public function get_partner_access_token(bool $test_mode) {
|
|
|
|
$cache_key = 'wu_pp_proxy_token_' . ($test_mode ? 'sandbox' : 'live');
|
|
$cached_token = get_transient($cache_key);
|
|
|
|
if ($cached_token) {
|
|
return $cached_token;
|
|
}
|
|
|
|
$credentials = $this->get_partner_credentials($test_mode);
|
|
|
|
if (empty($credentials['client_id']) || empty($credentials['client_secret'])) {
|
|
return new \WP_Error(
|
|
'missing_credentials',
|
|
'PayPal partner credentials are not configured on the proxy server.'
|
|
);
|
|
}
|
|
|
|
$response = wp_remote_post(
|
|
$this->get_api_base_url($test_mode) . '/v1/oauth2/token',
|
|
[
|
|
'headers' => [
|
|
'Authorization' => 'Basic ' . base64_encode($credentials['client_id'] . ':' . $credentials['client_secret']), // phpcs:ignore
|
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
],
|
|
'body' => 'grant_type=client_credentials',
|
|
'timeout' => 30,
|
|
]
|
|
);
|
|
|
|
if (is_wp_error($response)) {
|
|
$this->log('Partner access token request failed (transport error)', [
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
'error' => $response->get_error_message(),
|
|
]);
|
|
|
|
return $response;
|
|
}
|
|
|
|
$body = json_decode(wp_remote_retrieve_body($response), true);
|
|
$code = wp_remote_retrieve_response_code($response);
|
|
$debug_id = $this->get_debug_id($response);
|
|
|
|
if (200 !== $code || empty($body['access_token'])) {
|
|
$error_msg = $body['error_description'] ?? 'Failed to obtain PayPal access token';
|
|
|
|
$this->log('Partner access token request failed', [
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
'status' => $code,
|
|
'debug_id' => $debug_id,
|
|
'error' => $error_msg,
|
|
]);
|
|
|
|
return new \WP_Error('token_error', $error_msg);
|
|
}
|
|
|
|
$this->log('Partner access token obtained', [
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
'debug_id' => $debug_id,
|
|
]);
|
|
|
|
$expires_in = isset($body['expires_in']) ? (int) $body['expires_in'] - 300 : 3300;
|
|
set_transient($cache_key, $body['access_token'], $expires_in);
|
|
|
|
return $body['access_token'];
|
|
}
|
|
|
|
/**
|
|
* Handle POST /oauth/init
|
|
*
|
|
* Creates a PayPal Partner Referral URL for merchant onboarding.
|
|
* The customer site sends its return URL; the proxy calls PayPal
|
|
* with the partner credentials and returns the onboarding link.
|
|
*
|
|
* @param \WP_REST_Request $request The request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_oauth_init(\WP_REST_Request $request): \WP_REST_Response {
|
|
|
|
$body = $request->get_json_params();
|
|
|
|
$return_url = $body['returnUrl'] ?? '';
|
|
$test_mode = (bool) ($body['testMode'] ?? true);
|
|
|
|
$this->log('oauth/init received', [
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
'return_url' => $return_url,
|
|
]);
|
|
|
|
if (empty($return_url)) {
|
|
$this->log('oauth/init rejected: missing returnUrl');
|
|
|
|
return new \WP_REST_Response(
|
|
['error' => 'returnUrl is required'],
|
|
400
|
|
);
|
|
}
|
|
|
|
$access_token = $this->get_partner_access_token($test_mode);
|
|
|
|
if (is_wp_error($access_token)) {
|
|
return new \WP_REST_Response(
|
|
['error' => $access_token->get_error_message()],
|
|
500
|
|
);
|
|
}
|
|
|
|
$credentials = $this->get_partner_credentials($test_mode);
|
|
|
|
// Generate a tracking ID for this onboarding
|
|
$tracking_id = 'wu_' . wp_generate_uuid4();
|
|
|
|
// Store tracking ID for later verification (24 hours)
|
|
set_transient(
|
|
'wu_pp_onboarding_' . $tracking_id,
|
|
[
|
|
'started' => time(),
|
|
'test_mode' => $test_mode,
|
|
'return_url' => $return_url,
|
|
],
|
|
DAY_IN_SECONDS
|
|
);
|
|
|
|
// Append tracking_id to the return URL so the customer site can verify
|
|
$return_url_with_tracking = add_query_arg('tracking_id', $tracking_id, $return_url);
|
|
|
|
// Build the partner referral request
|
|
$referral_data = [
|
|
'tracking_id' => $tracking_id,
|
|
'partner_config_override' => [
|
|
'return_url' => $return_url_with_tracking,
|
|
],
|
|
'operations' => [
|
|
[
|
|
'operation' => 'API_INTEGRATION',
|
|
'api_integration_preference' => [
|
|
'rest_api_integration' => [
|
|
'integration_method' => 'PAYPAL',
|
|
'integration_type' => 'THIRD_PARTY',
|
|
'third_party_details' => [
|
|
'features' => [
|
|
'PAYMENT',
|
|
'REFUND',
|
|
'PARTNER_FEE',
|
|
'DELAY_FUNDS_DISBURSEMENT',
|
|
'ACCESS_MERCHANT_INFORMATION',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'products' => ['PPCP'],
|
|
'legal_consents' => [
|
|
[
|
|
'type' => 'SHARE_DATA_CONSENT',
|
|
'granted' => true,
|
|
],
|
|
],
|
|
];
|
|
|
|
$response = wp_remote_post(
|
|
$this->get_api_base_url($test_mode) . '/v2/customer/partner-referrals',
|
|
[
|
|
'headers' => [
|
|
'Authorization' => 'Bearer ' . $access_token,
|
|
'Content-Type' => 'application/json',
|
|
'PayPal-Partner-Attribution-Id' => self::BN_CODE,
|
|
],
|
|
'body' => wp_json_encode($referral_data),
|
|
'timeout' => 30,
|
|
]
|
|
);
|
|
|
|
if (is_wp_error($response)) {
|
|
$this->log('partner-referrals request failed (transport error)', [
|
|
'tracking_id' => $tracking_id,
|
|
'error' => $response->get_error_message(),
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
['error' => 'Failed to create partner referral: ' . $response->get_error_message()],
|
|
500
|
|
);
|
|
}
|
|
|
|
$resp_body = json_decode(wp_remote_retrieve_body($response), true);
|
|
$resp_code = wp_remote_retrieve_response_code($response);
|
|
$debug_id = $this->get_debug_id($response);
|
|
|
|
if (201 !== $resp_code || empty($resp_body['links'])) {
|
|
$error_msg = $resp_body['message'] ?? 'Failed to create partner referral';
|
|
|
|
$this->log('partner-referrals returned non-201', [
|
|
'tracking_id' => $tracking_id,
|
|
'status' => $resp_code,
|
|
'debug_id' => $debug_id,
|
|
'error' => $error_msg,
|
|
'details' => $resp_body['details'] ?? null,
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
['error' => $error_msg],
|
|
500
|
|
);
|
|
}
|
|
|
|
// Find the action_url link
|
|
$action_url = '';
|
|
|
|
foreach ($resp_body['links'] as $link) {
|
|
if ('action_url' === $link['rel']) {
|
|
$action_url = $link['href'];
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (empty($action_url)) {
|
|
$this->log('partner-referrals missing action_url link', [
|
|
'tracking_id' => $tracking_id,
|
|
'debug_id' => $debug_id,
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
['error' => 'No action URL returned from PayPal'],
|
|
500
|
|
);
|
|
}
|
|
|
|
$this->log('partner-referrals succeeded', [
|
|
'tracking_id' => $tracking_id,
|
|
'debug_id' => $debug_id,
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
[
|
|
'actionUrl' => $action_url,
|
|
'trackingId' => $tracking_id,
|
|
],
|
|
200
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle POST /oauth/verify
|
|
*
|
|
* After merchant completes onboarding and returns to the customer site,
|
|
* the customer site calls this endpoint to verify the merchant's status
|
|
* using the partner credentials (which the customer site doesn't have).
|
|
*
|
|
* @param \WP_REST_Request $request The request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_oauth_verify(\WP_REST_Request $request): \WP_REST_Response {
|
|
|
|
$body = $request->get_json_params();
|
|
|
|
$merchant_id = $body['merchantId'] ?? '';
|
|
$tracking_id = $body['trackingId'] ?? '';
|
|
$test_mode = (bool) ($body['testMode'] ?? true);
|
|
|
|
$this->log('oauth/verify received', [
|
|
'merchant_id' => $merchant_id,
|
|
'tracking_id' => $tracking_id,
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
]);
|
|
|
|
if (empty($merchant_id) || empty($tracking_id)) {
|
|
$this->log('oauth/verify rejected: missing merchantId or trackingId');
|
|
|
|
return new \WP_REST_Response(
|
|
['error' => 'merchantId and trackingId are required'],
|
|
400
|
|
);
|
|
}
|
|
|
|
// Verify the tracking ID was created by us
|
|
$onboarding_data = get_transient('wu_pp_onboarding_' . $tracking_id);
|
|
|
|
if (! $onboarding_data) {
|
|
$this->log('oauth/verify rejected: invalid or expired tracking ID', [
|
|
'tracking_id' => $tracking_id,
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
['error' => 'Invalid or expired tracking ID'],
|
|
400
|
|
);
|
|
}
|
|
|
|
// Clean up the tracking transient
|
|
delete_transient('wu_pp_onboarding_' . $tracking_id);
|
|
|
|
$access_token = $this->get_partner_access_token($test_mode);
|
|
|
|
if (is_wp_error($access_token)) {
|
|
return new \WP_REST_Response(
|
|
['error' => $access_token->get_error_message()],
|
|
500
|
|
);
|
|
}
|
|
|
|
$credentials = $this->get_partner_credentials($test_mode);
|
|
|
|
if (empty($credentials['merchant_id'])) {
|
|
// Without partner merchant ID, record the onboarding event and return basic success.
|
|
$this->log('oauth/verify: partner merchant_id not configured, skipping merchant-integrations call', [
|
|
'merchant_id' => $merchant_id,
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
]);
|
|
|
|
PayPal_Merchants_Table::upsert_merchant($merchant_id, $tracking_id, $test_mode);
|
|
|
|
return new \WP_REST_Response(
|
|
[
|
|
'merchantId' => $merchant_id,
|
|
'paymentsReceivable' => true,
|
|
'emailConfirmed' => true,
|
|
],
|
|
200
|
|
);
|
|
}
|
|
|
|
$response = wp_remote_get(
|
|
$this->get_api_base_url($test_mode) . '/v1/customer/partners/' . $credentials['merchant_id'] . '/merchant-integrations/' . $merchant_id,
|
|
[
|
|
'headers' => [
|
|
'Authorization' => 'Bearer ' . $access_token,
|
|
'Content-Type' => 'application/json',
|
|
'PayPal-Partner-Attribution-Id' => self::BN_CODE,
|
|
],
|
|
'timeout' => 30,
|
|
]
|
|
);
|
|
|
|
if (is_wp_error($response)) {
|
|
$this->log('merchant-integrations request failed (transport error)', [
|
|
'merchant_id' => $merchant_id,
|
|
'error' => $response->get_error_message(),
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
['error' => 'Failed to verify merchant: ' . $response->get_error_message()],
|
|
500
|
|
);
|
|
}
|
|
|
|
$resp_body = json_decode(wp_remote_retrieve_body($response), true);
|
|
$resp_code = wp_remote_retrieve_response_code($response);
|
|
$debug_id = $this->get_debug_id($response);
|
|
|
|
if (200 !== $resp_code) {
|
|
$error_msg = $resp_body['message'] ?? 'Failed to verify merchant status';
|
|
|
|
$this->log('merchant-integrations returned non-200', [
|
|
'merchant_id' => $merchant_id,
|
|
'status' => $resp_code,
|
|
'debug_id' => $debug_id,
|
|
'error' => $error_msg,
|
|
'details' => $resp_body['details'] ?? null,
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
['error' => $error_msg],
|
|
$resp_code
|
|
);
|
|
}
|
|
|
|
// Record the successful onboarding event.
|
|
$verified_merchant_id = $resp_body['merchant_id'] ?? $merchant_id;
|
|
$verified_tracking_id = $resp_body['tracking_id'] ?? $tracking_id;
|
|
|
|
$this->log('merchant-integrations succeeded', [
|
|
'merchant_id' => $verified_merchant_id,
|
|
'debug_id' => $debug_id,
|
|
'payments_receivable' => $resp_body['payments_receivable'] ?? false,
|
|
'email_confirmed' => $resp_body['primary_email_confirmed'] ?? false,
|
|
]);
|
|
|
|
PayPal_Merchants_Table::upsert_merchant($verified_merchant_id, $verified_tracking_id, $test_mode);
|
|
|
|
return new \WP_REST_Response(
|
|
[
|
|
'merchantId' => $verified_merchant_id,
|
|
'trackingId' => $verified_tracking_id,
|
|
'paymentsReceivable' => $resp_body['payments_receivable'] ?? false,
|
|
'emailConfirmed' => $resp_body['primary_email_confirmed'] ?? false,
|
|
],
|
|
200
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle POST /partner-token
|
|
*
|
|
* Returns a short-lived partner access token and partner client ID.
|
|
* Used by the plugin to create PayPal orders with platform fees
|
|
* via the PayPal-Auth-Assertion header.
|
|
*
|
|
* The partner access token alone cannot make payments — it requires
|
|
* a PayPal-Auth-Assertion header with a merchant's payer_id that was
|
|
* onboarded through our partner referral.
|
|
*
|
|
* @param \WP_REST_Request $request The request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_partner_token(\WP_REST_Request $request): \WP_REST_Response {
|
|
|
|
$body = $request->get_json_params();
|
|
$test_mode = (bool) ($body['testMode'] ?? true);
|
|
|
|
$this->log('partner-token received', [
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
]);
|
|
|
|
$access_token = $this->get_partner_access_token($test_mode);
|
|
|
|
if (is_wp_error($access_token)) {
|
|
return new \WP_REST_Response(
|
|
['error' => $access_token->get_error_message()],
|
|
500
|
|
);
|
|
}
|
|
|
|
$credentials = $this->get_partner_credentials($test_mode);
|
|
|
|
$this->log('partner-token issued', [
|
|
'mode' => $test_mode ? 'sandbox' : 'live',
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
[
|
|
'access_token' => $access_token,
|
|
'partner_client_id' => $credentials['client_id'],
|
|
'expires_in' => 3300, // ~55 minutes (conservative)
|
|
],
|
|
200
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle GET /status
|
|
*
|
|
* Returns whether PayPal OAuth Connect is available.
|
|
* OAuth is enabled only when partner credentials are configured
|
|
* on the proxy server. Customer sites cache this response.
|
|
*
|
|
* @param \WP_REST_Request $request The request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_status(\WP_REST_Request $request): \WP_REST_Response {
|
|
|
|
$sandbox_creds = $this->get_partner_credentials(true);
|
|
$live_creds = $this->get_partner_credentials(false);
|
|
|
|
$sandbox_ready = ! empty($sandbox_creds['client_id']) && ! empty($sandbox_creds['client_secret']);
|
|
$live_ready = ! empty($live_creds['client_id']) && ! empty($live_creds['client_secret']);
|
|
|
|
return new \WP_REST_Response(
|
|
[
|
|
'oauth_enabled' => $sandbox_ready || $live_ready,
|
|
'sandbox_ready' => $sandbox_ready,
|
|
'live_ready' => $live_ready,
|
|
],
|
|
200
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle POST /deauthorize
|
|
*
|
|
* Notification that a customer site has disconnected.
|
|
* Records the disconnect event in the analytics table and logs for auditing.
|
|
*
|
|
* @param \WP_REST_Request $request The request.
|
|
* @return \WP_REST_Response
|
|
*/
|
|
public function handle_deauthorize(\WP_REST_Request $request): \WP_REST_Response {
|
|
|
|
$body = $request->get_json_params();
|
|
|
|
$site_url = $body['siteUrl'] ?? 'unknown';
|
|
$merchant_id = $body['merchantId'] ?? '';
|
|
$test_mode = (bool) ($body['testMode'] ?? true);
|
|
$mode = $test_mode ? 'sandbox' : 'live';
|
|
|
|
$this->log('deauthorize received', [
|
|
'site_url' => $site_url,
|
|
'merchant_id' => $merchant_id ?: 'unknown',
|
|
'mode' => $mode,
|
|
]);
|
|
|
|
// Record the disconnect event in the analytics table when a merchant ID is provided.
|
|
if ( ! empty($merchant_id)) {
|
|
PayPal_Merchants_Table::mark_disconnected($merchant_id, $test_mode);
|
|
}
|
|
|
|
// Log the disconnect for auditing.
|
|
$this->log('Site disconnected', [
|
|
'site_url' => $site_url,
|
|
'merchant_id' => $merchant_id ?: 'unknown',
|
|
'mode' => $mode,
|
|
]);
|
|
|
|
return new \WP_REST_Response(
|
|
['success' => true],
|
|
200
|
|
);
|
|
}
|
|
}
|