Initial commit
diff --git a/includes/admin/class-wc-rest-stripe-settings-controller.php b/includes/admin/class-wc-rest-stripe-settings-controller.php
new file mode 100644
index 0000000..82492d4
--- /dev/null
+++ b/includes/admin/class-wc-rest-stripe-settings-controller.php
@@ -0,0 +1,593 @@
+<?php
+/**
+ * Class WC_REST_Stripe_Settings_Controller
+ */
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * REST controller for settings.
+ */
+class WC_REST_Stripe_Settings_Controller extends WC_Stripe_REST_Base_Controller {
+
+ /**
+ * Endpoint path.
+ *
+ * @var string
+ */
+ protected $rest_base = 'wc_stripe/settings';
+
+ /**
+ * Stripe payment gateway.
+ *
+ * @var WC_Gateway_Stripe
+ */
+ private $gateway;
+
+ /**
+ * Constructor.
+ *
+ * @param WC_Gateway_Stripe $gateway Stripe payment gateway.
+ */
+ public function __construct( WC_Gateway_Stripe $gateway ) {
+ $this->gateway = $gateway;
+ }
+
+ /**
+ * Configure REST API routes.
+ */
+ public function register_routes() {
+ $form_fields = $this->gateway->get_form_fields();
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ [
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => [ $this, 'get_settings' ],
+ 'permission_callback' => [ $this, 'check_permission' ],
+ ]
+ );
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ [
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => [ $this, 'update_settings' ],
+ 'permission_callback' => [ $this, 'check_permission' ],
+ 'args' => [
+ 'is_stripe_enabled' => [
+ 'description' => __( 'If Stripe should be enabled.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'is_test_mode_enabled' => [
+ 'description' => __( 'Stripe test mode setting.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'title' => [
+ 'description' => __( 'Stripe display title.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'string',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'title_upe' => [
+ 'description' => __( 'New checkout experience title.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'string',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'description' => [
+ 'description' => __( 'Stripe display description.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'string',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'enabled_payment_method_ids' => [
+ 'description' => __( 'Payment method IDs that should be enabled. Other methods will be disabled.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'array',
+ 'items' => [
+ 'type' => 'string',
+ 'enum' => $this->gateway->get_upe_available_payment_methods(),
+ ],
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'is_payment_request_enabled' => [
+ 'description' => __( 'If Stripe express checkouts should be enabled.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'payment_request_button_type' => [
+ 'description' => __( 'Express checkout button types.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'string',
+ 'enum' => array_keys( $form_fields['payment_request_button_type']['options'] ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'payment_request_button_theme' => [
+ 'description' => __( 'Express checkout button themes.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'string',
+ 'enum' => array_keys( $form_fields['payment_request_button_theme']['options'] ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'payment_request_button_size' => [
+ 'description' => __( 'Express checkout button sizes.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'string',
+ // it can happen that `$form_fields['payment_request_button_size']` is empty (in tests) - fixing temporarily.
+ 'enum' => array_keys( isset( $form_fields['payment_request_button_size']['options'] ) ? $form_fields['payment_request_button_size']['options'] : [] ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'payment_request_button_locations' => [
+ 'description' => __( 'Express checkout locations that should be enabled.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'array',
+ 'items' => [
+ 'type' => 'string',
+ 'enum' => array_keys( $form_fields['payment_request_button_locations']['options'] ),
+ ],
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'is_manual_capture_enabled' => [
+ 'description' => __( 'If manual capture of charges should be enabled.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'is_saved_cards_enabled' => [
+ 'description' => __( 'If "Saved cards" should be enabled.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'is_separate_card_form_enabled' => [
+ 'description' => __( 'If credit card number field, expiry date field, and CVC field should be separate.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'statement_descriptor' => [
+ 'description' => __( 'Bank account descriptor to be displayed in customers\' bank accounts.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'string',
+ 'validate_callback' => [ $this, 'validate_regular_statement_descriptor' ],
+ ],
+ 'is_short_statement_descriptor_enabled' => [
+ 'description' => __( 'When enabled, we\'ll include the order number for card and express checkout transactions.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ 'short_statement_descriptor' => [
+ 'description' => __( 'We\'ll use the short version in combination with the customer order number.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'string',
+ 'validate_callback' => [ $this, 'validate_short_statement_descriptor' ],
+ ],
+ 'is_debug_log_enabled' => [
+ 'description' => __( 'When enabled, payment error logs will be saved to WooCommerce > Status > Logs.', 'woocommerce-gateway-stripe' ),
+ 'type' => 'boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Validate the regular statement descriptor.
+ *
+ * @param mixed $value The value being validated.
+ * @param WP_REST_Request $request The request made.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+ public function validate_regular_statement_descriptor( $value, $request, $param ) {
+ return $this->validate_statement_descriptor( $value, $request, $param, 22 );
+ }
+
+ /**
+ * Validate the short statement descriptor.
+ *
+ * @param mixed $value The value being validated.
+ * @param WP_REST_Request $request The request made.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+ public function validate_short_statement_descriptor( $value, $request, $param ) {
+ $is_short_account_statement_enabled = $request->get_param( 'is_short_statement_descriptor_enabled' );
+
+ // bypassing validation to avoid errors in the client, it won't be updated under this condition
+ if ( ! $is_short_account_statement_enabled ) {
+ return true;
+ }
+
+ return $this->validate_statement_descriptor( $value, $request, $param, 10 );
+ }
+
+ /**
+ * Validate the statement descriptor argument.
+ *
+ * @since 4.7.0
+ *
+ * @param mixed $value The value being validated.
+ * @param WP_REST_Request $request The request made.
+ * @param string $param The parameter name, used in error messages.
+ * @param int $max_length Maximum statement length.
+ * @return true|WP_Error
+ */
+ public function validate_statement_descriptor( $value, $request, $param, $max_length ) {
+ $string_validation_result = rest_validate_request_arg( $value, $request, $param );
+ if ( true !== $string_validation_result ) {
+ return $string_validation_result;
+ }
+
+ // Relaxing validation because it's blocking the user from saving it when they're on another tab of the settings screen
+ // TODO: work that out with either a UX approach or handling the validations of each tab separately
+ if ( '' === $value ) {
+ return true;
+ }
+
+ try {
+ $this->gateway->validate_account_statement_descriptor_field( $param, $value, $max_length );
+ } catch ( Exception $exception ) {
+ return new WP_Error(
+ 'rest_invalid_pattern',
+ $exception->getMessage()
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieve settings.
+ *
+ * @return WP_REST_Response
+ */
+ public function get_settings() {
+ return new WP_REST_Response(
+ [
+ /* Settings > General */
+ 'is_stripe_enabled' => $this->gateway->is_enabled(),
+ 'is_test_mode_enabled' => $this->gateway->is_in_test_mode(),
+ 'title' => $this->gateway->get_validated_option( 'title' ),
+ 'title_upe' => $this->gateway->get_validated_option( 'title_upe' ),
+ 'description' => $this->gateway->get_validated_option( 'description' ),
+
+ /* Settings > Payments accepted on checkout */
+ 'enabled_payment_method_ids' => $this->gateway->get_upe_enabled_payment_method_ids(),
+ 'available_payment_method_ids' => $this->gateway->get_upe_available_payment_methods(),
+
+ /* Settings > Express checkouts */
+ 'is_payment_request_enabled' => 'yes' === $this->gateway->get_option( 'payment_request' ),
+ 'payment_request_button_type' => $this->gateway->get_validated_option( 'payment_request_button_type' ),
+ 'payment_request_button_theme' => $this->gateway->get_validated_option( 'payment_request_button_theme' ),
+ 'payment_request_button_size' => $this->gateway->get_validated_option( 'payment_request_button_size' ),
+ 'payment_request_button_locations' => $this->gateway->get_validated_option( 'payment_request_button_locations' ),
+
+ /* Settings > Payments & transactions */
+ 'is_manual_capture_enabled' => ! $this->gateway->is_automatic_capture_enabled(),
+ 'is_saved_cards_enabled' => 'yes' === $this->gateway->get_option( 'saved_cards' ),
+ 'is_separate_card_form_enabled' => 'no' === $this->gateway->get_option( 'inline_cc_form' ),
+ 'statement_descriptor' => $this->gateway->get_validated_option( 'statement_descriptor' ),
+ 'is_short_statement_descriptor_enabled' => 'yes' === $this->gateway->get_option( 'is_short_statement_descriptor_enabled' ),
+ 'short_statement_descriptor' => $this->gateway->get_validated_option( 'short_statement_descriptor' ),
+
+ /* Settings > Advanced settings */
+ 'is_debug_log_enabled' => 'yes' === $this->gateway->get_option( 'logging' ),
+ 'is_upe_enabled' => WC_Stripe_Feature_Flags::is_upe_checkout_enabled(),
+ ]
+ );
+ }
+
+ /**
+ * Update settings.
+ *
+ * @param WP_REST_Request $request Full data about the request.
+ */
+ public function update_settings( WP_REST_Request $request ) {
+ /* Settings > General */
+ $this->update_is_stripe_enabled( $request );
+ $this->update_title( $request );
+ $this->update_title_upe( $request );
+ $this->update_description( $request );
+ $this->update_is_test_mode_enabled( $request );
+
+ /* Settings > Payments accepted on checkout */
+ $this->update_enabled_payment_methods( $request );
+
+ /* Settings > Express checkouts */
+ $this->update_is_payment_request_enabled( $request );
+ $this->update_payment_request_settings( $request );
+
+ /* Settings > Payments & transactions */
+ $this->update_is_manual_capture_enabled( $request );
+ $this->update_is_saved_cards_enabled( $request );
+ $this->update_is_separate_card_form_enabled( $request );
+ $this->update_account_statement_descriptor( $request );
+ $this->update_is_short_account_statement_enabled( $request );
+ $this->update_short_account_statement_descriptor( $request );
+
+ /* Settings > Advanced settings */
+ $this->update_is_debug_log_enabled( $request );
+ $this->update_is_upe_enabled( $request );
+
+ return new WP_REST_Response( [], 200 );
+ }
+
+ /**
+ * Updates Stripe enabled status.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_stripe_enabled( WP_REST_Request $request ) {
+ $is_stripe_enabled = $request->get_param( 'is_stripe_enabled' );
+
+ if ( null === $is_stripe_enabled ) {
+ return;
+ }
+
+ if ( $is_stripe_enabled ) {
+ $this->gateway->enable();
+ } else {
+ $this->gateway->disable();
+ }
+ }
+
+ /**
+ * Updates title.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_title( WP_REST_Request $request ) {
+ $title = $request->get_param( 'title' );
+
+ if ( null === $title ) {
+ return;
+ }
+
+ $this->gateway->update_validated_option( 'title', $title );
+ }
+
+ /**
+ * Updates UPE title.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_title_upe( WP_REST_Request $request ) {
+ $title_upe = $request->get_param( 'title_upe' );
+
+ if ( null === $title_upe ) {
+ return;
+ }
+
+ $this->gateway->update_validated_option( 'title_upe', $title_upe );
+ }
+
+ /**
+ * Updates description.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_description( WP_REST_Request $request ) {
+ $description = $request->get_param( 'description' );
+
+ if ( null === $description ) {
+ return;
+ }
+
+ $this->gateway->update_validated_option( 'description', $description );
+ }
+
+ /**
+ * Updates Stripe test mode.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_test_mode_enabled( WP_REST_Request $request ) {
+ $is_test_mode_enabled = $request->get_param( 'is_test_mode_enabled' );
+
+ if ( null === $is_test_mode_enabled ) {
+ return;
+ }
+
+ $this->gateway->update_option( 'testmode', $is_test_mode_enabled ? 'yes' : 'no' );
+ }
+
+ /**
+ * Updates the "payment request" enable/disable settings.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_payment_request_enabled( WP_REST_Request $request ) {
+ $is_payment_request_enabled = $request->get_param( 'is_payment_request_enabled' );
+
+ if ( null === $is_payment_request_enabled ) {
+ return;
+ }
+
+ $this->gateway->update_option( 'payment_request', $is_payment_request_enabled ? 'yes' : 'no' );
+ }
+
+ /**
+ * Updates manual capture.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_manual_capture_enabled( WP_REST_Request $request ) {
+ $is_manual_capture_enabled = $request->get_param( 'is_manual_capture_enabled' );
+
+ if ( null === $is_manual_capture_enabled ) {
+ return;
+ }
+
+ $this->gateway->update_option( 'capture', $is_manual_capture_enabled ? 'no' : 'yes' );
+ }
+
+ /**
+ * Updates "saved cards" feature.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_saved_cards_enabled( WP_REST_Request $request ) {
+ $is_saved_cards_enabled = $request->get_param( 'is_saved_cards_enabled' );
+
+ if ( null === $is_saved_cards_enabled ) {
+ return;
+ }
+
+ $this->gateway->update_option( 'saved_cards', $is_saved_cards_enabled ? 'yes' : 'no' );
+ }
+
+ /**
+ * Updates "saved cards" feature.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_separate_card_form_enabled( WP_REST_Request $request ) {
+ $is_separate_card_form_enabled = $request->get_param( 'is_separate_card_form_enabled' );
+
+ if ( null === $is_separate_card_form_enabled ) {
+ return;
+ }
+
+ $this->gateway->update_option( 'inline_cc_form', $is_separate_card_form_enabled ? 'no' : 'yes' );
+ }
+
+ /**
+ * Updates account statement descriptor.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_account_statement_descriptor( WP_REST_Request $request ) {
+ $account_statement_descriptor = $request->get_param( 'statement_descriptor' );
+
+ if ( null === $account_statement_descriptor ) {
+ return;
+ }
+
+ $this->gateway->update_validated_option( 'statement_descriptor', $account_statement_descriptor );
+ }
+
+ /**
+ * Updates whether short account statement should be used.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_short_account_statement_enabled( WP_REST_Request $request ) {
+ $is_short_account_statement_enabled = $request->get_param( 'is_short_statement_descriptor_enabled' );
+
+ if ( null === $is_short_account_statement_enabled ) {
+ return;
+ }
+
+ $this->gateway->update_option( 'is_short_statement_descriptor_enabled', $is_short_account_statement_enabled ? 'yes' : 'no' );
+ }
+
+ /**
+ * Updates short account statement descriptor.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_short_account_statement_descriptor( WP_REST_Request $request ) {
+ $is_short_account_statement_enabled = $request->get_param( 'is_short_statement_descriptor_enabled' );
+ $short_account_statement_descriptor = $request->get_param( 'short_statement_descriptor' );
+
+ // since we're bypassing the validation on the same condition, we shouldn't update it
+ if ( ! $is_short_account_statement_enabled ) {
+ return;
+ }
+
+ if ( null === $short_account_statement_descriptor ) {
+ return;
+ }
+
+ $this->gateway->update_validated_option( 'short_statement_descriptor', $short_account_statement_descriptor );
+ }
+
+ /**
+ * Updates whether debug logging is enabled.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_debug_log_enabled( WP_REST_Request $request ) {
+ $is_debug_log_enabled = $request->get_param( 'is_debug_log_enabled' );
+
+ if ( null === $is_debug_log_enabled ) {
+ return;
+ }
+
+ $this->gateway->update_option( 'logging', $is_debug_log_enabled ? 'yes' : 'no' );
+
+ }
+
+ /**
+ * Updates whether debug logging is enabled.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_is_upe_enabled( WP_REST_Request $request ) {
+ $is_upe_enabled = $request->get_param( 'is_upe_enabled' );
+
+ if ( null === $is_upe_enabled ) {
+ return;
+ }
+
+ $settings = get_option( 'woocommerce_stripe_settings', [] );
+ $settings[ WC_Stripe_Feature_Flags::UPE_CHECKOUT_FEATURE_ATTRIBUTE_NAME ] = $is_upe_enabled ? 'yes' : 'disabled';
+ update_option( 'woocommerce_stripe_settings', $settings );
+
+ // including the class again because otherwise it's not present.
+ if ( WC_Stripe_Inbox_Notes::are_inbox_notes_supported() ) {
+ require_once WC_STRIPE_PLUGIN_PATH . '/includes/notes/class-wc-stripe-upe-availability-note.php';
+ WC_Stripe_UPE_Availability_Note::possibly_delete_note();
+
+ require_once WC_STRIPE_PLUGIN_PATH . '/includes/notes/class-wc-stripe-upe-stripelink-note.php';
+ WC_Stripe_UPE_StripeLink_Note::possibly_delete_note();
+ }
+ }
+
+ /**
+ * Updates appearance attributes of the payment request button.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_payment_request_settings( WP_REST_Request $request ) {
+ $attributes = [
+ 'payment_request_button_type' => 'payment_request_button_type',
+ 'payment_request_button_size' => 'payment_request_button_size',
+ 'payment_request_button_theme' => 'payment_request_button_theme',
+ 'payment_request_button_locations' => 'payment_request_button_locations',
+ ];
+
+ foreach ( $attributes as $request_key => $attribute ) {
+ if ( null === $request->get_param( $request_key ) ) {
+ continue;
+ }
+
+ $value = $request->get_param( $request_key );
+ $this->gateway->update_validated_option( $attribute, $value );
+ }
+ }
+
+ /**
+ * Updates the list of enabled payment methods.
+ *
+ * @param WP_REST_Request $request Request object.
+ */
+ private function update_enabled_payment_methods( WP_REST_Request $request ) {
+ // no need to update the payment methods, if the UPE checkout is not enabled
+ if ( ! WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
+ return;
+ }
+
+ $payment_method_ids_to_enable = $request->get_param( 'enabled_payment_method_ids' );
+
+ if ( null === $payment_method_ids_to_enable ) {
+ return;
+ }
+
+ $available_payment_methods = $this->gateway->get_upe_available_payment_methods();
+
+ $payment_method_ids_to_enable = array_values(
+ array_filter(
+ $payment_method_ids_to_enable,
+ function ( $payment_method ) use ( $available_payment_methods ) {
+ return in_array( $payment_method, $available_payment_methods, true );
+ }
+ )
+ );
+
+ $this->gateway->update_option( 'upe_checkout_experience_accepted_payments', $payment_method_ids_to_enable );
+ }
+}