init
This commit is contained in:
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Overwrites the default payment settings sections in WooCommerce
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Payments_Admin_Sections_Overwrite Class.
|
||||
*/
|
||||
class WC_Payments_Admin_Sections_Overwrite {
|
||||
/**
|
||||
* WC_Payments_Account instance to get information about the account.
|
||||
*
|
||||
* @var WC_Payments_Account
|
||||
*/
|
||||
private $account;
|
||||
|
||||
/**
|
||||
* WC_Payments_Admin_Sections_Overwrite constructor.
|
||||
*
|
||||
* @param WC_Payments_Account $account WC_Payments_Account instance.
|
||||
*/
|
||||
public function __construct( WC_Payments_Account $account ) {
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this class's WP hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init_hooks() {
|
||||
add_filter( 'woocommerce_get_sections_checkout', [ $this, 'add_checkout_sections' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an "all payment methods" and a "woopayments" section to the gateways settings page
|
||||
*
|
||||
* @param array $default_sections the sections for the payment gateways tab.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_checkout_sections( array $default_sections ): array {
|
||||
$sections_to_render = [];
|
||||
$sections_to_render['woocommerce_payments'] = 'WooPayments';
|
||||
$sections_to_render[''] = __( 'All payment methods', 'woocommerce-payments' );
|
||||
|
||||
return $sections_to_render;
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_Payments_Admin_Settings
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* WC_Payments_Admin_Settings class.
|
||||
*/
|
||||
class WC_Payments_Admin_Settings {
|
||||
|
||||
/**
|
||||
* WC_Payment_Gateway_WCPay.
|
||||
*
|
||||
* @var WC_Payment_Gateway_WCPay
|
||||
*/
|
||||
private $gateway;
|
||||
|
||||
/**
|
||||
* Set of parameters to build the URL to the gateway's settings page.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $settings_url_params = [
|
||||
'page' => 'wc-settings',
|
||||
'tab' => 'checkout',
|
||||
'section' => WC_Payment_Gateway_WCPay::GATEWAY_ID,
|
||||
];
|
||||
|
||||
/**
|
||||
* Initialize class actions.
|
||||
*
|
||||
* @param WC_Payment_Gateway_WCPay $gateway Payment Gateway.
|
||||
*/
|
||||
public function __construct( WC_Payment_Gateway_WCPay $gateway ) {
|
||||
$this->gateway = $gateway;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this class's WP hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init_hooks() {
|
||||
add_action( 'woocommerce_woocommerce_payments_admin_notices', [ $this, 'display_test_mode_notice' ] );
|
||||
add_filter( 'plugin_action_links_' . plugin_basename( WCPAY_PLUGIN_FILE ), [ $this, 'add_plugin_links' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notice explaining test mode when it's enabled.
|
||||
*/
|
||||
public function display_test_mode_notice() {
|
||||
if ( WC_Payments::mode()->is_test() ) {
|
||||
?>
|
||||
<div id="wcpay-test-mode-notice" class="notice notice-warning">
|
||||
<p>
|
||||
<b><?php esc_html_e( 'Test mode active: ', 'woocommerce-payments' ); ?></b>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: WooPayments */
|
||||
esc_html__( "All transactions are simulated. Customers can't make real purchases through %s.", 'woocommerce-payments' ),
|
||||
'WooPayments'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds links to the plugin's row in the "Plugins" Wp-Admin page.
|
||||
*
|
||||
* @see https://codex.wordpress.org/Plugin_API/Filter_Reference/plugin_action_links_(plugin_file_name)
|
||||
* @param array $links The existing list of links that will be rendered.
|
||||
* @return array The list of links that will be rendered, after adding some links specific to this plugin.
|
||||
*/
|
||||
public function add_plugin_links( $links ) {
|
||||
$plugin_links = [
|
||||
'<a href="' . esc_attr( self::get_settings_url() ) . '">' . esc_html__( 'Settings', 'woocommerce-payments' ) . '</a>',
|
||||
];
|
||||
|
||||
return array_merge( $plugin_links, $links );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current page is the WooPayments settings page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_current_page_settings() {
|
||||
return count( self::$settings_url_params ) === count( array_intersect_assoc( $_GET, self::$settings_url_params ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the configuration screen for this gateway, for use in internal links.
|
||||
*
|
||||
* @param array $query_args Optional additional query args to append to the URL.
|
||||
*
|
||||
* @return string URL of the configuration screen for this gateway
|
||||
*/
|
||||
public static function get_settings_url( $query_args = [] ) {
|
||||
return admin_url( add_query_arg( array_merge( self::$settings_url_params, $query_args ), 'admin.php' ) ); // nosemgrep: audit.php.wp.security.xss.query-arg -- constant string is passed in.
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_Payments_REST_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Exceptions\API_Exception;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for transactions.
|
||||
*/
|
||||
class WC_Payments_REST_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
|
||||
/**
|
||||
* Client for making requests to the WooCommerce Payments API
|
||||
*
|
||||
* @var WC_Payments_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
/**
|
||||
* WC_Payments_REST_Controller constructor.
|
||||
*
|
||||
* @param WC_Payments_API_Client $api_client - WooCommerce Payments API client.
|
||||
*/
|
||||
public function __construct( WC_Payments_API_Client $api_client ) {
|
||||
$this->api_client = $api_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards request to API client with taking care of API_Exception.
|
||||
*
|
||||
* @param string $api_method - API method name.
|
||||
* @param array $args - API method args.
|
||||
*
|
||||
* @return WP_Error|mixed - Method result of WP_Error in case of API_Exception.
|
||||
*/
|
||||
public function forward_request( $api_method, $args ) {
|
||||
try {
|
||||
$response = call_user_func_array( [ $this->api_client, $api_method ], $args );
|
||||
} catch ( API_Exception $e ) {
|
||||
$response = new WP_Error( $e->get_error_code(), $e->getMessage() );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify access.
|
||||
*
|
||||
* Override this method if custom permissions required.
|
||||
*/
|
||||
public function check_permission() {
|
||||
return current_user_can( 'manage_woocommerce' );
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Accounts_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for account details and status.
|
||||
*/
|
||||
class WC_REST_Payments_Accounts_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/accounts';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/payments/accounts',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_account_data' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account details via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_account_data( $request ) {
|
||||
$account = WC_Payments::get_account_service()->get_cached_account_data();
|
||||
if ( [] === $account ) {
|
||||
$default_currency = get_woocommerce_currency();
|
||||
$status = WC_Payments_Account::is_on_boarding_disabled() ? 'ONBOARDING_DISABLED' : 'NOACCOUNT';
|
||||
$account = [
|
||||
'card_present_eligible' => false,
|
||||
'country' => WC()->countries->get_base_country(),
|
||||
'current_deadline' => null,
|
||||
'has_overdue_requirements' => false,
|
||||
'has_pending_requirements' => false,
|
||||
'statement_descriptor' => '',
|
||||
'status' => $status,
|
||||
'store_currencies' => [
|
||||
'default' => $default_currency,
|
||||
'supported' => [
|
||||
$default_currency,
|
||||
],
|
||||
],
|
||||
'customer_currencies' => [
|
||||
'supported' => [
|
||||
$default_currency,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ( false !== $account ) {
|
||||
// Add extra properties to account if necessary.
|
||||
$account['card_present_eligible'] = false;
|
||||
$account['test_mode'] = WC_Payments::mode()->is_test();
|
||||
$account['test_mode_onboarding'] = WC_Payments::mode()->is_test_mode_onboarding();
|
||||
}
|
||||
|
||||
return rest_ensure_response( $account );
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Authorizations_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request;
|
||||
use WCPay\Core\Server\Request\List_Authorizations;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for authorizations.
|
||||
*/
|
||||
class WC_REST_Payments_Authorizations_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/authorizations';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_authorizations' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/summary',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_authorizations_summary' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<payment_intent_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_authorization' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve authorizations to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_authorizations( WP_REST_Request $request ) {
|
||||
$wcpay_request = List_Authorizations::from_rest_request( $request );
|
||||
|
||||
return $wcpay_request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve authorization to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_authorization( WP_REST_Request $request ) {
|
||||
$payment_intent_id = $request->get_param( 'payment_intent_id' );
|
||||
$request = Request::get( WC_Payments_API_Client::AUTHORIZATIONS_API, $payment_intent_id );
|
||||
$request->assign_hook( 'wcpay_get_authorization_request' );
|
||||
return $request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve authorizations summary to respond with via API.
|
||||
*/
|
||||
public function get_authorizations_summary() {
|
||||
$request = Request::get( WC_Payments_API_Client::AUTHORIZATIONS_API . '/summary' );
|
||||
$request->assign_hook( 'wc_pay_get_authorizations_summary' );
|
||||
return $request->handle_rest_request();
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Capital_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for Capital loans functionality.
|
||||
*/
|
||||
class WC_REST_Payments_Capital_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/capital';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/active_loan_summary',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_active_loan_summary' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/loans',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_loans' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the summary of the active Capital loan.
|
||||
*/
|
||||
public function get_active_loan_summary() {
|
||||
$request = Request::get( WC_Payments_API_Client::CAPITAL_API . '/active_loan_summary' );
|
||||
$request->assign_hook( 'wcpay_get_active_loan_summary_request' );
|
||||
return $request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the past and present Capital loans.
|
||||
*/
|
||||
public function get_loans() {
|
||||
$request = Request::get( WC_Payments_API_Client::CAPITAL_API . '/loans' );
|
||||
$request->assign_hook( 'wcpay_get_loans_request' );
|
||||
return $request->handle_rest_request();
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Charges_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\Get_Charge;
|
||||
use WCPay\Exceptions\API_Exception;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for charges.
|
||||
*/
|
||||
class WC_REST_Payments_Charges_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/charges';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<charge_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_charge' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/order/(?P<order_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'generate_charge_from_order' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve charge to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_charge( $request ) {
|
||||
$charge_id = $request->get_param( 'charge_id' );
|
||||
|
||||
try {
|
||||
$wcpay_request = Get_Charge::create( $charge_id );
|
||||
$charge = $wcpay_request->send();
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( 'wcpay_get_charge', $e->getMessage() ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $charge );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a charge-like object from an order.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function generate_charge_from_order( $request ) {
|
||||
$order_id = $request['order_id'];
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( false === $order ) {
|
||||
return new WP_Error( 'wcpay_missing_order', __( 'Order not found', 'woocommerce-payments' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
$currency = $order->get_currency();
|
||||
$amount = WC_Payments_Utils::prepare_amount( $order->get_total(), $currency );
|
||||
$billing_details = WC_Payments::get_order_service()->get_billing_data_from_order( $order ); // TODO: Inject order_service after #7464 is fixed.
|
||||
$date_created = $order->get_date_created();
|
||||
$intent_id = $order->get_meta( '_intent_id' );
|
||||
$intent_status = $order->get_meta( '_intent_status' );
|
||||
|
||||
$charge = [
|
||||
'id' => $order->get_id(),
|
||||
'amount' => $amount,
|
||||
'amount_captured' => 0,
|
||||
'amount_refunded' => 0,
|
||||
'application_fee_amount' => 0,
|
||||
'balance_transaction' => [
|
||||
'currency' => $currency,
|
||||
'amount' => $amount,
|
||||
'fee' => 0,
|
||||
],
|
||||
'billing_details' => $billing_details,
|
||||
'created' => $date_created ? $date_created->getTimestamp() : null,
|
||||
'currency' => $currency,
|
||||
'disputed' => false,
|
||||
'outcome' => false,
|
||||
'order' => $this->api_client->build_order_info( $order ),
|
||||
'paid' => false,
|
||||
'paydown' => null,
|
||||
'payment_intent' => ! empty( $intent_id ) ? $intent_id : null,
|
||||
'payment_method_details' => [
|
||||
'card' => [
|
||||
'country' => $order->get_billing_country(),
|
||||
'checks' => [],
|
||||
'network' => '',
|
||||
],
|
||||
'type' => 'card',
|
||||
],
|
||||
'refunded' => false,
|
||||
'refunds' => null,
|
||||
'status' => ! empty( $intent_status ) ? $intent_status : $order->get_status(),
|
||||
];
|
||||
|
||||
$charge = $this->api_client->add_formatted_address_to_charge_object( $charge );
|
||||
|
||||
return rest_ensure_response( $charge );
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Connection_Tokens_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for terminal tokens.
|
||||
*/
|
||||
class WC_REST_Payments_Connection_Tokens_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/connection_tokens';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/payments/connection_tokens',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'create_token' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connection token via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function create_token( $request ) {
|
||||
$response = $this->forward_request( 'create_token', [ $request ] );
|
||||
|
||||
// As an aid to mobile clients, tuck in the test_mode flag in the response returned to the request.
|
||||
if ( is_a( $response, 'WP_REST_Response' ) ) {
|
||||
if ( property_exists( $response, 'data' ) ) {
|
||||
if ( is_array( $response->data ) ) {
|
||||
$response->data['test_mode'] = WC_Payments::mode()->is_test();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Customer_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request;
|
||||
use WCPay\Exceptions\API_Exception;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for customers.
|
||||
*/
|
||||
class WC_REST_Payments_Customer_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Onboarding Service.
|
||||
*
|
||||
* @var WC_Payments_Customer_Service
|
||||
*/
|
||||
protected $customer_service;
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/customers';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param WC_Payments_API_Client $api_client WooCommerce Payments API client.
|
||||
* @param WC_Payments_Customer_Service $customer_service Token service.
|
||||
*/
|
||||
public function __construct(
|
||||
WC_Payments_API_Client $api_client,
|
||||
WC_Payments_Customer_Service $customer_service
|
||||
) {
|
||||
parent::__construct( $api_client );
|
||||
$this->customer_service = $customer_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<customer_id>\w+)/payment_methods',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_customer_payment_methods' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
],
|
||||
'schema' => [ $this, 'get_item_schema' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transaction to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_customer_payment_methods( $request ) {
|
||||
$customer_id = $request->get_param( 'customer_id' );
|
||||
$payment_methods_types = WC_Payments::get_gateway()->get_upe_enabled_payment_method_ids() ?? [];
|
||||
$payment_methods = [];
|
||||
|
||||
// Perhaps we can fetch it directly from server and avoid looping to get payment methods from cache.
|
||||
foreach ( $payment_methods_types as $type ) {
|
||||
try {
|
||||
$payment_methods[] = $this->customer_service->get_payment_methods_for_customer( $customer_id, $type );
|
||||
} catch ( API_Exception $e ) {
|
||||
wp_send_json_error(
|
||||
wp_strip_all_tags( $e->getMessage() ),
|
||||
403
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$payment_methods = array_merge( ...$payment_methods );
|
||||
$data = [];
|
||||
foreach ( $payment_methods as $payment_method ) {
|
||||
$response = $this->prepare_item_for_response( $payment_method, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare each item for response.
|
||||
*
|
||||
* @param array|mixed $item Item to prepare.
|
||||
* @param WP_REST_Request $request Request instance.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
|
||||
$prepared_item = [];
|
||||
|
||||
$prepared_item['id'] = $item['id'];
|
||||
$prepared_item['type'] = $item['type'];
|
||||
$prepared_item['billing_details'] = $item['billing_details'];
|
||||
if ( array_key_exists( 'card', $item ) ) {
|
||||
$prepared_item['card'] = [
|
||||
'brand' => $item['card']['brand'],
|
||||
'last4' => $item['card']['last4'],
|
||||
'exp_month' => $item['card']['exp_month'],
|
||||
'exp_year' => $item['card']['exp_year'],
|
||||
];
|
||||
}
|
||||
if ( array_key_exists( 'card', $item ) ) {
|
||||
$prepared_item['card'] = [
|
||||
'brand' => $item['card']['brand'],
|
||||
'last4' => $item['card']['last4'],
|
||||
'exp_month' => $item['card']['exp_month'],
|
||||
'exp_year' => $item['card']['exp_year'],
|
||||
];
|
||||
} elseif ( array_key_exists( 'sepa_debit', $item ) ) {
|
||||
$prepared_item['sepa_debit'] = [
|
||||
'last4' => $item['sepa_debit']['last4'],
|
||||
];
|
||||
} elseif ( array_key_exists( 'link', $item ) ) {
|
||||
$prepared_item['link'] = [
|
||||
'email' => $item['link']['email'],
|
||||
];
|
||||
}
|
||||
|
||||
$context = $request['context'] ?? 'view';
|
||||
$prepared_item = $this->add_additional_fields_to_object( $prepared_item, $request );
|
||||
$prepared_item = $this->filter_response_by_context( $prepared_item, $context );
|
||||
|
||||
return rest_ensure_response( $prepared_item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Item schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
return [
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'payment_method',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'ID for the payment method.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'type' => [
|
||||
'description' => __( 'Type of the payment method.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'enum' => [ 'card', 'sepa_debit', 'link' ],
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'billing_details' => [
|
||||
'description' => __( 'Billing details for the payment method.', 'woocommerce-payments' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'properties' => [
|
||||
'address' => [
|
||||
'description' => __( 'Address associated with the billing details.', 'woocommerce-payments' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'properties' => [
|
||||
'city' => [
|
||||
'description' => __( 'City of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'country' => [
|
||||
'description' => __( 'Country of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'line1' => [
|
||||
'description' => __( 'Line 1 of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'line2' => [
|
||||
'description' => __( 'Line 2 of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'postal_code' => [
|
||||
'description' => __( 'Postal code of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'state' => [
|
||||
'description' => __( 'State of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'email' => [
|
||||
'description' => __( 'Email associated with the billing details.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'Name associated with the billing details.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'phone' => [
|
||||
'description' => __( 'Phone number associated with the billing details.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'card' => [
|
||||
'description' => __( 'Card details for the payment method.', 'woocommerce-payments' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'properties' => [
|
||||
'brand' => [
|
||||
'description' => __( 'Brand of the card.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'last4' => [
|
||||
'description' => __( 'Last 4 digits of the card.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'exp_month' => [
|
||||
'description' => __( 'Expiration month of the card.', 'woocommerce-payments' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'exp_year' => [
|
||||
'description' => __( 'Expiration year of the card.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'sepa_debit' => [
|
||||
'description' => __( 'SEPA Debit details for the payment method.', 'woocommerce-payments' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'properties' => [
|
||||
'last4' => [
|
||||
'description' => __( 'Last 4 digits of the SEPA Debit.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'link' => [
|
||||
'description' => __( 'Link details for the payment method.', 'woocommerce-payments' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'properties' => [
|
||||
'email' => [
|
||||
'description' => __( 'Email associated with the link.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Deposits_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request;
|
||||
use WCPay\Core\Server\Request\List_Deposits;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for deposits.
|
||||
*/
|
||||
class WC_REST_Payments_Deposits_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/deposits';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_deposits' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/summary',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_deposits_summary' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/download',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_deposits_export' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<deposit_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_deposit' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/overview-all',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_all_deposits_overviews' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'manual_deposit' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve deposits to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_deposits( $request ) {
|
||||
$wcpay_request = List_Deposits::from_rest_request( $request );
|
||||
|
||||
return $wcpay_request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve deposits summary to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_deposits_summary( $request ) {
|
||||
$filters = $this->get_deposits_filters( $request );
|
||||
return $this->forward_request( 'get_deposits_summary', [ $filters ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an overview of all deposits from the API.
|
||||
*/
|
||||
public function get_all_deposits_overviews() {
|
||||
$request = Request::get( WC_Payments_API_Client::DEPOSITS_API . '/overview-all' );
|
||||
$request->assign_hook( 'wcpay_get_all_deposits_overviews' );
|
||||
return $request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve deposit to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_deposit( $request ) {
|
||||
$deposit_id = $request->get_param( 'deposit_id' );
|
||||
$wcpay_request = Request::get( WC_Payments_API_Client::DEPOSITS_API, $deposit_id );
|
||||
$wcpay_request->assign_hook( 'wcpay_get_deposit' );
|
||||
return $wcpay_request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate deposits export via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_deposits_export( $request ) {
|
||||
$user_email = $request->get_param( 'user_email' );
|
||||
$locale = $request->get_param( 'locale' );
|
||||
$filters = $this->get_deposits_filters( $request );
|
||||
|
||||
return $this->forward_request( 'get_deposits_export', [ $filters, $user_email, $locale ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract deposits filters from request
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
private function get_deposits_filters( $request ) {
|
||||
return array_filter(
|
||||
[
|
||||
'match' => $request->get_param( 'match' ),
|
||||
'store_currency_is' => $request->get_param( 'store_currency_is' ),
|
||||
'date_before' => $request->get_param( 'date_before' ),
|
||||
'date_after' => $request->get_param( 'date_after' ),
|
||||
'date_between' => $request->get_param( 'date_between' ),
|
||||
'status_is' => $request->get_param( 'status_is' ),
|
||||
'status_is_not' => $request->get_param( 'status_is_not' ),
|
||||
],
|
||||
static function ( $filter ) {
|
||||
return null !== $filter;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a manual deposit.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function manual_deposit( $request ) {
|
||||
$params = $request->get_params();
|
||||
return $this->forward_request( 'manual_deposit', [ $params['type'], $params['currency'] ] );
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Disputes_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\List_Disputes;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for disputes.
|
||||
*/
|
||||
class WC_REST_Payments_Disputes_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/disputes';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_disputes' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/summary',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_disputes_summary' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/download',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_disputes_export' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<dispute_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_dispute' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<dispute_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'update_dispute' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<dispute_id>\w+)/close',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'close_dispute' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve disputes to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_disputes( WP_REST_Request $request ) {
|
||||
$wcpay_request = List_Disputes::from_rest_request( $request );
|
||||
|
||||
return $wcpay_request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions summary to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_disputes_summary( WP_REST_Request $request ) {
|
||||
$filters = $this->get_disputes_filters( $request );
|
||||
return $this->forward_request( 'get_disputes_summary', [ $filters ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve dispute to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_dispute( $request ) {
|
||||
$dispute_id = $request->get_param( 'dispute_id' );
|
||||
return $this->forward_request( 'get_dispute', [ $dispute_id ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update and respond with dispute via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function update_dispute( $request ) {
|
||||
$params = $request->get_params();
|
||||
return $this->forward_request(
|
||||
'update_dispute',
|
||||
[
|
||||
$params['dispute_id'],
|
||||
$params['evidence'],
|
||||
$params['submit'],
|
||||
$params['metadata'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close and respond with dispute via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function close_dispute( $request ) {
|
||||
$dispute_id = $request->get_param( 'dispute_id' );
|
||||
return $this->forward_request( 'close_dispute', [ $dispute_id ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate disputes export via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_disputes_export( $request ) {
|
||||
$user_email = $request->get_param( 'user_email' );
|
||||
$locale = $request->get_param( 'locale' );
|
||||
$filters = $this->get_disputes_filters( $request );
|
||||
|
||||
return $this->forward_request( 'get_disputes_export', [ $filters, $user_email, $locale ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract disputes filters from request
|
||||
* The reason to map the filter properties is to keep consitency between the front-end models.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
private function get_disputes_filters( $request ) {
|
||||
return array_filter(
|
||||
[
|
||||
'match' => $request->get_param( 'match' ),
|
||||
'currency_is' => $request->get_param( 'store_currency_is' ),
|
||||
'created_before' => $request->get_param( 'date_before' ),
|
||||
'created_after' => $request->get_param( 'date_after' ),
|
||||
'created_between' => $request->get_param( 'date_between' ),
|
||||
'search' => $request->get_param( 'search' ),
|
||||
'status_is' => $request->get_param( 'status_is' ),
|
||||
'status_is_not' => $request->get_param( 'status_is_not' ),
|
||||
],
|
||||
static function ( $filter ) {
|
||||
return null !== $filter;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Documents_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\List_Documents;
|
||||
use WCPay\Exceptions\API_Exception;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for documents.
|
||||
*/
|
||||
class WC_REST_Payments_Documents_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/documents';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_documents' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/summary',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_documents_summary' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<document_id>[\w-]+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_document' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_documents( $request ) {
|
||||
$wcpay_request = List_Documents::from_rest_request( $request );
|
||||
return $wcpay_request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents summary to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_documents_summary( $request ) {
|
||||
$filters = $this->get_documents_filters( $request );
|
||||
return $this->forward_request( 'get_documents_summary', [ $filters ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and serve a document for API requests.
|
||||
* This method serves the document directly and halts execution, skipping the REST return
|
||||
* and preventing additional data to be sent.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_document( $request ) {
|
||||
$response = [];
|
||||
$document_id = $request->get_param( 'document_id' );
|
||||
|
||||
try {
|
||||
$response = $this->api_client->get_document( $document_id );
|
||||
} catch ( API_Exception $e ) {
|
||||
$message = sprintf(
|
||||
/* translators: %1: The document ID. %2: The error message.*/
|
||||
esc_html__( 'There was an error accessing document %1$s. %2$s', 'woocommerce-payments' ),
|
||||
$document_id,
|
||||
$e->getMessage()
|
||||
);
|
||||
wp_die( esc_html( $message ), '', (int) $e->get_http_code() );
|
||||
}
|
||||
|
||||
// WooCommerce core only includes Tracks in admin, not the REST API, so we need to use this wc_admin method
|
||||
// that includes WC_Tracks in case it's not loaded.
|
||||
if ( function_exists( 'wc_admin_record_tracks_event' ) ) {
|
||||
wc_admin_record_tracks_event(
|
||||
'wcpay_document_downloaded',
|
||||
[
|
||||
'document_id' => $document_id,
|
||||
'mode' => WC_Payments::mode()->is_test() ? 'test' : 'live',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Set the headers to match what was returned from the server.
|
||||
if ( ! headers_sent() ) {
|
||||
nocache_headers();
|
||||
status_header( $response['response']['code'], $response['response']['message'] ?? '' );
|
||||
header( 'Content-Type: ' . $response['headers']['content-type'] );
|
||||
header( 'Content-Disposition: ' . $response['headers']['content-disposition'] ?? '' );
|
||||
}
|
||||
|
||||
// We should output the server's file without escaping.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $response['body'];
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract documents filters from request
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
private function get_documents_filters( $request ) {
|
||||
return array_filter(
|
||||
[
|
||||
'match' => $request->get_param( 'match' ),
|
||||
'date_before' => $request->get_param( 'date_before' ),
|
||||
'date_after' => $request->get_param( 'date_after' ),
|
||||
'date_between' => $request->get_param( 'date_between' ),
|
||||
'type_is' => $request->get_param( 'type_is' ),
|
||||
'type_is_not' => $request->get_param( 'type_is_not' ),
|
||||
],
|
||||
static function ( $filter ) {
|
||||
return null !== $filter;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Files_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for files.
|
||||
*/
|
||||
class WC_REST_Payments_Files_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/file';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'upload_file' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<file_id>\w+)/details',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_file_detail' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<file_id>\w+)/content',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_file_content' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<file_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_file' ],
|
||||
'permission_callback' => [],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create file and respond with file object via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function upload_file( $request ) {
|
||||
return $this->forward_request( 'upload_file', [ $request ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a file content via API.
|
||||
*
|
||||
* @param WP_REST_Request $request - request object.
|
||||
*
|
||||
* @return WP_Error|WP_HTTP_Response
|
||||
*/
|
||||
public function get_file( WP_REST_Request $request ) {
|
||||
$file_id = $request->get_param( 'file_id' );
|
||||
$as_account = (bool) $request->get_param( 'as_account' );
|
||||
|
||||
$file_service = new WC_Payments_File_Service();
|
||||
$purpose = get_transient( WC_Payments_File_Service::CACHE_KEY_PREFIX_PURPOSE . $file_id . '_' . ( $as_account ? '1' : '0' ) );
|
||||
|
||||
if ( ! $purpose ) {
|
||||
$file = $this->forward_request( 'get_file', [ $file_id, $as_account ] );
|
||||
|
||||
if ( is_wp_error( $file ) ) {
|
||||
return $this->file_error_response( $file );
|
||||
}
|
||||
$purpose = $file->get_data()['purpose'];
|
||||
set_transient( WC_Payments_File_Service::CACHE_KEY_PREFIX_PURPOSE . $file_id, $purpose, WC_Payments_File_Service::CACHE_PERIOD );
|
||||
}
|
||||
|
||||
if ( ! $file_service->is_file_public( $purpose ) && ! $this->check_permission() ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__( 'Sorry, you are not allowed to do that.', 'woocommerce-payments' ),
|
||||
[ 'status' => rest_authorization_required_code() ]
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->forward_request( 'get_file_contents', [ $file_id, $as_account ] );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->file_error_response( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* WP_REST_Server will convert the response data to JSON prior to output it.
|
||||
* Using this filter to prevent it, and output the data from WP_HTTP_Response instead.
|
||||
*/
|
||||
add_filter(
|
||||
'rest_pre_serve_request',
|
||||
function ( bool $served, WP_HTTP_Response $response ): bool {
|
||||
echo $response->get_data(); // @codingStandardsIgnoreLine
|
||||
return true;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
return new WP_HTTP_Response(
|
||||
base64_decode( $result->get_data()['file_content'] ), // @codingStandardsIgnoreLine
|
||||
200,
|
||||
[
|
||||
'Content-Type' => $result->get_data()['content_type'],
|
||||
'Content-Disposition' => 'inline',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve file details via the API.
|
||||
*
|
||||
* Example response:
|
||||
* {
|
||||
* "id": "file_1Np1S5J5cIRIG92xknlr0iND",
|
||||
* "object": "file",
|
||||
* "created": 1694405421,
|
||||
* "expires_at": 1717733421,
|
||||
* "filename": "Screenshot 2023-09-04 at 5.08.31\u202fPM.png",
|
||||
* "purpose": "dispute_evidence",
|
||||
* "size": 21444,
|
||||
* "title": null,
|
||||
* "type": "png",
|
||||
* }
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
public function get_file_detail( WP_REST_Request $request ) {
|
||||
$file_id = $request->get_param( 'file_id' );
|
||||
$as_account = (bool) $request->get_param( 'as_account' );
|
||||
|
||||
return $this->forward_request( 'get_file', [ $file_id, $as_account ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve file contents via the API as a base64 encoded string.
|
||||
*
|
||||
* Example response:
|
||||
* {
|
||||
* "content_type": "image\/png",
|
||||
* "file_content": "iVBORw.......",
|
||||
* }
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
public function get_file_content( WP_REST_Request $request ) {
|
||||
$file_id = $request->get_param( 'file_id' );
|
||||
$as_account = (bool) $request->get_param( 'as_account' );
|
||||
|
||||
return $this->forward_request( 'get_file_contents', [ $file_id, $as_account ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert error response
|
||||
*
|
||||
* @param WP_Error $error - error.
|
||||
*
|
||||
* @return WP_Error
|
||||
*/
|
||||
private function file_error_response( WP_Error $error ): WP_Error {
|
||||
$error_status_code = 'resource_missing' === $error->get_error_code() ? WP_Http::NOT_FOUND : WP_Http::INTERNAL_SERVER_ERROR;
|
||||
return new WP_Error(
|
||||
$error->get_error_code(),
|
||||
$error->get_error_message(),
|
||||
[ 'status' => $error_status_code ]
|
||||
);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Orders_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for order processing.
|
||||
*/
|
||||
class WC_REST_Payments_Fraud_Outcomes_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/fraud_outcomes';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<id>\w+)/latest',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_latest_fraud_outcome' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve charge to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_latest_fraud_outcome( $request ) {
|
||||
$id = $request->get_param( 'id' );
|
||||
|
||||
return $this->forward_request( 'get_latest_fraud_outcome', [ $id ] );
|
||||
}
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Onboarding_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Logger;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for account details and status.
|
||||
*/
|
||||
class WC_REST_Payments_Onboarding_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
const RESULT_BAD_REQUEST = 'bad_request';
|
||||
|
||||
/**
|
||||
* Onboarding Service.
|
||||
*
|
||||
* @var WC_Payments_Onboarding_Service
|
||||
*/
|
||||
protected $onboarding_service;
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/onboarding';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param WC_Payments_API_Client $api_client WooCommerce Payments API client.
|
||||
* @param WC_Payments_Onboarding_Service $onboarding_service Onboarding Service class instance.
|
||||
*/
|
||||
public function __construct(
|
||||
WC_Payments_API_Client $api_client,
|
||||
WC_Payments_Onboarding_Service $onboarding_service
|
||||
) {
|
||||
parent::__construct( $api_client );
|
||||
$this->onboarding_service = $onboarding_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/kyc/session',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_embedded_kyc_session' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'progressive' => [
|
||||
'required' => false,
|
||||
'description' => 'Whether the session is for progressive onboarding.',
|
||||
'type' => 'string',
|
||||
],
|
||||
'self_assessment' => [
|
||||
'required' => false,
|
||||
'description' => 'The self-assessment data.',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'country' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The country code where the company is legally registered.',
|
||||
'required' => true,
|
||||
],
|
||||
'business_type' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The company incorporation type.',
|
||||
'required' => true,
|
||||
],
|
||||
'mcc' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The merchant category code. This can either be a true MCC or an MCCs tree item id from the onboarding form.',
|
||||
'required' => true,
|
||||
],
|
||||
'annual_revenue' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The estimated annual revenue bucket id.',
|
||||
'required' => true,
|
||||
],
|
||||
'go_live_timeframe' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The timeframe bucket for the estimated first live transaction.',
|
||||
'required' => true,
|
||||
],
|
||||
'url' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The URL of the store.',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/kyc/finalize',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'finalize_embedded_kyc' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'source' => [
|
||||
'required' => false,
|
||||
'description' => 'The very first entry point the merchant entered our onboarding flow.',
|
||||
'type' => 'string',
|
||||
],
|
||||
'from' => [
|
||||
'required' => false,
|
||||
'description' => 'The previous step in the onboarding flow leading the merchant to arrive at the current step.',
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/business_types',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_business_types' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/router/po_eligible',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'args' => [
|
||||
'business' => [
|
||||
'required' => true,
|
||||
'description' => 'The context about the merchant\'s business (self-assessment data).',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'country' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The country code where the company is legally registered.',
|
||||
'required' => true,
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The company incorporation type.',
|
||||
'required' => true,
|
||||
],
|
||||
'mcc' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The merchant category code. This can either be a true MCC or an MCCs tree item id from the onboarding form.',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'store' => [
|
||||
'required' => true,
|
||||
'description' => 'The context about the merchant\'s store (self-assessment data).',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'annual_revenue' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The estimated annual revenue bucket id.',
|
||||
'required' => true,
|
||||
],
|
||||
'go_live_timeframe' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The timeframe bucket for the estimated first live transaction.',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'woo_store_stats' => [
|
||||
'required' => false,
|
||||
'description' => 'Context about the merchant\'s current WooCommerce store.',
|
||||
'type' => 'object',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'get_progressive_onboarding_eligible' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an account embedded KYC session via the API.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function get_embedded_kyc_session( WP_REST_Request $request ) {
|
||||
$self_assessment_data = ! empty( $request->get_param( 'self_assessment' ) ) ? wc_clean( wp_unslash( $request->get_param( 'self_assessment' ) ) ) : [];
|
||||
$progressive = ! empty( $request->get_param( 'progressive' ) ) && 'true' === $request->get_param( 'progressive' );
|
||||
|
||||
$account_session = $this->onboarding_service->create_embedded_kyc_session(
|
||||
$self_assessment_data,
|
||||
$progressive
|
||||
);
|
||||
|
||||
if ( $account_session ) {
|
||||
$account_session['locale'] = get_user_locale();
|
||||
}
|
||||
|
||||
return rest_ensure_response( $account_session );
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize the embedded KYC session via the API.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function finalize_embedded_kyc( WP_REST_Request $request ) {
|
||||
$source = $request->get_param( 'source' ) ?? '';
|
||||
$from = $request->get_param( 'from' ) ?? '';
|
||||
$actioned_notes = WC_Payments_Onboarding_Service::get_actioned_notes();
|
||||
|
||||
// Call the API to finalize the onboarding.
|
||||
try {
|
||||
$response = $this->onboarding_service->finalize_embedded_kyc(
|
||||
get_user_locale(),
|
||||
$source,
|
||||
$actioned_notes
|
||||
);
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( self::RESULT_BAD_REQUEST, $e->getMessage(), [ 'status' => 400 ] );
|
||||
}
|
||||
|
||||
// Handle some post-onboarding tasks and get the redirect params.
|
||||
$finalize = WC_Payments::get_account_service()->finalize_embedded_connection(
|
||||
$response['mode'],
|
||||
[
|
||||
'promo' => $response['promotion_id'] ?? '',
|
||||
'from' => $from,
|
||||
'source' => $source,
|
||||
]
|
||||
);
|
||||
|
||||
// Return the response, the client will handle the redirect.
|
||||
return rest_ensure_response(
|
||||
array_merge(
|
||||
$response,
|
||||
$finalize
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get business types via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_business_types( WP_REST_Request $request ) {
|
||||
$business_types = $this->onboarding_service->get_cached_business_types();
|
||||
return rest_ensure_response( [ 'data' => $business_types ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progressive onboarding eligibility via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_progressive_onboarding_eligible( WP_REST_Request $request ) {
|
||||
return $this->forward_request(
|
||||
'get_onboarding_po_eligible',
|
||||
[
|
||||
'business_info' => $request->get_param( 'business' ),
|
||||
'store_info' => $request->get_param( 'store' ),
|
||||
'woo_store_stats' => $request->get_param( 'woo_store_stats' ) ?? [],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
+591
@@ -0,0 +1,591 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Orders_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use WCPay\Core\Server\Request\Create_Intention;
|
||||
use WCPay\Core\Server\Request\Get_Intention;
|
||||
use WCPay\Logger;
|
||||
use WCPay\Constants\Order_Status;
|
||||
use WCPay\Constants\Intent_Status;
|
||||
use WCPay\Constants\Payment_Method;
|
||||
|
||||
/**
|
||||
* REST controller for order processing.
|
||||
*/
|
||||
class WC_REST_Payments_Orders_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/orders';
|
||||
|
||||
/**
|
||||
* Instance of WC_Payment_Gateway_WCPay
|
||||
*
|
||||
* @var WC_Payment_Gateway_WCPay
|
||||
*/
|
||||
private $gateway;
|
||||
|
||||
/**
|
||||
* WC_Payments_Customer_Service instance for working with customer information
|
||||
*
|
||||
* @var WC_Payments_Customer_Service
|
||||
*/
|
||||
private $customer_service;
|
||||
|
||||
/**
|
||||
* WC_Payments_Order_Service instance for updating order statuses.
|
||||
*
|
||||
* @var WC_Payments_Order_Service
|
||||
*/
|
||||
private $order_service;
|
||||
|
||||
/**
|
||||
* WC_Payments_REST_Controller constructor.
|
||||
*
|
||||
* @param WC_Payments_API_Client $api_client WooCommerce Payments API client.
|
||||
* @param WC_Payment_Gateway_WCPay $gateway WooCommerce Payments payment gateway.
|
||||
* @param WC_Payments_Customer_Service $customer_service Customer class instance.
|
||||
* @param WC_Payments_Order_Service $order_service Order Service class instance.
|
||||
*/
|
||||
public function __construct( WC_Payments_API_Client $api_client, WC_Payment_Gateway_WCPay $gateway, WC_Payments_Customer_Service $customer_service, WC_Payments_Order_Service $order_service ) {
|
||||
parent::__construct( $api_client );
|
||||
$this->gateway = $gateway;
|
||||
$this->customer_service = $customer_service;
|
||||
$this->order_service = $order_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
$this->rest_base . '/(?P<order_id>\w+)/capture_terminal_payment',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'capture_terminal_payment' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'payment_intent_id' => [
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
$this->rest_base . '/(?P<order_id>\w+)/capture_authorization',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'capture_authorization' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'payment_intent_id' => [
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
$this->rest_base . '/(?P<order_id>\w+)/cancel_authorization',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'cancel_authorization' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'payment_intent_id' => [
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
$this->rest_base . '/(?P<order_id>\w+)/create_terminal_intent',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'create_terminal_intent' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
$this->rest_base . '/(?P<order_id>\d+)/create_customer',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'create_customer' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an intent ID and an order ID, add the intent ID to the order and capture it.
|
||||
* Use-cases: Mobile apps using it for `card_present` and `interac_present` payment types.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function capture_terminal_payment( WP_REST_Request $request ) {
|
||||
try {
|
||||
$intent_id = $request['payment_intent_id'];
|
||||
$order_id = $request['order_id'];
|
||||
|
||||
// Do not process non-existing orders.
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( false === $order ) {
|
||||
return new WP_Error( 'wcpay_missing_order', __( 'Order not found', 'woocommerce-payments' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
// Do not process orders with refund(s).
|
||||
if ( 0 < $order->get_total_refunded() ) {
|
||||
return new WP_Error(
|
||||
'wcpay_refunded_order_uncapturable',
|
||||
__( 'Payment cannot be captured for partially or fully refunded orders.', 'woocommerce-payments' ),
|
||||
[ 'status' => 400 ]
|
||||
);
|
||||
}
|
||||
|
||||
// Do not process already processed orders to prevent double-charging.
|
||||
$processed_order_intent_statuses = [
|
||||
Intent_Status::SUCCEEDED,
|
||||
Intent_Status::CANCELED,
|
||||
Intent_Status::PROCESSING,
|
||||
];
|
||||
$stored_intent_id = $order->get_meta( WC_Payments_Order_Service::INTENT_ID_META_KEY );
|
||||
$stored_intent_status = $order->get_meta( WC_Payments_Order_Service::INTENTION_STATUS_META_KEY );
|
||||
if (
|
||||
in_array( $stored_intent_status, $processed_order_intent_statuses, true ) ||
|
||||
( $stored_intent_id && $stored_intent_id !== $intent_id )
|
||||
) {
|
||||
return new WP_Error( 'wcpay_payment_uncapturable', __( 'The payment cannot be captured for completed or processed orders.', 'woocommerce-payments' ), [ 'status' => 409 ] );
|
||||
}
|
||||
|
||||
// Do not process intents that can't be captured.
|
||||
$request = Get_Intention::create( $intent_id );
|
||||
$request->set_hook_args( $order );
|
||||
$intent = $request->send();
|
||||
|
||||
$intent_metadata = is_array( $intent->get_metadata() ) ? $intent->get_metadata() : [];
|
||||
$intent_meta_order_id_raw = $intent_metadata['order_id'] ?? '';
|
||||
$intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0;
|
||||
if ( $intent_meta_order_id !== $order->get_id() ) {
|
||||
Logger::error( 'Payment capture rejected due to failed validation: order id on intent is incorrect or missing.' );
|
||||
return new WP_Error( 'wcpay_intent_order_mismatch', __( 'The payment cannot be captured', 'woocommerce-payments' ), [ 'status' => 409 ] );
|
||||
}
|
||||
if ( ! $intent->is_authorized() ) {
|
||||
return new WP_Error( 'wcpay_payment_uncapturable', __( 'The payment cannot be captured', 'woocommerce-payments' ), [ 'status' => 409 ] );
|
||||
}
|
||||
|
||||
// Update the order: set the payment method and attach intent attributes.
|
||||
$order->set_payment_method( WC_Payment_Gateway_WCPay::GATEWAY_ID );
|
||||
$order->set_payment_method_title( __( 'WooCommerce In-Person Payments', 'woocommerce-payments' ) );
|
||||
$this->order_service->attach_intent_info_to_order( $order, $intent );
|
||||
$this->order_service->update_order_status_from_intent( $order, $intent );
|
||||
|
||||
// Certain payments (eg. Interac) are captured on the client-side (mobile app).
|
||||
// The client may send us the captured intent to link it to its WC order.
|
||||
// Doing so via this endpoint is more reliable than depending on the payment_intent.succeeded event.
|
||||
$is_intent_captured = Intent_Status::SUCCEEDED === $intent->get_status();
|
||||
$result_for_captured_intent = [
|
||||
'status' => Intent_Status::SUCCEEDED,
|
||||
'id' => $intent->get_id(),
|
||||
];
|
||||
|
||||
$result = $is_intent_captured ? $result_for_captured_intent : $this->gateway->capture_charge( $order, false, $intent_metadata );
|
||||
|
||||
if ( Intent_Status::SUCCEEDED !== $result['status'] ) {
|
||||
$http_code = $result['http_code'] ?? 502;
|
||||
$error_code = $result['error_code'] ?? null;
|
||||
$extra_details = $result['extra_details'] ?? [];
|
||||
return new WP_Error(
|
||||
'wcpay_capture_error',
|
||||
sprintf(
|
||||
// translators: %s: the error message.
|
||||
__( 'Payment capture failed to complete with the following message: %s', 'woocommerce-payments' ),
|
||||
$result['message'] ?? __( 'Unknown error', 'woocommerce-payments' )
|
||||
),
|
||||
[
|
||||
'status' => $http_code,
|
||||
'extra_details' => $extra_details,
|
||||
'error_type' => $error_code,
|
||||
]
|
||||
);
|
||||
}
|
||||
// Store receipt generation URL for mobile applications in order meta-data.
|
||||
$order->add_meta_data( 'receipt_url', get_rest_url( null, sprintf( '%s/payments/readers/receipts/%s', $this->namespace, $intent->get_id() ) ) );
|
||||
|
||||
// Add payment method for future subscription payments.
|
||||
$generated_card = $intent->get_charge()->get_payment_method_details()[ Payment_Method::CARD_PRESENT ]['generated_card'] ?? null;
|
||||
// If we don't get a generated card, e.g. because a digital wallet was used, we can still return that the initial payment was successful.
|
||||
// The subscription will not be activated and customers will need to provide a new payment method for renewals.
|
||||
if ( $generated_card ) {
|
||||
$has_subscriptions = function_exists( 'wcs_order_contains_subscription' ) &&
|
||||
function_exists( 'wcs_get_subscriptions_for_order' ) &&
|
||||
function_exists( 'wcs_is_manual_renewal_required' ) &&
|
||||
wcs_order_contains_subscription( $order_id );
|
||||
if ( $has_subscriptions ) {
|
||||
$token = WC_Payments::get_token_service()->add_payment_method_to_user( $generated_card, $order->get_user() );
|
||||
$this->gateway->add_token_to_order( $order, $token );
|
||||
foreach ( wcs_get_subscriptions_for_order( $order ) as $subscription ) {
|
||||
$subscription->set_payment_method( WC_Payment_Gateway_WCPay::GATEWAY_ID );
|
||||
// Where the setting doesn't force manual renewals, we should turn them off, because we have an auto-renewal token now.
|
||||
if ( ! wcs_is_manual_renewal_required() ) {
|
||||
$subscription->set_requires_manual_renewal( false );
|
||||
}
|
||||
$subscription->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualize order status.
|
||||
$this->order_service->mark_terminal_payment_completed( $order, $intent_id, $result['status'] );
|
||||
|
||||
return rest_ensure_response(
|
||||
[
|
||||
'status' => $result['status'],
|
||||
'id' => $result['id'],
|
||||
]
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
Logger::error( 'Failed to capture a terminal payment via REST API: ' . $e );
|
||||
return new WP_Error( 'wcpay_server_error', __( 'Unexpected server error', 'woocommerce-payments' ), [ 'status' => 500 ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures an authorization.
|
||||
* Use-cases: Merchants manually capturing a payment when they enable "capture later" option.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function capture_authorization( WP_REST_Request $request ) {
|
||||
try {
|
||||
$intent_id = $request['payment_intent_id'];
|
||||
$order_id = $request['order_id'];
|
||||
|
||||
// Do not process non-existing orders.
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( false === $order ) {
|
||||
return new WP_Error( 'wcpay_missing_order', __( 'Order not found', 'woocommerce-payments' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
// Do not process orders with refund(s).
|
||||
if ( 0 < $order->get_total_refunded() ) {
|
||||
return new WP_Error(
|
||||
'wcpay_refunded_order_uncapturable',
|
||||
__( 'Payment cannot be captured for partially or fully refunded orders.', 'woocommerce-payments' ),
|
||||
[ 'status' => 400 ]
|
||||
);
|
||||
}
|
||||
|
||||
// Do not process intents that can't be captured.
|
||||
$request = Get_Intention::create( $intent_id );
|
||||
$request->set_hook_args( $order );
|
||||
$intent = $request->send();
|
||||
|
||||
$intent_metadata = is_array( $intent->get_metadata() ) ? $intent->get_metadata() : [];
|
||||
$intent_meta_order_id_raw = $intent_metadata['order_id'] ?? '';
|
||||
$intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0;
|
||||
if ( $intent_meta_order_id !== $order->get_id() ) {
|
||||
Logger::error( 'Payment capture rejected due to failed validation: order id on intent is incorrect or missing.' );
|
||||
return new WP_Error( 'wcpay_intent_order_mismatch', __( 'The payment cannot be captured', 'woocommerce-payments' ), [ 'status' => 409 ] );
|
||||
}
|
||||
if ( ! $intent->is_authorized() ) {
|
||||
return new WP_Error( 'wcpay_payment_uncapturable', __( 'The payment cannot be captured', 'woocommerce-payments' ), [ 'status' => 409 ] );
|
||||
}
|
||||
|
||||
$this->add_fraud_outcome_manual_entry( $order, 'approve' );
|
||||
|
||||
$result = $this->gateway->capture_charge( $order, true, $intent_metadata );
|
||||
|
||||
if ( Intent_Status::SUCCEEDED !== $result['status'] ) {
|
||||
$error_code = $result['error_code'] ?? null;
|
||||
$extra_details = $result['extra_details'] ?? [];
|
||||
return new WP_Error(
|
||||
'wcpay_capture_error',
|
||||
sprintf(
|
||||
// translators: %s: the error message.
|
||||
__( 'Payment capture failed to complete with the following message: %s', 'woocommerce-payments' ),
|
||||
$result['message'] ?? __( 'Unknown error', 'woocommerce-payments' )
|
||||
),
|
||||
[
|
||||
'status' => $result['http_code'] ?? 502,
|
||||
'extra_details' => $extra_details,
|
||||
'error_type' => $error_code,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$order->save_meta_data();
|
||||
|
||||
return rest_ensure_response(
|
||||
[
|
||||
'status' => $result['status'],
|
||||
'id' => $result['id'],
|
||||
]
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
Logger::error( 'Failed to capture an authorization via REST API: ' . $e );
|
||||
return new WP_Error( 'wcpay_server_error', __( 'Unexpected server error', 'woocommerce-payments' ), [ 'status' => 500 ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns customer id from order. Create or update customer if needed.
|
||||
* Use-cases:
|
||||
* - It was used by older versions of our mobile apps to add the customer details to Payment Intents.
|
||||
* - It is used by the apps to set customer details on Payment Intents for an order containing subscriptions. Required for capturing renewal payments off session.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function create_customer( $request ) {
|
||||
try {
|
||||
$order_id = $request['order_id'];
|
||||
|
||||
// Do not process non-existing orders.
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( false === $order || ! ( $order instanceof WC_Order ) ) {
|
||||
return new WP_Error( 'wcpay_missing_order', __( 'Order not found', 'woocommerce-payments' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
$disallowed_order_statuses = apply_filters(
|
||||
'wcpay_create_customer_disallowed_order_statuses',
|
||||
[
|
||||
Order_Status::COMPLETED,
|
||||
Order_Status::CANCELLED,
|
||||
Order_Status::REFUNDED,
|
||||
Order_Status::FAILED,
|
||||
]
|
||||
);
|
||||
if ( $order->has_status( $disallowed_order_statuses ) ) {
|
||||
return new WP_Error( 'wcpay_invalid_order_status', __( 'Invalid order status', 'woocommerce-payments' ), [ 'status' => 400 ] );
|
||||
}
|
||||
|
||||
$order_user = $order->get_user();
|
||||
$customer_id = $this->order_service->get_customer_id_for_order( $order );
|
||||
$customer_data = WC_Payments_Customer_Service::map_customer_data( $order );
|
||||
$is_guest_customer = false === $order_user;
|
||||
|
||||
// If the order is created for a registered customer, try extracting it's Stripe customer ID.
|
||||
if ( ! $customer_id && ! $is_guest_customer ) {
|
||||
$customer_id = $this->customer_service->get_customer_id_by_user_id( $order_user->ID );
|
||||
}
|
||||
|
||||
$order_user = $is_guest_customer ? new WP_User() : $order_user;
|
||||
$customer_id = $customer_id
|
||||
? $this->customer_service->update_customer_for_user( $customer_id, $order_user, $customer_data )
|
||||
: $this->customer_service->create_customer_for_user( $order_user, $customer_data );
|
||||
|
||||
$this->order_service->set_customer_id_for_order( $order, $customer_id );
|
||||
$order->save();
|
||||
|
||||
return rest_ensure_response(
|
||||
[
|
||||
'id' => $customer_id,
|
||||
]
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
Logger::error( 'Failed to create / update customer from order via REST API: ' . $e );
|
||||
return new WP_Error( 'wcpay_server_error', __( 'Unexpected server error', 'woocommerce-payments' ), [ 'status' => 500 ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new in-person payment intent for the given order ID without confirming it.
|
||||
* Use-cases: Mobile apps using it for `card_present` payment types. (`interac_present` is handled by the apps via Stripe SDK).
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function create_terminal_intent( $request ) {
|
||||
// Do not process non-existing orders.
|
||||
$order = wc_get_order( $request['order_id'] );
|
||||
if ( false === $order ) {
|
||||
return new WP_Error( 'wcpay_missing_order', __( 'Order not found', 'woocommerce-payments' ), [ 'status' => 404 ] );
|
||||
}
|
||||
try {
|
||||
$currency = strtolower( $order->get_currency() );
|
||||
$customer_id = $request->get_param( 'customer_id' );
|
||||
$metadata = $request->get_param( 'metadata' ) ?? [];
|
||||
$metadata['order_number'] = $order->get_order_number();
|
||||
|
||||
$wcpay_server_request = Create_Intention::create();
|
||||
$wcpay_server_request->set_currency_code( $currency );
|
||||
$wcpay_server_request->set_amount( WC_Payments_Utils::prepare_amount( $order->get_total(), $currency ) );
|
||||
if ( $customer_id ) {
|
||||
$wcpay_server_request->set_customer( $customer_id );
|
||||
}
|
||||
$wcpay_server_request->set_metadata( $metadata );
|
||||
$wcpay_server_request->set_payment_method_types( $this->get_terminal_intent_payment_method( $request ) );
|
||||
$wcpay_server_request->set_capture_method( 'manual' === $this->get_terminal_intent_capture_method( $request ) );
|
||||
$wcpay_server_request->set_hook_args( $order );
|
||||
$intent = $wcpay_server_request->send();
|
||||
|
||||
return rest_ensure_response(
|
||||
[
|
||||
'id' => ! empty( $intent ) ? $intent->get_id() : null,
|
||||
]
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
Logger::error( 'Failed to create an intention via REST API: ' . $e );
|
||||
return new WP_Error( 'wcpay_server_error', __( 'Unexpected server error', 'woocommerce-payments' ), [ 'status' => 500 ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return terminal intent payment method array based on payment methods request.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param array $default_value - default value.
|
||||
*
|
||||
* @return array|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function get_terminal_intent_payment_method( $request, array $default_value = [ Payment_Method::CARD_PRESENT ] ): array {
|
||||
$payment_methods = $request->get_param( 'payment_methods' );
|
||||
if ( null === $payment_methods ) {
|
||||
return $default_value;
|
||||
}
|
||||
|
||||
if ( ! is_array( $payment_methods ) ) {
|
||||
throw new \Exception( 'Invalid param \'payment_methods\'!' );
|
||||
}
|
||||
|
||||
foreach ( $payment_methods as $value ) {
|
||||
if ( ! in_array( $value, Payment_Method::IPP_ALLOWED_PAYMENT_METHODS, true ) ) {
|
||||
throw new \Exception( 'One or more payment methods are not supported!' );
|
||||
}
|
||||
}
|
||||
|
||||
return $payment_methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return terminal intent capture method based on capture method request.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param string $default_value default value.
|
||||
*
|
||||
* @return string|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function get_terminal_intent_capture_method( $request, string $default_value = 'manual' ): string {
|
||||
$capture_method = $request->get_param( 'capture_method' );
|
||||
if ( null === $capture_method ) {
|
||||
return $default_value;
|
||||
}
|
||||
|
||||
if ( ! in_array( $capture_method, [ 'manual', 'automatic' ], true ) ) {
|
||||
throw new \Exception( 'Invalid param \'capture_method\'!' );
|
||||
}
|
||||
|
||||
return $capture_method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels an authorization.
|
||||
* Use-cases: Merchants manually canceling when blocking an on hold review by Fraud & Risk tools.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function cancel_authorization( WP_REST_Request $request ) {
|
||||
try {
|
||||
$intent_id = $request['payment_intent_id'];
|
||||
$order_id = $request['order_id'];
|
||||
|
||||
// Do not process non-existing orders.
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( false === $order ) {
|
||||
return new WP_Error( 'wcpay_missing_order', __( 'Order not found', 'woocommerce-payments' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
// Do not process orders with refund(s).
|
||||
if ( 0 < $order->get_total_refunded() ) {
|
||||
return new WP_Error(
|
||||
'wcpay_refunded_order_uncapturable',
|
||||
__( 'Payment cannot be canceled for partially or fully refunded orders.', 'woocommerce-payments' ),
|
||||
[ 'status' => 400 ]
|
||||
);
|
||||
}
|
||||
|
||||
// Do not process intents that can't be canceled.
|
||||
$request = Get_Intention::create( $intent_id );
|
||||
$request->set_hook_args( $order );
|
||||
$intent = $request->send();
|
||||
|
||||
$intent_metadata = is_array( $intent->get_metadata() ) ? $intent->get_metadata() : [];
|
||||
$intent_meta_order_id_raw = $intent_metadata['order_id'] ?? '';
|
||||
$intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0;
|
||||
if ( $intent_meta_order_id !== $order->get_id() ) {
|
||||
Logger::error( 'Payment cancellation rejected due to failed validation: order id on intent is incorrect or missing.' );
|
||||
return new WP_Error( 'wcpay_intent_order_mismatch', __( 'The payment cannot be canceled', 'woocommerce-payments' ), [ 'status' => 409 ] );
|
||||
}
|
||||
if ( ! in_array( $intent->get_status(), [ Intent_Status::REQUIRES_CAPTURE ], true ) ) {
|
||||
return new WP_Error( 'wcpay_payment_uncapturable', __( 'The payment cannot be canceled', 'woocommerce-payments' ), [ 'status' => 409 ] );
|
||||
}
|
||||
|
||||
$this->add_fraud_outcome_manual_entry( $order, 'block' );
|
||||
|
||||
$result = $this->gateway->cancel_authorization( $order );
|
||||
|
||||
if ( Intent_Status::CANCELED !== $result['status'] ) {
|
||||
return new WP_Error(
|
||||
'wcpay_cancel_error',
|
||||
sprintf(
|
||||
// translators: %s: the error message.
|
||||
__( 'Payment cancel failed to complete with the following message: %s', 'woocommerce-payments' ),
|
||||
$result['message'] ?? __( 'Unknown error', 'woocommerce-payments' )
|
||||
),
|
||||
[ 'status' => $result['http_code'] ?? 502 ]
|
||||
);
|
||||
}
|
||||
|
||||
$order->save_meta_data();
|
||||
|
||||
return rest_ensure_response(
|
||||
[
|
||||
'status' => $result['status'],
|
||||
'id' => $result['id'],
|
||||
]
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
Logger::error( 'Failed to cancel an authorization via REST API: ' . $e );
|
||||
return new WP_Error( 'wcpay_server_error', __( 'Unexpected server error', 'woocommerce-payments' ), [ 'status' => 500 ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the fraud_outcome_manual_entry meta to the order.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string $action User action.
|
||||
*/
|
||||
private function add_fraud_outcome_manual_entry( $order, $action ) {
|
||||
$current_user = wp_get_current_user();
|
||||
$order->add_meta_data(
|
||||
'_wcpay_fraud_outcome_manual_entry',
|
||||
[
|
||||
'type' => 'fraud_outcome_manual_' . $action,
|
||||
'user' => [
|
||||
'id' => $current_user->ID,
|
||||
'username' => $current_user->user_login,
|
||||
],
|
||||
'action' => 'block' === $action ? 'blocked' : 'approved',
|
||||
'datetime' => time(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Payment_Intents_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\Create_And_Confirm_Intention;
|
||||
use WCPay\Logger;
|
||||
use WCPay\Exceptions\Rest_Request_Exception;
|
||||
use WCPay\Constants\Payment_Type;
|
||||
use WCPay\Internal\Service\Level3Service;
|
||||
use WCPay\Internal\Service\OrderService;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for charges.
|
||||
*/
|
||||
class WC_REST_Payments_Payment_Intents_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/payment_intents';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<payment_intent_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_payment_intent' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve charge to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_payment_intent( $request ) {
|
||||
$payment_intent_id = $request->get_param( 'payment_intent_id' );
|
||||
|
||||
return $this->forward_request( 'get_intent', [ $payment_intent_id ] );
|
||||
}
|
||||
}
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Payment_Intents_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\Create_And_Confirm_Intention;
|
||||
use WCPay\Logger;
|
||||
use WCPay\Exceptions\Rest_Request_Exception;
|
||||
use WCPay\Constants\Payment_Type;
|
||||
use WCPay\Internal\Service\Level3Service;
|
||||
use WCPay\Internal\Service\OrderService;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for charges.
|
||||
*/
|
||||
class WC_REST_Payments_Payment_Intents_Create_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Instance of WC_Payment_Gateway_WCPay
|
||||
*
|
||||
* @var WC_Payment_Gateway_WCPay
|
||||
*/
|
||||
private $gateway;
|
||||
|
||||
/**
|
||||
* Order service instance.
|
||||
*
|
||||
* @var OrderService
|
||||
*/
|
||||
private $order_service;
|
||||
|
||||
/**
|
||||
* Level3 service instance.
|
||||
*
|
||||
* @var Level3Service
|
||||
*/
|
||||
private $level3_service;
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/payment_intents';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'create_payment_intent' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'schema' => [ $this, 'get_item_schema' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_REST_Payments_Payment_Intents_Create_Controller constructor.
|
||||
*
|
||||
* @param WC_Payments_API_Client $api_client WooCommerce Payments API client.
|
||||
* @param WC_Payment_Gateway_WCPay $gateway WooCommerce Payments payment gateway.
|
||||
* @param OrderService $order_service The new order servie.
|
||||
* @param Level3Service $level3_service Level3 service instance.
|
||||
*/
|
||||
public function __construct(
|
||||
WC_Payments_API_Client $api_client,
|
||||
WC_Payment_Gateway_WCPay $gateway,
|
||||
OrderService $order_service,
|
||||
Level3Service $level3_service
|
||||
) {
|
||||
parent::__construct( $api_client );
|
||||
|
||||
$this->gateway = $gateway;
|
||||
$this->order_service = $order_service;
|
||||
$this->level3_service = $level3_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a payment intent.
|
||||
*
|
||||
* @param WP_REST_Request $request data about the request.
|
||||
*
|
||||
* @throws Rest_Request_Exception
|
||||
*/
|
||||
public function create_payment_intent( $request ) {
|
||||
try {
|
||||
|
||||
$order_id = $request->get_param( 'order_id' );
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( ! $order ) {
|
||||
throw new Rest_Request_Exception( __( 'Order not found', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
$wcpay_server_request = Create_And_Confirm_Intention::create();
|
||||
|
||||
$currency = strtolower( $order->get_currency() );
|
||||
$amount = WC_Payments_Utils::prepare_amount( $order->get_total(), $currency );
|
||||
$wcpay_server_request->set_currency_code( $currency );
|
||||
$wcpay_server_request->set_amount( $amount );
|
||||
|
||||
$metadata = $this->order_service->get_payment_metadata( $order_id, Payment_Type::SINGLE() );
|
||||
$wcpay_server_request->set_metadata( $metadata );
|
||||
|
||||
$wcpay_server_request->set_customer( $request->get_param( 'customer' ) );
|
||||
$wcpay_server_request->set_level3( $this->level3_service->get_data_from_order( $order_id ) );
|
||||
$wcpay_server_request->set_payment_method( $request->get_param( 'payment_method' ) );
|
||||
$wcpay_server_request->set_payment_method_types( [ 'card' ] );
|
||||
$wcpay_server_request->set_off_session( true );
|
||||
$wcpay_server_request->set_capture_method( $this->gateway->get_option( 'manual_capture' ) && ( 'yes' === $this->gateway->get_option( 'manual_capture' ) ) );
|
||||
|
||||
$wcpay_server_request->assign_hook( 'wcpay_create_and_confirm_intent_request_api' );
|
||||
$intent = $wcpay_server_request->send();
|
||||
|
||||
$response = $this->prepare_item_for_response( $intent, $request );
|
||||
return rest_ensure_response( $this->prepare_response_for_collection( $response ) );
|
||||
|
||||
} catch ( \Throwable $e ) {
|
||||
Logger::error( 'Failed to create an intention via REST API: ' . $e );
|
||||
return new WP_Error( 'wcpay_server_error', $e->getMessage(), [ 'status' => 500 ] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Item schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
return [
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'payment_intent',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'ID for the payment intent.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'amount' => [
|
||||
'description' => __( 'The amount of the transaction.', 'woocommerce-payments' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'currency' => [
|
||||
'description' => __( 'The currency of the transaction.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'created' => [
|
||||
'description' => __( 'The date when the payment intent was created.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'customer' => [
|
||||
'description' => __( 'The customer id of the intent', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'status' => [
|
||||
'description' => __( 'The status of the payment intent.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'charge' => [
|
||||
'description' => __( 'Charge object associated with this payment intention.', 'woocommerce-payments' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => 'ID for the charge.',
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'amount' => [
|
||||
'description' => 'The amount of the charge.',
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'payment_method_details' => [
|
||||
'description' => 'Details for the payment method used for the charge.',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'card' => [
|
||||
'description' => 'Details for a card payment method.',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'amount_authorized' => [
|
||||
'description' => 'The amount authorized by the card.',
|
||||
'type' => 'integer',
|
||||
],
|
||||
'brand' => [
|
||||
'description' => 'The brand of the card.',
|
||||
'type' => 'string',
|
||||
],
|
||||
'capture_before' => [
|
||||
'description' => 'Timestamp for when the authorization must be captured.',
|
||||
'type' => 'string',
|
||||
],
|
||||
'country' => [
|
||||
'description' => 'The ISO country code.',
|
||||
'type' => 'string',
|
||||
],
|
||||
'exp_month' => [
|
||||
'description' => 'The expiration month of the card.',
|
||||
'type' => 'integer',
|
||||
],
|
||||
'exp_year' => [
|
||||
'description' => 'The expiration year of the card.',
|
||||
'type' => 'integer',
|
||||
],
|
||||
'last4' => [
|
||||
'description' => 'The last 4 digits of the card.',
|
||||
'type' => 'string',
|
||||
],
|
||||
'three_d_secure' => [
|
||||
'description' => 'Details for 3D Secure authentication.',
|
||||
'type' => 'object',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'billing_details' => [
|
||||
'description' => __( 'Billing details for the payment method.', 'woocommerce-payments' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'properties' => [
|
||||
'address' => [
|
||||
'description' => __( 'Address associated with the billing details.', 'woocommerce-payments' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
'properties' => [
|
||||
'city' => [
|
||||
'description' => __( 'City of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'country' => [
|
||||
'description' => __( 'Country of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'line1' => [
|
||||
'description' => __( 'Line 1 of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'line2' => [
|
||||
'description' => __( 'Line 2 of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'postal_code' => [
|
||||
'description' => __( 'Postal code of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'state' => [
|
||||
'description' => __( 'State of the billing address.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'email' => [
|
||||
'description' => __( 'Email associated with the billing details.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'Name associated with the billing details.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'phone' => [
|
||||
'description' => __( 'Phone number associated with the billing details.', 'woocommerce-payments' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'payment_method' => [
|
||||
'description' => 'The payment method associated with this charge.',
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'application_fee_amount' => [
|
||||
'description' => 'The application fee amount.',
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
'status' => [
|
||||
'description' => 'The status of the payment intent created.',
|
||||
'type' => 'string',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare each item for response.
|
||||
*
|
||||
* @param array|mixed $item Item to prepare.
|
||||
* @param WP_REST_Request $request Request instance.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$prepared_item = [];
|
||||
$prepared_item['id'] = $item->get_id();
|
||||
$prepared_item['amount'] = $item->get_amount();
|
||||
$prepared_item['currency'] = $item->get_currency();
|
||||
$prepared_item['created'] = $item->get_created()->format( 'Y-m-d H:i:s' );
|
||||
$prepared_item['customer'] = $item->get_customer_id();
|
||||
$prepared_item['payment_method'] = $item->get_payment_method_id();
|
||||
$prepared_item['status'] = $item->get_status();
|
||||
|
||||
try {
|
||||
$charge = $item->get_charge();
|
||||
$prepared_item['charge']['id'] = $charge->get_id();
|
||||
$prepared_item['charge']['amount'] = $charge->get_amount();
|
||||
$prepared_item['charge']['application_fee_amount'] = $charge->get_application_fee_amount();
|
||||
$prepared_item['charge']['status'] = $charge->get_status();
|
||||
|
||||
$billing_details = $charge->get_billing_details();
|
||||
if ( isset( $billing_details['address'] ) ) {
|
||||
$prepared_item['charge']['billing_details']['address']['city'] = $billing_details['address']['city'] ?? '';
|
||||
$prepared_item['charge']['billing_details']['address']['country'] = $billing_details['address']['country'] ?? '';
|
||||
$prepared_item['charge']['billing_details']['address']['line1'] = $billing_details['address']['line1'] ?? '';
|
||||
$prepared_item['charge']['billing_details']['address']['line2'] = $billing_details['address']['line2'] ?? '';
|
||||
$prepared_item['charge']['billing_details']['address']['postal_code'] = $billing_details['address']['postal_code'] ?? '';
|
||||
$prepared_item['charge']['billing_details']['address']['state'] = $billing_details['address']['state'] ?? '';
|
||||
}
|
||||
$prepared_item['charge']['billing_details']['email'] = $billing_details['email'] ?? '';
|
||||
$prepared_item['charge']['billing_details']['name'] = $billing_details['name'] ?? '';
|
||||
$prepared_item['charge']['billing_details']['phone'] = $billing_details['phone'] ?? '';
|
||||
|
||||
$payment_method_details = $charge->get_payment_method_details();
|
||||
if ( isset( $payment_method_details['card'] ) ) {
|
||||
$prepared_item['charge']['payment_method_details']['card']['amount_authorized'] = $payment_method_details['card']['amount_authorized'] ?? '';
|
||||
$prepared_item['charge']['payment_method_details']['card']['brand'] = $payment_method_details['card']['brand'] ?? '';
|
||||
$prepared_item['charge']['payment_method_details']['card']['capture_before'] = $payment_method_details['card']['capture_before'] ?? '';
|
||||
$prepared_item['charge']['payment_method_details']['card']['country'] = $payment_method_details['card']['country'] ?? '';
|
||||
$prepared_item['charge']['payment_method_details']['card']['exp_month'] = $payment_method_details['card']['exp_month'] ?? '';
|
||||
$prepared_item['charge']['payment_method_details']['card']['exp_year'] = $payment_method_details['card']['exp_year'] ?? '';
|
||||
$prepared_item['charge']['payment_method_details']['card']['last4'] = $payment_method_details['card']['last4'] ?? '';
|
||||
$prepared_item['charge']['payment_method_details']['card']['three_d_secure'] = $payment_method_details['card']['three_d_secure'] ?? '';
|
||||
}
|
||||
} catch ( \Throwable $e ) {
|
||||
Logger::error( 'Failed to prepare payment intent for response: ' . $e );
|
||||
}
|
||||
|
||||
$context = $request['context'] ?? 'view';
|
||||
$prepared_item = $this->add_additional_fields_to_object( $prepared_item, $request );
|
||||
$prepared_item = $this->filter_response_by_context( $prepared_item, $context );
|
||||
|
||||
return rest_ensure_response( $prepared_item );
|
||||
}
|
||||
}
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Reader_Charges
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\Get_Charge;
|
||||
use WCPay\Core\Server\Request\Get_Intention;
|
||||
use WCPay\Constants\Intent_Status;
|
||||
use WCPay\Core\Server\Request;
|
||||
use WCPay\Exceptions\API_Exception;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
require_once WCPAY_ABSPATH . 'includes/in-person-payments/class-wc-payments-printed-receipt-sample-order.php';
|
||||
|
||||
/**
|
||||
* REST controller for reader charges.
|
||||
*/
|
||||
class WC_REST_Payments_Reader_Controller extends WC_Payments_REST_Controller {
|
||||
const STORE_READERS_TRANSIENT_KEY = 'wcpay_store_terminal_readers';
|
||||
|
||||
const PREVIEW_RECEIPT_CHARGE_DATA = [
|
||||
'amount_captured' => 0,
|
||||
'payment_method_details' => [
|
||||
'card_present' => [
|
||||
'brand' => 'Sample',
|
||||
'last4' => '0000',
|
||||
'receipt' => [
|
||||
'application_preferred_name' => 'Sample, Receipts preview',
|
||||
'dedicated_file_name' => '0000',
|
||||
'account_type' => 'Sample',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/readers';
|
||||
|
||||
/**
|
||||
* Instance of WC_Payment_Gateway_WCPay.
|
||||
*
|
||||
* @var WC_Payment_Gateway_WCPay
|
||||
*/
|
||||
private $wcpay_gateway;
|
||||
|
||||
/**
|
||||
* Instance of WC_Payments_In_Person_Payments_Receipts_Service.
|
||||
*
|
||||
* @var WC_Payments_In_Person_Payments_Receipts_Service
|
||||
*/
|
||||
private $receipts_service;
|
||||
|
||||
/**
|
||||
* WC_REST_Payments_Reader_Controller
|
||||
*
|
||||
* @param WC_Payments_API_Client $api_client WC_Payments_API_Client.
|
||||
* @param WC_Payment_Gateway_WCPay $wcpay_gateway WC_Payment_Gateway_WCPay.
|
||||
* @param WC_Payments_In_Person_Payments_Receipts_Service $receipts_service WC_Payments_In_Person_Payments_Receipts_Service.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( WC_Payments_API_Client $api_client, WC_Payment_Gateway_WCPay $wcpay_gateway, WC_Payments_In_Person_Payments_Receipts_Service $receipts_service ) {
|
||||
parent::__construct( $api_client );
|
||||
|
||||
$this->wcpay_gateway = $wcpay_gateway;
|
||||
$this->receipts_service = $receipts_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_all_readers' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'register_reader' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'location' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
],
|
||||
'registration_code' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
],
|
||||
'label' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
'metadata' => [
|
||||
'type' => 'object',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/charges/(?P<transaction_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_summary' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/receipts/preview',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'preview_print_receipt' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/receipts/(?P<payment_intent_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'generate_print_receipt' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve payment readers charges to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_summary( $request ) {
|
||||
|
||||
$transaction_id = $request->get_param( 'transaction_id' );
|
||||
|
||||
try {
|
||||
// retrieve transaction details to get the charge date.
|
||||
$transaction = $this->api_client->get_transaction( $transaction_id );
|
||||
|
||||
if ( empty( $transaction ) ) {
|
||||
return rest_ensure_response( [] );
|
||||
}
|
||||
$summary = $this->api_client->get_readers_charge_summary( gmdate( 'Y-m-d', $transaction['created'] ) );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( 'wcpay_get_summary', $e->getMessage() ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $summary );
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies the get all readers request to the server.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_all_readers( $request ) {
|
||||
try {
|
||||
return rest_ensure_response( $this->fetch_readers() );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( $e->get_error_code(), $e->getMessage() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a card reader to an account and terminal location.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function register_reader( $request ) {
|
||||
try {
|
||||
$response = $this->api_client->register_terminal_reader(
|
||||
$request->get_param( 'location' ),
|
||||
$request->get_param( 'registration_code' ),
|
||||
$request->get_param( 'label' ),
|
||||
$request->get_param( 'metadata' )
|
||||
);
|
||||
|
||||
$reader = wp_array_slice_assoc( $response, [ 'id', 'livemode', 'device_type', 'label', 'location', 'metadata', 'status' ] );
|
||||
|
||||
return rest_ensure_response( $reader );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response(
|
||||
new WP_Error(
|
||||
$e->get_error_code(),
|
||||
$e->getMessage(),
|
||||
[ 'status' => $e->get_http_code() ]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the reader status is active
|
||||
*
|
||||
* @param array $readers The readers charges object.
|
||||
* @param string $id The reader ID.
|
||||
* @return bool
|
||||
*/
|
||||
private function is_reader_active( $readers, $id ) {
|
||||
foreach ( $readers as $reader ) {
|
||||
if ( $reader['reader_id'] === $id && 'active' === $reader['status'] ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read readers from transient and re-populates it if needed.
|
||||
*
|
||||
* @return array Terminal readers.
|
||||
* @throws API_Exception If request to server fails.
|
||||
*/
|
||||
private function fetch_readers(): array {
|
||||
$readers = get_transient( static::STORE_READERS_TRANSIENT_KEY );
|
||||
|
||||
if ( ! $readers ) {
|
||||
// Retrieve terminal readers.
|
||||
$request = Request::get( WC_Payments_API_Client::TERMINAL_READERS_API );
|
||||
$request->assign_hook( 'wcpay_get_terminal_readers_request' );
|
||||
|
||||
$readers_data = $request->send();
|
||||
|
||||
// Retrieve the readers by charges.
|
||||
$reader_by_charges = $this->api_client->get_readers_charge_summary( gmdate( 'Y-m-d', time() ) );
|
||||
|
||||
$readers = [];
|
||||
foreach ( $readers_data as $reader ) {
|
||||
$readers[] = [
|
||||
'id' => $reader['id'],
|
||||
'livemode' => $reader['livemode'],
|
||||
'device_type' => $reader['device_type'],
|
||||
'label' => $reader['label'],
|
||||
'location' => $reader['location'],
|
||||
'metadata' => $reader['metadata'],
|
||||
'status' => $reader['status'],
|
||||
'is_active' => $this->is_reader_active( $reader_by_charges, $reader['id'] ),
|
||||
];
|
||||
}
|
||||
|
||||
set_transient( static::STORE_READERS_TRANSIENT_KEY, $readers, 2 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $readers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders HTML for a print receipt
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_HTTP_Response|WP_Error
|
||||
* @throws \RuntimeException Error collecting data.
|
||||
*/
|
||||
public function generate_print_receipt( $request ) {
|
||||
try {
|
||||
/* Collect the data, available on the server side. */
|
||||
$wcpay_request = Get_Intention::create( $request->get_param( 'payment_intent_id' ) );
|
||||
$payment_intent = $wcpay_request->send();
|
||||
if ( Intent_Status::SUCCEEDED !== $payment_intent->get_status() ) {
|
||||
throw new \RuntimeException( __( 'Invalid payment intent', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
$charge = $payment_intent->get_charge();
|
||||
$charge_id = $charge ? $charge->get_id() : null;
|
||||
$charge_request = Get_Charge::create( $charge_id );
|
||||
$charge_array = $charge_request->send();
|
||||
|
||||
/* Collect receipt data, stored on the store side. */
|
||||
$order = wc_get_order( $charge_array['order']['number'] );
|
||||
if ( false === $order ) {
|
||||
throw new \RuntimeException( __( 'Order not found', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
// Retrieve branding logo file ID.
|
||||
$branding_logo = $this->wcpay_gateway->get_option( 'account_branding_logo', '' );
|
||||
|
||||
/* Collect merchant settings */
|
||||
$settings = [
|
||||
'branding_logo' => ( ! empty( $branding_logo ) ) ? $this->api_client->get_file_contents( $branding_logo, false ) : [],
|
||||
'business_name' => $this->wcpay_gateway->get_option( 'account_business_name' ),
|
||||
'support_info' => [
|
||||
'address' => $this->wcpay_gateway->get_option( 'account_business_support_address' ),
|
||||
'phone' => $this->wcpay_gateway->get_option( 'account_business_support_phone' ),
|
||||
'email' => $this->wcpay_gateway->get_option( 'account_business_support_email' ),
|
||||
],
|
||||
];
|
||||
|
||||
/* Generate receipt */
|
||||
$response = [ 'html_content' => $this->receipts_service->get_receipt_markup( $settings, $order, $charge_array ) ];
|
||||
} catch ( \Throwable $e ) {
|
||||
$error_status_code = $e instanceof API_Exception ? $e->get_http_code() : 500;
|
||||
$response = new WP_Error( 'generate_print_receipt_error', $e->getMessage(), [ 'status' => $error_status_code ] );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
/**
|
||||
* Returns HTML to preview a print receipt
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_HTTP_Response|WP_Error
|
||||
* @throws \RuntimeException Error collecting data.
|
||||
*/
|
||||
public function preview_print_receipt( WP_REST_Request $request ) {
|
||||
$preview = $this->receipts_service->get_receipt_markup(
|
||||
$this->create_print_preview_receipt_settings_data( $request->get_json_params() ),
|
||||
new WC_Payments_Printed_Receipt_Sample_Order(),
|
||||
self::PREVIEW_RECEIPT_CHARGE_DATA
|
||||
);
|
||||
|
||||
return rest_ensure_response( [ 'html_content' => $preview ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates settings data to be used on the printed receipt preview. Defaults to stored settings if one parameter is not provided.
|
||||
*
|
||||
* @param array $receipt_settings Array of settings to use to create the receipt preview.
|
||||
* @return array
|
||||
*/
|
||||
private function create_print_preview_receipt_settings_data( array $receipt_settings ): array {
|
||||
$support_address = empty( $receipt_settings['accountBusinessSupportAddress'] ) ? $this->wcpay_gateway->get_option( 'account_business_support_address' ) : $receipt_settings['accountBusinessSupportAddress'];
|
||||
return [
|
||||
'business_name' => empty( $receipt_settings['accountBusinessName'] ) ? $this->wcpay_gateway->get_option( 'account_business_name' ) : $receipt_settings['accountBusinessName'],
|
||||
'support_info' => [
|
||||
'address' => [
|
||||
'line1' => $support_address['line1'],
|
||||
'line2' => $support_address['line2'],
|
||||
'city' => $support_address['city'],
|
||||
'state' => $support_address['state'],
|
||||
'postal_code' => $support_address['postal_code'],
|
||||
'country' => $support_address['country'],
|
||||
],
|
||||
'phone' => empty( $receipt_settings['accountBusinessSupportPhone'] ) ? $this->wcpay_gateway->get_option( 'account_business_support_phone' ) : $receipt_settings['accountBusinessSupportPhone'],
|
||||
'email' => empty( $receipt_settings['accountBusinessSupportEmail'] ) ? $this->wcpay_gateway->get_option( 'account_business_support_email' ) : $receipt_settings['accountBusinessSupportEmail'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Timeline_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\Refund_Charge;
|
||||
use WCPay\Exceptions\API_Exception;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for the timeline, which includes all events related to an intention.
|
||||
*/
|
||||
class WC_REST_Payments_Refunds_Controller extends WC_Payments_REST_Controller {
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/refund';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'process_refund' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes direct refund bypassing any order checks.
|
||||
*
|
||||
* @internal Not intended for usage in integrations or outside of WooCommerce Payments.
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function process_refund( $request ) {
|
||||
$order_id = $request->get_param( 'order_id' );
|
||||
$charge_id = $request->get_param( 'charge_id' );
|
||||
$amount = $request->get_param( 'amount' );
|
||||
$reason = $request->get_param( 'reason' );
|
||||
|
||||
if ( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( $order ) {
|
||||
$result = wc_create_refund(
|
||||
[
|
||||
'amount' => WC_Payments_Utils::interpret_stripe_amount( $amount, $order->get_currency() ),
|
||||
'reason' => $reason,
|
||||
'order_id' => $order_id,
|
||||
'refund_payment' => true,
|
||||
'restock_items' => true,
|
||||
]
|
||||
);
|
||||
|
||||
return rest_ensure_response( $result );
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$refund_request = Refund_Charge::create( $charge_id );
|
||||
$refund_request->set_charge( $charge_id );
|
||||
$refund_request->set_amount( $amount );
|
||||
$refund_request->set_reason( $reason );
|
||||
$refund_request->set_source( 'transaction_details_no_order' );
|
||||
$response = $refund_request->send();
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( 'wcpay_refund_payment', $e->getMessage() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Reporting_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\Get_Reporting_Payment_Activity;
|
||||
use WCPay\Core\Server\Request\Request_Utils;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for customers.
|
||||
*/
|
||||
class WC_REST_Payments_Reporting_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/reporting';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/payment_activity',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_payment_activity' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Payment Activity data.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
*/
|
||||
public function get_payment_activity( $request ) {
|
||||
$wcpay_request = Get_Reporting_Payment_Activity::create();
|
||||
$date_start_in_store_timezone = $this->format_date_to_wp_timezone( $request->get_param( 'date_start' ) );
|
||||
$date_end_in_store_timezone = $this->format_date_to_wp_timezone( $request->get_param( 'date_end' ) );
|
||||
$wcpay_request->set_date_start( $date_start_in_store_timezone );
|
||||
$wcpay_request->set_date_end( $date_end_in_store_timezone );
|
||||
$wcpay_request->set_timezone( $request->get_param( 'timezone' ) );
|
||||
$wcpay_request->set_currency( $request->get_param( 'currency' ) );
|
||||
return $wcpay_request->handle_rest_request();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formats a date string to the WordPress timezone.
|
||||
*
|
||||
* @param string $date_string The date string to be formatted.
|
||||
* @return string The formatted date string in the 'Y-m-d\TH:i:s' format.
|
||||
*/
|
||||
private function format_date_to_wp_timezone( $date_string ) {
|
||||
$date = Request_Utils::format_transaction_date_by_timezone( $date_string, '+00:00' );
|
||||
$date = new DateTime( $date );
|
||||
return $date->format( 'Y-m-d\\TH:i:s' );
|
||||
}
|
||||
}
|
||||
+1098
File diff suppressed because it is too large
Load Diff
+149
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Survey_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for settings.
|
||||
*/
|
||||
class WC_REST_Payments_Survey_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/survey';
|
||||
|
||||
/**
|
||||
* The HTTP client, used to forward the request to WPCom.
|
||||
*
|
||||
* @var WC_Payments_Http
|
||||
*/
|
||||
protected $http_client;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
* WC_REST_Payments_Survey_Controller constructor.
|
||||
*
|
||||
* @param WC_Payments_Http_Interface $http_client - The HTTP client, used to forward the request to WPCom.
|
||||
*/
|
||||
public function __construct( WC_Payments_Http_Interface $http_client ) {
|
||||
$this->http_client = $http_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/payments-overview',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'submit_payments_overview_survey' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'rating' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'enum' => [
|
||||
'very-unhappy',
|
||||
'unhappy',
|
||||
'neutral',
|
||||
'happy',
|
||||
'very-happy',
|
||||
],
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
],
|
||||
'comments' => [
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'sanitize_callback' => 'wp_filter_nohtml_kses',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the overview survey trhough the WPcom API.
|
||||
*
|
||||
* @param WP_REST_Request $request the request being made.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function submit_payments_overview_survey( WP_REST_Request $request ): WP_REST_Response {
|
||||
$comments = $request->get_param( 'comments' ) ?? '';
|
||||
$rating = $request->get_param( 'rating' ) ?? '';
|
||||
|
||||
if ( empty( $rating ) ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'success' => false,
|
||||
'err' => 'No answers provided',
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$request_args = [
|
||||
'url' => WC_Payments_API_Client::ENDPOINT_BASE . '/marketing/survey',
|
||||
'method' => 'POST',
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Forwarded-For' => \WC_Geolocation::get_ip_address(),
|
||||
],
|
||||
];
|
||||
$request_body = wp_json_encode(
|
||||
[
|
||||
'site_id' => $this->http_client->get_blog_id(),
|
||||
'survey_id' => 'wcpay-payment-activity',
|
||||
'survey_responses' => [
|
||||
'rating' => $rating,
|
||||
'comments' => [ 'text' => $comments ],
|
||||
'wcpay-version' => [ 'text' => WCPAY_VERSION_NUMBER ],
|
||||
],
|
||||
]
|
||||
);
|
||||
$is_site_specific = true;
|
||||
$use_user_token = true;
|
||||
|
||||
$wpcom_response = $this->http_client->remote_request(
|
||||
$request_args,
|
||||
$request_body,
|
||||
$is_site_specific,
|
||||
$use_user_token
|
||||
);
|
||||
|
||||
$wpcom_response_status_code = wp_remote_retrieve_response_code( $wpcom_response );
|
||||
|
||||
if ( 200 === $wpcom_response_status_code ) {
|
||||
update_option( 'wcpay_survey_payment_overview_submitted', true );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $wpcom_response, $wpcom_response_status_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify access.
|
||||
*
|
||||
* Override this method if custom permissions required.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check_permission() {
|
||||
return current_user_can( 'manage_woocommerce' );
|
||||
}
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Terminal_Locations_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use WCPay\Core\Server\Request;
|
||||
use WCPay\Exceptions\API_Exception;
|
||||
/**
|
||||
* REST controller for account details and status.
|
||||
*/
|
||||
class WC_REST_Payments_Terminal_Locations_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
const STORE_LOCATIONS_TRANSIENT_KEY = 'wcpay_store_terminal_locations';
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/terminal/locations';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
$this->rest_base . '/store',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_store_location' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<location_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => [ $this, 'delete_location' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<location_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'update_location' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'display_name' => [
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
],
|
||||
'address' => [
|
||||
'type' => 'object',
|
||||
'required' => false,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<location_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_location' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'create_location' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'display_name' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
],
|
||||
'address' => [
|
||||
'type' => 'object',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_all_locations' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get store terminal location.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_store_location( $request ) {
|
||||
$store_address = WC()->countries;
|
||||
$location_address = array_filter(
|
||||
[
|
||||
'city' => $store_address->get_base_city(),
|
||||
'country' => $store_address->get_base_country(),
|
||||
'line1' => $store_address->get_base_address(),
|
||||
'line2' => $store_address->get_base_address_2(),
|
||||
'postal_code' => $store_address->get_base_postcode(),
|
||||
'state' => $store_address->get_base_state(),
|
||||
]
|
||||
);
|
||||
|
||||
// If address is not populated, emit an error and specify the URL where this can be done.
|
||||
// See also https://tosbourn.com/list-of-countries-without-a-postcode/ when launching in new countries.
|
||||
$is_address_populated = isset( $location_address['country'], $location_address['city'], $location_address['postal_code'], $location_address['line1'] );
|
||||
if ( ! $is_address_populated ) {
|
||||
return rest_ensure_response(
|
||||
new \WP_Error(
|
||||
'store_address_is_incomplete',
|
||||
admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'page' => 'wc-settings',
|
||||
'tab' => 'general',
|
||||
],
|
||||
'admin.php'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Check the existing locations to see if one of them matches the store.
|
||||
// Originally we picked `get_bloginfo` for generating names, but later switched to `site_url` for max immutability.
|
||||
$store_hostname = str_replace( [ 'https://', 'http://' ], '', get_site_url() );
|
||||
$possible_names = [ get_bloginfo(), $store_hostname ];
|
||||
foreach ( $this->fetch_locations() as $location ) {
|
||||
if ( in_array( $location['display_name'], $possible_names, true ) ) {
|
||||
$matching_address_fields = array_intersect( $location['address'], $location_address );
|
||||
if ( count( $matching_address_fields ) === count( $location_address ) ) {
|
||||
return rest_ensure_response( $this->extract_location_fields( $location ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the location is missing, Create a new one and actualize the transient.
|
||||
$location = $this->api_client->create_terminal_location( $store_hostname, $location_address );
|
||||
$this->reload_locations();
|
||||
|
||||
return rest_ensure_response( $this->extract_location_fields( $location ) );
|
||||
} catch ( API_Exception $e ) {
|
||||
$error = new WP_Error( $e->get_error_code(), $e->getMessage() );
|
||||
// Stripe will return a 400 for incorrect city, state, or country. Ideally, we should return
|
||||
// a more appropriate error code like 'store_address_is_incorrect', but that will break older mobile app clients.
|
||||
// Until we have a more granular versioning support for WCPay REST endpoints, this is the best we can do.
|
||||
if ( 'invalid_request_error' === $e->get_error_code() ) {
|
||||
$error = new WP_Error(
|
||||
'store_address_is_incomplete',
|
||||
admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'page' => 'wc-settings',
|
||||
'tab' => 'general',
|
||||
],
|
||||
'admin.php'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return rest_ensure_response( $error );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies the delete location request to the server.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function delete_location( $request ) {
|
||||
try {
|
||||
// Delete the location and reload the transient.
|
||||
$location = $this->api_client->delete_terminal_location( $request->get_param( 'location_id' ) );
|
||||
$this->reload_locations();
|
||||
|
||||
return rest_ensure_response( $location );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( $e->get_error_code(), $e->getMessage() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies the update location request to the server.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function update_location( $request ) {
|
||||
try {
|
||||
// Update the location and reload the transient.
|
||||
$location = $this->api_client->update_terminal_location( $request->get_param( 'location_id' ), $request['display_name'], $request['address'] );
|
||||
$this->reload_locations();
|
||||
|
||||
return rest_ensure_response( $this->extract_location_fields( $location ) );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( $e->get_error_code(), $e->getMessage() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies the get location request to the server.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_location( $request ) {
|
||||
try {
|
||||
// Check if the location is already in the transient.
|
||||
$location_id = $request->get_param( 'location_id' );
|
||||
foreach ( $this->fetch_locations() as $location ) {
|
||||
if ( $location['id'] === $location_id ) {
|
||||
return rest_ensure_response( $this->extract_location_fields( $location ) );
|
||||
}
|
||||
}
|
||||
// If the location is missing, fetch it individually and reload the transient.
|
||||
$request = Request::get( WC_Payments_API_Client::TERMINAL_LOCATIONS_API, $location_id );
|
||||
$request->assign_hook( 'wcpay_get_terminal_location' );
|
||||
$location = $request->send();
|
||||
$this->reload_locations();
|
||||
|
||||
return rest_ensure_response( $this->extract_location_fields( $location ) );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( $e->get_error_code(), $e->getMessage() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies the create location request to the server.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function create_location( $request ) {
|
||||
try {
|
||||
// Create location and reload the transient.
|
||||
$location = $this->api_client->create_terminal_location( $request['display_name'], $request['address'] );
|
||||
$this->reload_locations();
|
||||
|
||||
return rest_ensure_response( $this->extract_location_fields( $location ) );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( $e->get_error_code(), $e->getMessage() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies the get all locations request to the server.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_all_locations( $request ) {
|
||||
try {
|
||||
return rest_ensure_response( array_map( [ $this, 'extract_location_fields' ], $this->fetch_locations() ) );
|
||||
} catch ( API_Exception $e ) {
|
||||
return rest_ensure_response( new WP_Error( $e->get_error_code(), $e->getMessage() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the relevant fields from a terminal location object.
|
||||
*
|
||||
* @param array $location The location.
|
||||
* @return array The picked fields from location object.
|
||||
*/
|
||||
private function extract_location_fields( array $location ): array {
|
||||
return [
|
||||
'id' => $location['id'],
|
||||
'address' => $location['address'],
|
||||
'display_name' => $location['display_name'],
|
||||
'livemode' => $location['livemode'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read locations from transient and re-populates it if needed.
|
||||
*
|
||||
* @return array Terminal locations.
|
||||
* @throws API_Exception If request to server fails.
|
||||
*/
|
||||
private function fetch_locations(): array {
|
||||
$locations = get_transient( static::STORE_LOCATIONS_TRANSIENT_KEY );
|
||||
if ( ! $locations ) {
|
||||
$request = Request::get( WC_Payments_API_Client::TERMINAL_LOCATIONS_API );
|
||||
$request->assign_hook( 'wcpay_get_terminal_locations' );
|
||||
$locations = $request->send();
|
||||
set_transient( static::STORE_LOCATIONS_TRANSIENT_KEY, $locations, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $locations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the locations stored in transient.
|
||||
*
|
||||
* @return void
|
||||
* @throws API_Exception If request to server fails.
|
||||
*/
|
||||
private function reload_locations() {
|
||||
$request = Request::get( WC_Payments_API_Client::TERMINAL_LOCATIONS_API );
|
||||
$request->assign_hook( 'wcpay_get_terminal_locations' );
|
||||
|
||||
$locations = $request->send();
|
||||
set_transient( static::STORE_LOCATIONS_TRANSIENT_KEY, $locations, DAY_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Timeline_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for the timeline, which includes all events related to an intention.
|
||||
*/
|
||||
class WC_REST_Payments_Timeline_Controller extends WC_Payments_REST_Controller {
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/timeline';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<intention_id>\w+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_timeline' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve timeline to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_timeline( $request ) {
|
||||
$intention_id = $request->get_param( 'intention_id' );
|
||||
return $this->forward_request( 'get_timeline', [ $intention_id ] );
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Tos_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\Add_Account_Tos_Agreement;
|
||||
use WCPay\Exceptions\Rest_Request_Exception;
|
||||
use WCPay\Logger;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for Terms of Services routes.
|
||||
*/
|
||||
class WC_REST_Payments_Tos_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Result codes for returning to the WCPay server API. They don't have any special meaning, but can will be logged
|
||||
* and are therefore useful when debugging how we reacted to a webhook.
|
||||
*/
|
||||
const RESULT_SUCCESS = 'success';
|
||||
const RESULT_BAD_REQUEST = 'bad_request';
|
||||
const RESULT_ERROR = 'error';
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/tos';
|
||||
|
||||
/**
|
||||
* Instance of WC_Payment_Gateway_WCPay
|
||||
*
|
||||
* @var WC_Payment_Gateway_WCPay
|
||||
*/
|
||||
private $gateway;
|
||||
|
||||
/**
|
||||
* WC Payments Account.
|
||||
*
|
||||
* @var WC_Payments_Account
|
||||
*/
|
||||
private $account;
|
||||
|
||||
/**
|
||||
* WC_REST_Payments_Webhook_Controller constructor.
|
||||
*
|
||||
* @param WC_Payments_API_Client $api_client WC_Payments_API_Client instance.
|
||||
* @param WC_Payment_Gateway_WCPay $gateway WC_Payment_Gateway_WCPay instance.
|
||||
* @param WC_Payments_Account $account WC_Payments_Account instance.
|
||||
*/
|
||||
public function __construct( WC_Payments_API_Client $api_client, WC_Payment_Gateway_WCPay $gateway, WC_Payments_Account $account ) {
|
||||
parent::__construct( $api_client );
|
||||
$this->gateway = $gateway;
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'handle_tos' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/reactivate',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'reactivate' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/stripe_track_connected',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'remove_stripe_connect_track' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record ToS acceptance.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
* @throws Rest_Request_Exception Throw if accept param is missing.
|
||||
*/
|
||||
public function handle_tos( $request ) {
|
||||
$body = $request->get_json_params();
|
||||
|
||||
try {
|
||||
if ( ! isset( $body['accept'] ) ) {
|
||||
throw new Rest_Request_Exception( __( 'ToS accept parameter is missing', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
$is_accepted = (bool) $body['accept'];
|
||||
|
||||
Logger::debug( sprintf( 'ToS acceptance request received. Accept: %s', $is_accepted ? 'yes' : 'no' ) );
|
||||
|
||||
if ( $is_accepted ) {
|
||||
$this->handle_tos_accepted();
|
||||
} else {
|
||||
$this->handle_tos_declined();
|
||||
}
|
||||
} catch ( Rest_Request_Exception $e ) {
|
||||
Logger::error( $e );
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_BAD_REQUEST ], 400 );
|
||||
} catch ( Exception $e ) {
|
||||
Logger::error( $e );
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_ERROR ], 500 );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_SUCCESS ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process ToS accepted.
|
||||
*/
|
||||
private function handle_tos_accepted() {
|
||||
$this->gateway->enable();
|
||||
|
||||
// Accessing directly, because a user must be already logged in.
|
||||
$current_user = wp_get_current_user();
|
||||
$user_name = $current_user->user_login;
|
||||
|
||||
$request = Add_Account_Tos_Agreement::create();
|
||||
$request->set_source( 'settings-popup' );
|
||||
$request->set_user_name( $user_name );
|
||||
$request->send();
|
||||
|
||||
$this->account->refresh_account_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process ToS declined.
|
||||
*/
|
||||
private function handle_tos_declined() {
|
||||
// TODO: maybe record ToS declined data.
|
||||
$this->gateway->disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the gateway again, after it's been disabled.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function reactivate( $request ) {
|
||||
try {
|
||||
$this->gateway->enable();
|
||||
Logger::debug( 'Gateway re-enabled after ToS decline.' );
|
||||
} catch ( Exception $e ) {
|
||||
Logger::error( $e );
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_ERROR ], 500 );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_SUCCESS ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes _wcpay_onboarding_stripe_connected option after KYC completion has been tracked.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function remove_stripe_connect_track( $request ) {
|
||||
delete_option( '_wcpay_onboarding_stripe_connected' );
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_SUCCESS ] );
|
||||
}
|
||||
}
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Transactions_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request\List_Transactions;
|
||||
use WCPay\Core\Server\Request\List_Fraud_Outcome_Transactions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for transactions.
|
||||
*/
|
||||
class WC_REST_Payments_Transactions_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/transactions';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_transactions' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/download',
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_transactions_export' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/summary',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_transactions_summary' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/search',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_transactions_search_autocomplete' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/fraud-outcomes',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_fraud_outcome_transactions' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/fraud-outcomes/summary',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_fraud_outcome_transactions_summary' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/fraud-outcomes/search',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_fraud_outcome_transactions_search_autocomplete' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/fraud-outcomes/download',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_fraud_outcome_transactions_export' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_transactions( $request ) {
|
||||
|
||||
$wcpay_request = List_Transactions::from_rest_request( $request );
|
||||
|
||||
return $wcpay_request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve fraud outcome transactions to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_fraud_outcome_transactions( $request ) {
|
||||
$wcpay_request = List_Fraud_Outcome_Transactions::from_rest_request( $request );
|
||||
|
||||
return $this->forward_request( 'list_fraud_outcome_transactions', [ $wcpay_request ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve fraud outcome transactions summary to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_fraud_outcome_transactions_summary( $request ) {
|
||||
$wcpay_request = List_Fraud_Outcome_Transactions::from_rest_request( $request );
|
||||
|
||||
return $this->forward_request( 'list_fraud_outcome_transactions_summary', [ $wcpay_request ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions search options to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_fraud_outcome_transactions_search_autocomplete( $request ) {
|
||||
$wcpay_request = List_Fraud_Outcome_Transactions::from_rest_request( $request );
|
||||
|
||||
return $this->forward_request( 'get_fraud_outcome_transactions_search_autocomplete', [ $wcpay_request ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate transactions export via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_fraud_outcome_transactions_export( $request ) {
|
||||
$wcpay_request = List_Fraud_Outcome_Transactions::from_rest_request( $request );
|
||||
|
||||
return $this->forward_request( 'get_fraud_outcome_transactions_export', [ $wcpay_request ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate transactions export via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_transactions_export( $request ) {
|
||||
$user_email = $request->get_param( 'user_email' );
|
||||
$deposit_id = $request->get_param( 'deposit_id' );
|
||||
$locale = $request->get_param( 'locale' );
|
||||
$filters = $this->get_transactions_filters( $request );
|
||||
|
||||
return $this->forward_request( 'get_transactions_export', [ $filters, $user_email, $deposit_id, $locale ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions summary to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_transactions_summary( $request ) {
|
||||
$deposit_id = $request->get_param( 'deposit_id' );
|
||||
$filters = $this->get_transactions_filters( $request );
|
||||
return $this->forward_request( 'get_transactions_summary', [ $filters, $deposit_id ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions search options to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function get_transactions_search_autocomplete( $request ) {
|
||||
$search_term = $request->get_param( 'search_term' );
|
||||
return $this->forward_request( 'get_transactions_search_autocomplete', [ $search_term ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract transactions filters from request
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
private function get_transactions_filters( $request ) {
|
||||
$date_between_filter = $request->get_param( 'date_between' );
|
||||
$user_timezone = $request->get_param( 'user_timezone' );
|
||||
|
||||
if ( ! is_null( $date_between_filter ) ) {
|
||||
$date_between_filter = array_map(
|
||||
function ( $transaction_date ) use ( $user_timezone ) {
|
||||
return $this->format_transaction_date_with_timestamp( $transaction_date, $user_timezone );
|
||||
},
|
||||
$date_between_filter
|
||||
);
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
[
|
||||
'match' => $request->get_param( 'match' ),
|
||||
'date_before' => $this->format_transaction_date_with_timestamp( $request->get_param( 'date_before' ), $user_timezone ),
|
||||
'date_after' => $this->format_transaction_date_with_timestamp( $request->get_param( 'date_after' ), $user_timezone ),
|
||||
'date_between' => $date_between_filter,
|
||||
'type_is' => $request->get_param( 'type_is' ),
|
||||
'type_is_not' => $request->get_param( 'type_is_not' ),
|
||||
'source_device_is' => $request->get_param( 'source_device_is' ),
|
||||
'source_device_is_not' => $request->get_param( 'source_device_is_not' ),
|
||||
'channel_is' => $request->get_param( 'channel_is' ),
|
||||
'channel_is_not' => $request->get_param( 'channel_is_not' ),
|
||||
'customer_country_is' => $request->get_param( 'customer_country_is' ),
|
||||
'customer_country_is_not' => $request->get_param( 'customer_country_is_not' ),
|
||||
'risk_level_is' => $request->get_param( 'risk_level_is' ),
|
||||
'risk_level_is_not' => $request->get_param( 'risk_level_is_not' ),
|
||||
'store_currency_is' => $request->get_param( 'store_currency_is' ),
|
||||
'customer_currency_is' => $request->get_param( 'customer_currency_is' ),
|
||||
'customer_currency_is_not' => $request->get_param( 'customer_currency_is_not' ),
|
||||
'source_is' => $request->get_param( 'source_is' ),
|
||||
'source_is_not' => $request->get_param( 'source_is_not' ),
|
||||
'loan_id_is' => $request->get_param( 'loan_id_is' ),
|
||||
'search' => $request->get_param( 'search' ),
|
||||
],
|
||||
static function ( $filter ) {
|
||||
return null !== $filter;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the incoming transaction date as per the blog's timezone.
|
||||
*
|
||||
* @param string|null $transaction_date Transaction date to format.
|
||||
* @param string|null $user_timezone User's timezone passed from client.
|
||||
*
|
||||
* @return string|null The formatted transaction date as per timezone.
|
||||
*/
|
||||
private function format_transaction_date_with_timestamp( $transaction_date, $user_timezone ) {
|
||||
if ( is_null( $transaction_date ) || is_null( $user_timezone ) ) {
|
||||
return $transaction_date;
|
||||
}
|
||||
|
||||
// Get blog timezone.
|
||||
$blog_time = new DateTime( $transaction_date );
|
||||
$blog_time->setTimezone( new DateTimeZone( wp_timezone_string() ) );
|
||||
|
||||
// Get local timezone.
|
||||
$local_time = new DateTime( $transaction_date );
|
||||
$local_time->setTimezone( new DateTimeZone( $user_timezone ) );
|
||||
|
||||
// Compute time difference in minutes.
|
||||
$time_difference = ( strtotime( $local_time->format( 'Y-m-d H:i:s' ) ) - strtotime( $blog_time->format( 'Y-m-d H:i:s' ) ) ) / 60;
|
||||
|
||||
// Shift date by time difference.
|
||||
$formatted_date = new DateTime( $transaction_date );
|
||||
date_modify( $formatted_date, $time_difference . 'minutes' );
|
||||
|
||||
return $formatted_date->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_VAT_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Core\Server\Request;
|
||||
use WCPay\Exceptions\API_Exception;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for vat.
|
||||
*/
|
||||
class WC_REST_Payments_VAT_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/vat';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<vat_number>[\w\.\%]+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'validate_vat' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'args' => [
|
||||
'vat_number' => [
|
||||
'type' => 'string',
|
||||
'format' => 'text-field',
|
||||
'required' => false,
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'string',
|
||||
'format' => 'text-field',
|
||||
'required' => true,
|
||||
],
|
||||
'address' => [
|
||||
'type' => 'string',
|
||||
'format' => 'textarea-field',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'save_vat_details' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate VAT number to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function validate_vat( $request ) {
|
||||
$vat_number = sanitize_text_field( $request->get_param( 'vat_number' ) );
|
||||
$server_request = Request::get( WC_Payments_API_Client::VAT_API, $vat_number );
|
||||
$server_request->assign_hook( 'wcpay_validate_vat_request' );
|
||||
return $server_request->handle_rest_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save VAT details and respond via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*/
|
||||
public function save_vat_details( $request ) {
|
||||
$vat_number = $request->get_param( 'vat_number' );
|
||||
$name = $request->get_param( 'name' );
|
||||
$address = $request->get_param( 'address' );
|
||||
return $this->forward_request( 'save_vat_details', [ $vat_number, $name, $address ] );
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_Payments_Webhook_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
use WCPay\Exceptions\Invalid_Webhook_Data_Exception;
|
||||
use WCPay\Logger;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for webhooks.
|
||||
*/
|
||||
class WC_REST_Payments_Webhook_Controller extends WC_Payments_REST_Controller {
|
||||
|
||||
/**
|
||||
* Result codes for returning to the WCPay server API. They don't have any special meaning, but can will be logged
|
||||
* and are therefore useful when debugging how we reacted to a webhook.
|
||||
*/
|
||||
const RESULT_SUCCESS = 'success';
|
||||
const RESULT_BAD_REQUEST = 'bad_request';
|
||||
const RESULT_ERROR = 'error';
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'payments/webhook';
|
||||
|
||||
/**
|
||||
* Webhook Processing Service.
|
||||
*
|
||||
* @var WC_Payments_Webhook_Processing_Service
|
||||
*/
|
||||
private $webhook_processing_service;
|
||||
|
||||
/**
|
||||
* WC_REST_Payments_Webhook_Controller constructor.
|
||||
*
|
||||
* @param WC_Payments_API_Client $api_client WC_Payments_API_Client instance.
|
||||
* @param WC_Payments_Webhook_Processing_Service $webhook_processing_service WC_Payments_Webhook_Processing_Service instance.
|
||||
*/
|
||||
public function __construct(
|
||||
WC_Payments_API_Client $api_client,
|
||||
WC_Payments_Webhook_Processing_Service $webhook_processing_service
|
||||
) {
|
||||
parent::__construct( $api_client );
|
||||
$this->webhook_processing_service = $webhook_processing_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'handle_webhook' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions to respond with via API.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_webhook( $request ) {
|
||||
$body = $request->get_json_params();
|
||||
|
||||
try {
|
||||
$this->webhook_processing_service->process( $body );
|
||||
} catch ( Invalid_Webhook_Data_Exception $e ) {
|
||||
Logger::error( $e );
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_BAD_REQUEST ], 400 );
|
||||
} catch ( Exception $e ) {
|
||||
Logger::error( $e );
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_ERROR ], 500 );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( [ 'result' => self::RESULT_SUCCESS ] );
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_UPE_Flag_Toggle_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* REST controller for UPE feature flag. Needs to stay in the codebase to avoid error on plugin update for versions 6.9.2 or earlier.
|
||||
*/
|
||||
class WC_REST_UPE_Flag_Toggle_Controller extends WP_REST_Controller {
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_REST_WooPay_Session_Controller
|
||||
*
|
||||
* @package WooCommerce\Payments\Admin
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use WCPay\WooPay\WooPay_Session;
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use WCPay\Logger;
|
||||
|
||||
/**
|
||||
* REST controller to check get WooPay extension data for user.
|
||||
*/
|
||||
class WC_REST_WooPay_Session_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'payments/woopay';
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'session';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_session_data' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'email' => [
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve WooPay session data.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response The initial session request data.
|
||||
*/
|
||||
public function get_session_data( WP_REST_Request $request ): WP_REST_Response {
|
||||
try {
|
||||
// phpcs:ignore
|
||||
/**
|
||||
* @psalm-suppress UndefinedClass
|
||||
*/
|
||||
$response = WooPay_Session::get_init_session_request( null, null, null, $request );
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
} catch ( Exception $e ) {
|
||||
$error = new WP_Error( 'wcpay_server_error', $e->getMessage(), [ 'status' => 400 ] );
|
||||
Logger::log( 'Error validating cart token from WooPay request: ' . $e->getMessage() );
|
||||
|
||||
return rest_convert_error_to_response( $error );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permission confirms that the request is from WooPay.
|
||||
*
|
||||
* @return bool True if request is from WooPay and has a valid signature.
|
||||
*/
|
||||
public function check_permission() {
|
||||
return $this->is_request_from_woopay() && $this->has_valid_request_signature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request that's currently being processed is signed with the blog token.
|
||||
*
|
||||
* @return bool True if the request signature is valid.
|
||||
*/
|
||||
private function has_valid_request_signature(): bool {
|
||||
return apply_filters( 'wcpay_woopay_is_signed_with_blog_token', Rest_Authentication::is_signed_with_blog_token() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request that's currently being processed is from WooPay, false
|
||||
* otherwise.
|
||||
*
|
||||
* @return bool True if request is from WooPay.
|
||||
*/
|
||||
private function is_request_from_woopay(): bool {
|
||||
return isset( $_SERVER['HTTP_USER_AGENT'] ) && 'WooPay' === $_SERVER['HTTP_USER_AGENT'];
|
||||
}
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_Payments_Task_Disputes
|
||||
*
|
||||
* @package WooCommerce\Payments\Tasks
|
||||
*/
|
||||
|
||||
namespace WooCommerce\Payments\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use WCPay\Database_Cache;
|
||||
use WC_Payments_Utils;
|
||||
use WC_Payments_API_Client;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC Onboarding Task displayed if disputes awaiting response.
|
||||
*
|
||||
* Note: this task is separate to the Payments → Overview disputes task, which is defined in client/overview/task-list/tasks.js.
|
||||
*/
|
||||
class WC_Payments_Task_Disputes extends Task {
|
||||
/**
|
||||
* Client for making requests to the WooCommerce Payments API
|
||||
*
|
||||
* @var WC_Payments_API_Client
|
||||
*/
|
||||
private $api_client;
|
||||
|
||||
/**
|
||||
* Database_Cache instance.
|
||||
*
|
||||
* @var Database_Cache
|
||||
*/
|
||||
private $database_cache;
|
||||
|
||||
|
||||
/**
|
||||
* Disputes due within 7 days.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $disputes_due_within_7d;
|
||||
|
||||
/**
|
||||
* Disputes due within 1 day.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $disputes_due_within_1d;
|
||||
|
||||
/**
|
||||
* A memory cache of all disputes needing response.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $disputes_needing_response = null;
|
||||
|
||||
/**
|
||||
* WC_Payments_Task_Disputes constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->api_client = \WC_Payments::get_payments_api_client();
|
||||
$this->database_cache = \WC_Payments::get_database_cache();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the task.
|
||||
*/
|
||||
private function fetch_relevant_disputes() {
|
||||
$this->disputes_due_within_7d = $this->get_disputes_needing_response_within_days( 7 );
|
||||
$this->disputes_due_within_1d = $this->get_disputes_needing_response_within_days( 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'woocommerce_payments_disputes_task';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( null === $this->disputes_needing_response ) {
|
||||
$this->fetch_relevant_disputes();
|
||||
}
|
||||
if ( count( (array) $this->disputes_due_within_7d ) === 1 ) {
|
||||
$dispute = $this->disputes_due_within_7d[0];
|
||||
$amount = WC_Payments_Utils::interpret_stripe_amount( $dispute['amount'], $dispute['currency'] );
|
||||
$amount_formatted = WC_Payments_Utils::format_currency( $amount, $dispute['currency'] );
|
||||
if ( count( (array) $this->disputes_due_within_1d ) > 0 ) {
|
||||
return sprintf(
|
||||
/* translators: %s is a currency formatted amount */
|
||||
__( 'Respond to a dispute for %s – Last day', 'woocommerce-payments' ),
|
||||
$amount_formatted
|
||||
);
|
||||
}
|
||||
return sprintf(
|
||||
/* translators: %s is a currency formatted amount */
|
||||
__( 'Respond to a dispute for %s', 'woocommerce-payments' ),
|
||||
$amount_formatted
|
||||
);
|
||||
}
|
||||
|
||||
$active_disputes = $this->get_disputes_needing_response();
|
||||
if ( ! is_array( $active_disputes ) || count( $active_disputes ) === 0 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$dispute_currencies = array_unique( array_column( $active_disputes, 'currency' ) );
|
||||
|
||||
// If multiple currencies, use simple task title without total amounts.
|
||||
if ( count( $dispute_currencies ) > 1 ) {
|
||||
return sprintf(
|
||||
// translators: %d is a number greater than 1.
|
||||
__( 'Respond to %d active disputes', 'woocommerce-payments' ),
|
||||
count( $active_disputes )
|
||||
);
|
||||
}
|
||||
|
||||
// If single currency, calculate total amount and include in task title.
|
||||
$dispute_total = array_reduce(
|
||||
$active_disputes,
|
||||
function ( $total, $dispute ) {
|
||||
return $total + ( $dispute['amount'] ?? 0 );
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
$dispute_total_formatted = WC_Payments_Utils::format_currency(
|
||||
WC_Payments_Utils::interpret_stripe_amount( $dispute_total, $dispute_currencies[0] ),
|
||||
$dispute_currencies[0]
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
/* translators: %d is a number greater than 1. %s is a formatted amount, eg: $10.00 */
|
||||
__( 'Respond to %1$d active disputes for a total of %2$s', 'woocommerce-payments' ),
|
||||
count( $active_disputes ),
|
||||
$dispute_total_formatted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent list ID.
|
||||
*
|
||||
* This function prior to WC 6.4.0 was abstract and so is needed for backwards compatibility.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_parent_id() {
|
||||
// WC 6.4.0 compatibility.
|
||||
if ( is_callable( 'parent::get_parent_id' ) ) {
|
||||
return parent::get_parent_id();
|
||||
}
|
||||
|
||||
return 'extended';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task subtitle.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_additional_info() {
|
||||
if ( count( (array) $this->disputes_due_within_7d ) === 1 ) {
|
||||
$local_timezone = new \DateTimeZone( wp_timezone_string() );
|
||||
$dispute = $this->disputes_due_within_7d[0];
|
||||
$due_by_local_time = ( new \DateTime( $dispute['due_by'] ) )->setTimezone( $local_timezone );
|
||||
// Sum of Unix timestamp and timezone offset in seconds.
|
||||
$due_by_ts = $due_by_local_time->getTimestamp() + $due_by_local_time->getOffset();
|
||||
|
||||
if ( count( (array) $this->disputes_due_within_1d ) > 0 ) {
|
||||
return sprintf(
|
||||
/* translators: %s is time, eg: 11:59 PM */
|
||||
__( 'Respond today by %s', 'woocommerce-payments' ),
|
||||
date_i18n( wc_time_format(), $due_by_ts )
|
||||
);
|
||||
}
|
||||
|
||||
$now = new \DateTime( 'now', $local_timezone );
|
||||
$diff = $now->diff( $due_by_local_time );
|
||||
|
||||
return sprintf(
|
||||
/* translators: %1$s is a date, eg: Jan 1, 2021. %2$s is the number of days left, eg: 2 days. */
|
||||
__( 'By %1$s – %2$s left to respond', 'woocommerce-payments' ),
|
||||
date_i18n( wc_date_format(), $due_by_ts ),
|
||||
/* translators: %s is the number of days left, e.g. 1 day. */
|
||||
sprintf( _n( '%d day', '%d days', $diff->days, 'woocommerce-payments' ), $diff->days )
|
||||
);
|
||||
}
|
||||
|
||||
if ( count( (array) $this->disputes_due_within_1d ) > 0 ) {
|
||||
return sprintf(
|
||||
/* translators: %d is the number of disputes. */
|
||||
__(
|
||||
'Final day to respond to %d of the disputes',
|
||||
'woocommerce-payments'
|
||||
),
|
||||
count( (array) $this->disputes_due_within_1d )
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: %d is the number of disputes. */
|
||||
__(
|
||||
'Last week to respond to %d of the disputes',
|
||||
'woocommerce-payments'
|
||||
),
|
||||
count( (array) $this->disputes_due_within_7d )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task's action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
$disputes = $this->disputes_due_within_7d;
|
||||
if ( count( (array) $disputes ) === 1 ) {
|
||||
$dispute = $disputes[0];
|
||||
return admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'page' => 'wc-admin',
|
||||
'path' => '%2Fpayments%2Ftransactions%2Fdetails',
|
||||
'id' => $dispute['charge_id'],
|
||||
],
|
||||
'admin.php'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'page' => 'wc-admin',
|
||||
'path' => '%2Fpayments%2Fdisputes',
|
||||
'filter' => 'awaiting_response',
|
||||
],
|
||||
'admin.php'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated time to complete the task.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the task is completed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the task is visible.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
if ( null === $this->disputes_needing_response ) {
|
||||
$this->fetch_relevant_disputes();
|
||||
}
|
||||
return count( (array) $this->disputes_due_within_7d ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disputes needing response within the given number of days.
|
||||
*
|
||||
* @param int $num_days Number of days in the future to check for disputes needing response.
|
||||
*
|
||||
* @return array Disputes needing response within the given number of days.
|
||||
*/
|
||||
private function get_disputes_needing_response_within_days( $num_days ) {
|
||||
$to_return = [];
|
||||
|
||||
$active_disputes = $this->get_disputes_needing_response();
|
||||
if ( ! is_array( $active_disputes ) ) {
|
||||
return $to_return;
|
||||
}
|
||||
|
||||
foreach ( $active_disputes as $dispute ) {
|
||||
if ( ! $dispute['due_by'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compare UTC times.
|
||||
$now_utc = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
|
||||
$due_by_utc = new \DateTime( $dispute['due_by'], new \DateTimeZone( 'UTC' ) );
|
||||
|
||||
if ( $now_utc > $due_by_utc ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $now_utc->diff( $due_by_utc );
|
||||
// If the dispute is due within the given number of days, add it to the list.
|
||||
if ( $diff->days <= $num_days ) {
|
||||
$to_return[] = $dispute;
|
||||
}
|
||||
}
|
||||
|
||||
return $to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets disputes awaiting a response. ie have a 'needs_response' or 'warning_needs_response' status.
|
||||
*
|
||||
* @return array|null Array of disputes awaiting a response. Null on failure.
|
||||
*/
|
||||
private function get_disputes_needing_response() {
|
||||
if ( null !== $this->disputes_needing_response ) {
|
||||
return $this->disputes_needing_response;
|
||||
}
|
||||
|
||||
$this->disputes_needing_response = $this->database_cache->get_or_add(
|
||||
Database_Cache::ACTIVE_DISPUTES_KEY,
|
||||
function () {
|
||||
try {
|
||||
$response = $this->api_client->get_disputes(
|
||||
[
|
||||
'pagesize' => 50,
|
||||
'search' => [ 'warning_needs_response', 'needs_response' ],
|
||||
]
|
||||
);
|
||||
} catch ( \Exception $e ) {
|
||||
// Ensure an array is always returned, even if the API call fails.
|
||||
return [];
|
||||
}
|
||||
|
||||
$active_disputes = $response['data'] ?? [];
|
||||
|
||||
// sort by due_by date ascending.
|
||||
usort(
|
||||
$active_disputes,
|
||||
function ( $a, $b ) {
|
||||
$a_due_by = new \DateTime( $a['due_by'] );
|
||||
$b_due_by = new \DateTime( $b['due_by'] );
|
||||
|
||||
return $a_due_by <=> $b_due_by;
|
||||
}
|
||||
);
|
||||
|
||||
return $active_disputes;
|
||||
},
|
||||
'is_array'
|
||||
);
|
||||
|
||||
return $this->disputes_needing_response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Tracker
|
||||
*
|
||||
* @package WooCommerce\Payments
|
||||
*/
|
||||
|
||||
namespace WCPay;
|
||||
|
||||
defined( 'ABSPATH' ) || exit; // block direct access.
|
||||
|
||||
/**
|
||||
* An API for adding track events that will get unloaded
|
||||
* at a later stage and pushed to WP.com.
|
||||
*/
|
||||
class Tracker {
|
||||
/**
|
||||
* A key value array event_name => properties.
|
||||
*
|
||||
* @var array $admin_events
|
||||
*/
|
||||
protected static $admin_events = [];
|
||||
|
||||
/**
|
||||
* Record an event in Tracks
|
||||
*
|
||||
* This only sends track events for admin logged in users. This is a limitation of the
|
||||
* WC_Tracks and related classes.
|
||||
*
|
||||
* The event name will be prefixed before sending it see Core_Tracks_Wrapper.
|
||||
*
|
||||
* @param string $event_name The name of the event.
|
||||
* @param array $properties Custom properties to send with the event.
|
||||
*/
|
||||
public static function track_admin( $event_name, $properties = [] ) {
|
||||
self::$admin_events[ $event_name ] = $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a track event.
|
||||
*
|
||||
* @param string $event_name The name of the event that should be removed.
|
||||
*/
|
||||
public static function remove_admin_event( $event_name ) {
|
||||
if ( isset( self::$admin_events ) ) {
|
||||
unset( self::$admin_events[ $event_name ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a track event.
|
||||
*/
|
||||
public static function get_admin_events() {
|
||||
return self::$admin_events;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Core Tracks Wrapper
|
||||
*
|
||||
* @package WooCommerce\Payments
|
||||
*/
|
||||
|
||||
namespace WCPay;
|
||||
|
||||
use WC_Tracks;
|
||||
|
||||
defined( 'ABSPATH' ) || exit; // block direct access.
|
||||
|
||||
/**
|
||||
* Moves all events from Tracker to Tracks loader
|
||||
*/
|
||||
function record_tracker_events() {
|
||||
foreach ( Tracker::get_admin_events() as $event => $properties ) {
|
||||
WC_Tracks::record_event( $event, $properties );
|
||||
Tracker::remove_admin_event( $event );
|
||||
}
|
||||
}
|
||||
|
||||
// Loaded on admin_init to ensure that we are in admin and that WC_Tracks is loaded.
|
||||
add_action(
|
||||
'admin_init',
|
||||
function () {
|
||||
if ( ! class_exists( 'WC_Tracks' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move all events with priority 1 just before the admin_footer hook adds footer pixels.
|
||||
add_action( 'admin_footer', 'WCPay\record_tracker_events', 1 );
|
||||
|
||||
/**
|
||||
* Send all events that were not handled in `admin_footer`.
|
||||
*
|
||||
* Between shutdown and admin footer many things can happen. Admin footer loads
|
||||
* scripts in the markup images that will call tracks from the browsers
|
||||
* side (which means it's faster as we're not doing network calls to wp.com server
|
||||
* side). Anything that is added afterward must be sent from the
|
||||
* server, but doing it on shutdown means it's not blocking anything.
|
||||
*/
|
||||
add_action( 'shutdown', __NAMESPACE__ . '\\record_tracker_events', 1 );
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user