[ 'Authorization' => 'Basic ' . base64_encode( $client_id . ':' . $secret ), 'Content-Type' => 'application/x-www-form-urlencoded', ], 'body' => [ 'grant_type' => 'client_credentials' ], 'timeout' => 15, ] ); if ( is_wp_error( $response ) ) { return new \WP_Error( 'chargeforms_paypal_unreachable', __( 'Could not reach PayPal. Please try again.', 'chargeforms' ) ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $body['access_token'] ) ) { return new \WP_Error( 'chargeforms_paypal_error', $body['error_description'] ?? __( 'PayPal rejected the authentication request.', 'chargeforms' ) ); } $ttl = max( 60, (int) ( $body['expires_in'] ?? 3600 ) - self::TOKEN_CACHE_MARGIN ); set_transient( $transient_key, $body['access_token'], $ttl ); return $body['access_token']; } /** * `$metadata['chargeforms_form_id']` is carried as `custom_id` on the * purchase unit — PayPal's equivalent of Stripe's `metadata.chargeforms_form_id`, * checked again after capture so a captured order can't be replayed * against a different form. */ public static function create_order( $amount_decimal, $currency, array $metadata = [] ) { $token = self::get_access_token(); if ( is_wp_error( $token ) ) { return $token; } $body = [ 'intent' => 'CAPTURE', 'purchase_units' => [ [ 'custom_id' => (string) ( $metadata['chargeforms_form_id'] ?? '' ), 'amount' => [ 'currency_code' => strtoupper( $currency ), 'value' => number_format( (float) $amount_decimal, 2, '.', '' ), ], ], ], ]; $response = wp_remote_post( self::api_base() . '/v2/checkout/orders', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', ], 'body' => wp_json_encode( $body ), 'timeout' => 15, ] ); return self::parse_response( $response ); } public static function capture_order( $order_id ) { $token = self::get_access_token(); if ( is_wp_error( $token ) ) { return $token; } $response = wp_remote_post( self::api_base() . '/v2/checkout/orders/' . rawurlencode( $order_id ) . '/capture', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', ], 'timeout' => 15, ] ); return self::parse_response( $response ); } private static function parse_response( $response ) { if ( is_wp_error( $response ) ) { return new \WP_Error( 'chargeforms_paypal_unreachable', __( 'Could not reach PayPal. Please try again.', 'chargeforms' ) ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! empty( $body['name'] ) && ! empty( $body['details'] ) ) { // PayPal's error shape: { name, message, details: [{issue, description}] }. $detail = $body['details'][0]['description'] ?? ( $body['message'] ?? __( 'PayPal rejected the request.', 'chargeforms' ) ); return new \WP_Error( 'chargeforms_paypal_error', $detail ); } return is_array( $body ) ? $body : new \WP_Error( 'chargeforms_paypal_error', __( 'Unexpected response from PayPal.', 'chargeforms' ) ); } /** Thin delegate onto Commerce_Calculator — see Stripe_Gateway::calculate_total()'s matching comment. */ public static function calculate_total( array $form, array $submitted_values ) { return Commerce_Calculator::calculate_total( $form, $submitted_values ); } }