Initial commit
diff --git a/includes/class-wc-stripe-apple-pay-registration.php b/includes/class-wc-stripe-apple-pay-registration.php
new file mode 100644
index 0000000..9df6124
--- /dev/null
+++ b/includes/class-wc-stripe-apple-pay-registration.php
@@ -0,0 +1,417 @@
+<?php
+/**
+ * Stripe Apple Pay Registration Class.
+ *
+ * @since 4.0.6
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+class WC_Stripe_Apple_Pay_Registration {
+
+ const DOMAIN_ASSOCIATION_FILE_NAME = 'apple-developer-merchantid-domain-association';
+ const DOMAIN_ASSOCIATION_FILE_DIR = '.well-known';
+
+ /**
+ * Enabled.
+ *
+ * @var
+ */
+ public $stripe_settings;
+
+ /**
+ * Apple Pay Domain Set.
+ *
+ * @var bool
+ */
+ public $apple_pay_domain_set;
+
+ /**
+ * Current domain name.
+ *
+ * @var bool
+ */
+ private $domain_name;
+
+ /**
+ * Stores Apple Pay domain verification issues.
+ *
+ * @var string
+ */
+ public $apple_pay_verify_notice;
+
+ public function __construct() {
+ add_action( 'init', [ $this, 'add_domain_association_rewrite_rule' ] );
+ add_action( 'admin_init', [ $this, 'verify_domain_on_domain_name_change' ] );
+ add_action( 'admin_notices', [ $this, 'admin_notices' ] );
+ add_filter( 'query_vars', [ $this, 'whitelist_domain_association_query_param' ], 10, 1 );
+ add_action( 'parse_request', [ $this, 'parse_domain_association_request' ], 10, 1 );
+
+ add_action( 'woocommerce_stripe_updated', [ $this, 'verify_domain_if_configured' ] );
+ add_action( 'add_option_woocommerce_stripe_settings', [ $this, 'verify_domain_on_new_settings' ], 10, 2 );
+ add_action( 'update_option_woocommerce_stripe_settings', [ $this, 'verify_domain_on_updated_settings' ], 10, 2 );
+
+ $this->stripe_settings = get_option( 'woocommerce_stripe_settings', [] );
+ $this->domain_name = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : str_replace( array( 'https://', 'http://' ), '', get_site_url() ); // @codingStandardsIgnoreLine
+ $this->apple_pay_domain_set = 'yes' === $this->get_option( 'apple_pay_domain_set', 'no' );
+ $this->apple_pay_verify_notice = '';
+ }
+
+ /**
+ * Gets the Stripe settings.
+ *
+ * @since 4.0.6
+ * @param string $setting
+ * @param string default
+ * @return string $setting_value
+ */
+ public function get_option( $setting = '', $default = '' ) {
+ if ( empty( $this->stripe_settings ) ) {
+ return $default;
+ }
+
+ if ( ! empty( $this->stripe_settings[ $setting ] ) ) {
+ return $this->stripe_settings[ $setting ];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Whether the gateway and Payment Request Button (prerequisites for Apple Pay) are enabled.
+ *
+ * @since 4.5.4
+ * @return string Whether Apple Pay required settings are enabled.
+ */
+ private function is_enabled() {
+ $stripe_enabled = 'yes' === $this->get_option( 'enabled', 'no' );
+ $payment_request_button_enabled = 'yes' === $this->get_option( 'payment_request', 'yes' );
+
+ return $stripe_enabled && $payment_request_button_enabled;
+ }
+
+ /**
+ * Gets the Stripe secret key for the current mode.
+ *
+ * @since 4.5.3
+ * @version 4.9.0
+ * @return string Secret key.
+ */
+ private function get_secret_key() {
+ return $this->get_option( 'secret_key' );
+ }
+
+ /**
+ * Trigger Apple Pay registration upon domain name change.
+ *
+ * @since 4.9.0
+ */
+ public function verify_domain_on_domain_name_change() {
+ if ( $this->domain_name !== $this->get_option( 'apple_pay_verified_domain' ) ) {
+ $this->verify_domain_if_configured();
+ }
+ }
+
+ /**
+ * Vefifies if hosted domain association file is up to date
+ * with the file from the plugin directory.
+ *
+ * @since 4.9.0
+ * @return bool Whether file is up to date or not.
+ */
+ private function verify_hosted_domain_association_file_is_up_to_date() {
+ // Contents of domain association file from plugin dir.
+ $new_contents = @file_get_contents( WC_STRIPE_PLUGIN_PATH . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME ); // @codingStandardsIgnoreLine
+ // Get file contents from local path and remote URL and check if either of which matches.
+ $fullpath = untrailingslashit( ABSPATH ) . '/' . self::DOMAIN_ASSOCIATION_FILE_DIR . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME;
+ $local_contents = @file_get_contents( $fullpath ); // @codingStandardsIgnoreLine
+ $url = get_site_url() . '/' . self::DOMAIN_ASSOCIATION_FILE_DIR . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME;
+ $response = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
+ $remote_contents = @wp_remote_retrieve_body( $response ); // @codingStandardsIgnoreLine
+
+ return $local_contents === $new_contents || $remote_contents === $new_contents;
+ }
+
+ /**
+ * Copies and overwrites domain association file.
+ *
+ * @since 4.9.0
+ * @return null|string Error message.
+ */
+ private function copy_and_overwrite_domain_association_file() {
+ $well_known_dir = untrailingslashit( ABSPATH ) . '/' . self::DOMAIN_ASSOCIATION_FILE_DIR;
+ $fullpath = $well_known_dir . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME;
+
+ if ( ! file_exists( $well_known_dir ) ) {
+ if ( ! @mkdir( $well_known_dir, 0755 ) ) { // @codingStandardsIgnoreLine
+ return __( 'Unable to create domain association folder to domain root.', 'woocommerce-gateway-stripe' );
+ }
+ }
+
+ if ( ! @copy( WC_STRIPE_PLUGIN_PATH . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME, $fullpath ) ) { // @codingStandardsIgnoreLine
+ return __( 'Unable to copy domain association file to domain root.', 'woocommerce-gateway-stripe' );
+ }
+ }
+
+ /**
+ * Updates the Apple Pay domain association file.
+ * Reports failure only if file isn't already being served properly.
+ *
+ * @since 4.9.0
+ */
+ public function update_domain_association_file() {
+ if ( $this->verify_hosted_domain_association_file_is_up_to_date() ) {
+ return;
+ }
+
+ $error_message = $this->copy_and_overwrite_domain_association_file();
+
+ if ( isset( $error_message ) ) {
+ $url = get_site_url() . '/' . self::DOMAIN_ASSOCIATION_FILE_DIR . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME;
+ WC_Stripe_Logger::log(
+ 'Error: ' . $error_message . ' ' .
+ /* translators: expected domain association file URL */
+ sprintf( __( 'To enable Apple Pay, domain association file must be hosted at %s.', 'woocommerce-gateway-stripe' ), $url )
+ );
+ } else {
+ WC_Stripe_Logger::log( __( 'Domain association file updated.', 'woocommerce-gateway-stripe' ) );
+ }
+ }
+
+ /**
+ * Adds a rewrite rule for serving the domain association file from the proper location.
+ */
+ public function add_domain_association_rewrite_rule() {
+ $regex = '^\\' . self::DOMAIN_ASSOCIATION_FILE_DIR . '\/' . self::DOMAIN_ASSOCIATION_FILE_NAME . '$';
+ $redirect = 'index.php?' . self::DOMAIN_ASSOCIATION_FILE_NAME . '=1';
+
+ add_rewrite_rule( $regex, $redirect, 'top' );
+ }
+
+ /**
+ * Add to the list of publicly allowed query variables.
+ *
+ * @param array $query_vars - provided public query vars.
+ * @return array Updated public query vars.
+ */
+ public function whitelist_domain_association_query_param( $query_vars ) {
+ $query_vars[] = self::DOMAIN_ASSOCIATION_FILE_NAME;
+ return $query_vars;
+ }
+
+ /**
+ * Serve domain association file when proper query param is provided.
+ *
+ * @param WP WordPress environment object.
+ */
+ public function parse_domain_association_request( $wp ) {
+ if (
+ ! isset( $wp->query_vars[ self::DOMAIN_ASSOCIATION_FILE_NAME ] ) ||
+ '1' !== $wp->query_vars[ self::DOMAIN_ASSOCIATION_FILE_NAME ]
+ ) {
+ return;
+ }
+
+ $path = WC_STRIPE_PLUGIN_PATH . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME;
+ header( 'Content-Type: text/plain;charset=utf-8' );
+ echo esc_html( file_get_contents( $path ) );
+ exit;
+ }
+
+ /**
+ * Makes request to register the domain with Stripe/Apple Pay.
+ *
+ * @since 3.1.0
+ * @version 4.9.0
+ * @param string $secret_key
+ */
+ private function make_domain_registration_request( $secret_key ) {
+ if ( empty( $secret_key ) ) {
+ throw new Exception( __( 'Unable to verify domain - missing secret key.', 'woocommerce-gateway-stripe' ) );
+ }
+
+ $endpoint = 'https://api.stripe.com/v1/apple_pay/domains';
+
+ $data = [
+ 'domain_name' => $this->domain_name,
+ ];
+
+ $headers = [
+ 'User-Agent' => 'WooCommerce Stripe Apple Pay',
+ 'Authorization' => 'Bearer ' . $secret_key,
+ ];
+
+ $response = wp_remote_post(
+ $endpoint,
+ [
+ 'headers' => $headers,
+ 'body' => http_build_query( $data ),
+ 'timeout' => 30,
+ ]
+ );
+
+ if ( is_wp_error( $response ) ) {
+ /* translators: error message */
+ throw new Exception( sprintf( __( 'Unable to verify domain - %s', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) );
+ }
+
+ if ( 200 !== $response['response']['code'] ) {
+ $parsed_response = json_decode( $response['body'] );
+
+ $this->apple_pay_verify_notice = $parsed_response->error->message;
+
+ /* translators: error message */
+ throw new Exception( sprintf( __( 'Unable to verify domain - %s', 'woocommerce-gateway-stripe' ), $parsed_response->error->message ) );
+ }
+ }
+
+ /**
+ * Processes the Apple Pay domain verification.
+ *
+ * @since 3.1.0
+ * @version 4.5.4
+ *
+ * @param string $secret_key
+ *
+ * @return bool Whether domain verification succeeded.
+ */
+ public function register_domain_with_apple( $secret_key ) {
+ try {
+ $this->make_domain_registration_request( $secret_key );
+
+ // No errors to this point, verification success!
+ $this->stripe_settings['apple_pay_verified_domain'] = $this->domain_name;
+ $this->stripe_settings['apple_pay_domain_set'] = 'yes';
+ $this->apple_pay_domain_set = true;
+
+ update_option( 'woocommerce_stripe_settings', $this->stripe_settings );
+
+ WC_Stripe_Logger::log( 'Your domain has been verified with Apple Pay!' );
+
+ return true;
+
+ } catch ( Exception $e ) {
+ $this->stripe_settings['apple_pay_verified_domain'] = $this->domain_name;
+ $this->stripe_settings['apple_pay_domain_set'] = 'no';
+ $this->apple_pay_domain_set = false;
+
+ update_option( 'woocommerce_stripe_settings', $this->stripe_settings );
+
+ WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
+
+ return false;
+ }
+ }
+
+ /**
+ * Process the Apple Pay domain verification if proper settings are configured.
+ *
+ * @since 4.5.4
+ * @version 4.9.0
+ */
+ public function verify_domain_if_configured() {
+ $secret_key = $this->get_secret_key();
+
+ if ( ! $this->is_enabled() || empty( $secret_key ) ) {
+ return;
+ }
+
+ // Ensure that domain association file will be served.
+ flush_rewrite_rules();
+
+ // The rewrite rule method doesn't work if permalinks are set to Plain.
+ // Create/update domain association file by copying it from the plugin folder as a fallback.
+ $this->update_domain_association_file();
+
+ // Register the domain with Apple Pay.
+ $verification_complete = $this->register_domain_with_apple( $secret_key );
+
+ // Show/hide notes if necessary.
+ WC_Stripe_Inbox_Notes::notify_on_apple_pay_domain_verification( $verification_complete );
+ }
+
+ /**
+ * Conditionally process the Apple Pay domain verification after settings are initially set.
+ *
+ * @since 4.5.4
+ * @version 4.5.4
+ */
+ public function verify_domain_on_new_settings( $option, $settings ) {
+ $this->verify_domain_on_updated_settings( [], $settings );
+ }
+
+ /**
+ * Conditionally process the Apple Pay domain verification after settings are updated.
+ *
+ * @since 4.5.3
+ * @version 4.5.4
+ */
+ public function verify_domain_on_updated_settings( $prev_settings, $settings ) {
+ // Grab previous state and then update cached settings.
+ $this->stripe_settings = $prev_settings;
+ $prev_secret_key = $this->get_secret_key();
+ $prev_is_enabled = $this->is_enabled();
+ $this->stripe_settings = $settings;
+
+ // If Stripe or Payment Request Button wasn't enabled (or secret key was different) then might need to verify now.
+ if ( ! $prev_is_enabled || ( $this->get_secret_key() !== $prev_secret_key ) ) {
+ $this->verify_domain_if_configured();
+ }
+ }
+
+ /**
+ * Display any admin notices to the user.
+ *
+ * @since 4.0.6
+ */
+ public function admin_notices() {
+ if ( ! $this->is_enabled() ) {
+ return;
+ }
+
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ return;
+ }
+
+ $empty_notice = empty( $this->apple_pay_verify_notice );
+ if ( $empty_notice && ( $this->apple_pay_domain_set || empty( $this->secret_key ) ) ) {
+ return;
+ }
+
+ /**
+ * Apple pay is enabled by default and domain verification initializes
+ * when setting screen is displayed. So if domain verification is not set,
+ * something went wrong so lets notify user.
+ */
+ $allowed_html = [
+ 'a' => [
+ 'href' => [],
+ 'title' => [],
+ ],
+ ];
+ $verification_failed_without_error = __( 'Apple Pay domain verification failed.', 'woocommerce-gateway-stripe' );
+ $verification_failed_with_error = __( 'Apple Pay domain verification failed with the following error:', 'woocommerce-gateway-stripe' );
+ $check_log_text = sprintf(
+ /* translators: 1) HTML anchor open tag 2) HTML anchor closing tag */
+ esc_html__( 'Please check the %1$slogs%2$s for more details on this issue. Logging must be enabled to see recorded logs.', 'woocommerce-gateway-stripe' ),
+ '<a href="' . admin_url( 'admin.php?page=wc-status&tab=logs' ) . '">',
+ '</a>'
+ );
+
+ ?>
+ <div class="error stripe-apple-pay-message">
+ <?php if ( $empty_notice ) : ?>
+ <p><?php echo esc_html( $verification_failed_without_error ); ?></p>
+ <?php else : ?>
+ <p><?php echo esc_html( $verification_failed_with_error ); ?></p>
+ <p><i><?php echo wp_kses( make_clickable( esc_html( $this->apple_pay_verify_notice ) ), $allowed_html ); ?></i></p>
+ <?php endif; ?>
+ <p><?php echo $check_log_text; ?></p>
+ </div>
+ <?php
+ }
+}
+
+new WC_Stripe_Apple_Pay_Registration();