swissChili | f0cbdc3 | 2023-01-05 17:21:38 -0500 | [diff] [blame] | 1 | <?php |
| 2 | /** |
| 3 | * Stripe Apple Pay Registration Class. |
| 4 | * |
| 5 | * @since 4.0.6 |
| 6 | */ |
| 7 | |
| 8 | if ( ! defined( 'ABSPATH' ) ) { |
| 9 | exit; |
| 10 | } |
| 11 | |
| 12 | class WC_Stripe_Apple_Pay_Registration { |
| 13 | |
| 14 | const DOMAIN_ASSOCIATION_FILE_NAME = 'apple-developer-merchantid-domain-association'; |
| 15 | const DOMAIN_ASSOCIATION_FILE_DIR = '.well-known'; |
| 16 | |
| 17 | /** |
| 18 | * Enabled. |
| 19 | * |
| 20 | * @var |
| 21 | */ |
| 22 | public $stripe_settings; |
| 23 | |
| 24 | /** |
| 25 | * Apple Pay Domain Set. |
| 26 | * |
| 27 | * @var bool |
| 28 | */ |
| 29 | public $apple_pay_domain_set; |
| 30 | |
| 31 | /** |
| 32 | * Current domain name. |
| 33 | * |
| 34 | * @var bool |
| 35 | */ |
| 36 | private $domain_name; |
| 37 | |
| 38 | /** |
| 39 | * Stores Apple Pay domain verification issues. |
| 40 | * |
| 41 | * @var string |
| 42 | */ |
| 43 | public $apple_pay_verify_notice; |
| 44 | |
| 45 | public function __construct() { |
| 46 | add_action( 'init', [ $this, 'add_domain_association_rewrite_rule' ] ); |
| 47 | add_action( 'admin_init', [ $this, 'verify_domain_on_domain_name_change' ] ); |
| 48 | add_action( 'admin_notices', [ $this, 'admin_notices' ] ); |
| 49 | add_filter( 'query_vars', [ $this, 'whitelist_domain_association_query_param' ], 10, 1 ); |
| 50 | add_action( 'parse_request', [ $this, 'parse_domain_association_request' ], 10, 1 ); |
| 51 | |
| 52 | add_action( 'woocommerce_stripe_updated', [ $this, 'verify_domain_if_configured' ] ); |
| 53 | add_action( 'add_option_woocommerce_stripe_settings', [ $this, 'verify_domain_on_new_settings' ], 10, 2 ); |
| 54 | add_action( 'update_option_woocommerce_stripe_settings', [ $this, 'verify_domain_on_updated_settings' ], 10, 2 ); |
| 55 | |
| 56 | $this->stripe_settings = get_option( 'woocommerce_stripe_settings', [] ); |
| 57 | $this->domain_name = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : str_replace( array( 'https://', 'http://' ), '', get_site_url() ); // @codingStandardsIgnoreLine |
| 58 | $this->apple_pay_domain_set = 'yes' === $this->get_option( 'apple_pay_domain_set', 'no' ); |
| 59 | $this->apple_pay_verify_notice = ''; |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Gets the Stripe settings. |
| 64 | * |
| 65 | * @since 4.0.6 |
| 66 | * @param string $setting |
| 67 | * @param string default |
| 68 | * @return string $setting_value |
| 69 | */ |
| 70 | public function get_option( $setting = '', $default = '' ) { |
| 71 | if ( empty( $this->stripe_settings ) ) { |
| 72 | return $default; |
| 73 | } |
| 74 | |
| 75 | if ( ! empty( $this->stripe_settings[ $setting ] ) ) { |
| 76 | return $this->stripe_settings[ $setting ]; |
| 77 | } |
| 78 | |
| 79 | return $default; |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Whether the gateway and Payment Request Button (prerequisites for Apple Pay) are enabled. |
| 84 | * |
| 85 | * @since 4.5.4 |
| 86 | * @return string Whether Apple Pay required settings are enabled. |
| 87 | */ |
| 88 | private function is_enabled() { |
| 89 | $stripe_enabled = 'yes' === $this->get_option( 'enabled', 'no' ); |
| 90 | $payment_request_button_enabled = 'yes' === $this->get_option( 'payment_request', 'yes' ); |
| 91 | |
| 92 | return $stripe_enabled && $payment_request_button_enabled; |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Gets the Stripe secret key for the current mode. |
| 97 | * |
| 98 | * @since 4.5.3 |
| 99 | * @version 4.9.0 |
| 100 | * @return string Secret key. |
| 101 | */ |
| 102 | private function get_secret_key() { |
| 103 | return $this->get_option( 'secret_key' ); |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * Trigger Apple Pay registration upon domain name change. |
| 108 | * |
| 109 | * @since 4.9.0 |
| 110 | */ |
| 111 | public function verify_domain_on_domain_name_change() { |
| 112 | if ( $this->domain_name !== $this->get_option( 'apple_pay_verified_domain' ) ) { |
| 113 | $this->verify_domain_if_configured(); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * Vefifies if hosted domain association file is up to date |
| 119 | * with the file from the plugin directory. |
| 120 | * |
| 121 | * @since 4.9.0 |
| 122 | * @return bool Whether file is up to date or not. |
| 123 | */ |
| 124 | private function verify_hosted_domain_association_file_is_up_to_date() { |
| 125 | // Contents of domain association file from plugin dir. |
| 126 | $new_contents = @file_get_contents( WC_STRIPE_PLUGIN_PATH . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME ); // @codingStandardsIgnoreLine |
| 127 | // Get file contents from local path and remote URL and check if either of which matches. |
| 128 | $fullpath = untrailingslashit( ABSPATH ) . '/' . self::DOMAIN_ASSOCIATION_FILE_DIR . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME; |
| 129 | $local_contents = @file_get_contents( $fullpath ); // @codingStandardsIgnoreLine |
| 130 | $url = get_site_url() . '/' . self::DOMAIN_ASSOCIATION_FILE_DIR . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME; |
| 131 | $response = @wp_remote_get( $url ); // @codingStandardsIgnoreLine |
| 132 | $remote_contents = @wp_remote_retrieve_body( $response ); // @codingStandardsIgnoreLine |
| 133 | |
| 134 | return $local_contents === $new_contents || $remote_contents === $new_contents; |
| 135 | } |
| 136 | |
| 137 | /** |
| 138 | * Copies and overwrites domain association file. |
| 139 | * |
| 140 | * @since 4.9.0 |
| 141 | * @return null|string Error message. |
| 142 | */ |
| 143 | private function copy_and_overwrite_domain_association_file() { |
| 144 | $well_known_dir = untrailingslashit( ABSPATH ) . '/' . self::DOMAIN_ASSOCIATION_FILE_DIR; |
| 145 | $fullpath = $well_known_dir . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME; |
| 146 | |
| 147 | if ( ! file_exists( $well_known_dir ) ) { |
| 148 | if ( ! @mkdir( $well_known_dir, 0755 ) ) { // @codingStandardsIgnoreLine |
| 149 | return __( 'Unable to create domain association folder to domain root.', 'woocommerce-gateway-stripe' ); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | if ( ! @copy( WC_STRIPE_PLUGIN_PATH . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME, $fullpath ) ) { // @codingStandardsIgnoreLine |
| 154 | return __( 'Unable to copy domain association file to domain root.', 'woocommerce-gateway-stripe' ); |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * Updates the Apple Pay domain association file. |
| 160 | * Reports failure only if file isn't already being served properly. |
| 161 | * |
| 162 | * @since 4.9.0 |
| 163 | */ |
| 164 | public function update_domain_association_file() { |
| 165 | if ( $this->verify_hosted_domain_association_file_is_up_to_date() ) { |
| 166 | return; |
| 167 | } |
| 168 | |
| 169 | $error_message = $this->copy_and_overwrite_domain_association_file(); |
| 170 | |
| 171 | if ( isset( $error_message ) ) { |
| 172 | $url = get_site_url() . '/' . self::DOMAIN_ASSOCIATION_FILE_DIR . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME; |
| 173 | WC_Stripe_Logger::log( |
| 174 | 'Error: ' . $error_message . ' ' . |
| 175 | /* translators: expected domain association file URL */ |
| 176 | sprintf( __( 'To enable Apple Pay, domain association file must be hosted at %s.', 'woocommerce-gateway-stripe' ), $url ) |
| 177 | ); |
| 178 | } else { |
| 179 | WC_Stripe_Logger::log( __( 'Domain association file updated.', 'woocommerce-gateway-stripe' ) ); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Adds a rewrite rule for serving the domain association file from the proper location. |
| 185 | */ |
| 186 | public function add_domain_association_rewrite_rule() { |
| 187 | $regex = '^\\' . self::DOMAIN_ASSOCIATION_FILE_DIR . '\/' . self::DOMAIN_ASSOCIATION_FILE_NAME . '$'; |
| 188 | $redirect = 'index.php?' . self::DOMAIN_ASSOCIATION_FILE_NAME . '=1'; |
| 189 | |
| 190 | add_rewrite_rule( $regex, $redirect, 'top' ); |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Add to the list of publicly allowed query variables. |
| 195 | * |
| 196 | * @param array $query_vars - provided public query vars. |
| 197 | * @return array Updated public query vars. |
| 198 | */ |
| 199 | public function whitelist_domain_association_query_param( $query_vars ) { |
| 200 | $query_vars[] = self::DOMAIN_ASSOCIATION_FILE_NAME; |
| 201 | return $query_vars; |
| 202 | } |
| 203 | |
| 204 | /** |
| 205 | * Serve domain association file when proper query param is provided. |
| 206 | * |
| 207 | * @param WP WordPress environment object. |
| 208 | */ |
| 209 | public function parse_domain_association_request( $wp ) { |
| 210 | if ( |
| 211 | ! isset( $wp->query_vars[ self::DOMAIN_ASSOCIATION_FILE_NAME ] ) || |
| 212 | '1' !== $wp->query_vars[ self::DOMAIN_ASSOCIATION_FILE_NAME ] |
| 213 | ) { |
| 214 | return; |
| 215 | } |
| 216 | |
| 217 | $path = WC_STRIPE_PLUGIN_PATH . '/' . self::DOMAIN_ASSOCIATION_FILE_NAME; |
| 218 | header( 'Content-Type: text/plain;charset=utf-8' ); |
| 219 | echo esc_html( file_get_contents( $path ) ); |
| 220 | exit; |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * Makes request to register the domain with Stripe/Apple Pay. |
| 225 | * |
| 226 | * @since 3.1.0 |
| 227 | * @version 4.9.0 |
| 228 | * @param string $secret_key |
| 229 | */ |
| 230 | private function make_domain_registration_request( $secret_key ) { |
| 231 | if ( empty( $secret_key ) ) { |
| 232 | throw new Exception( __( 'Unable to verify domain - missing secret key.', 'woocommerce-gateway-stripe' ) ); |
| 233 | } |
| 234 | |
| 235 | $endpoint = 'https://api.stripe.com/v1/apple_pay/domains'; |
| 236 | |
| 237 | $data = [ |
| 238 | 'domain_name' => $this->domain_name, |
| 239 | ]; |
| 240 | |
| 241 | $headers = [ |
| 242 | 'User-Agent' => 'WooCommerce Stripe Apple Pay', |
| 243 | 'Authorization' => 'Bearer ' . $secret_key, |
| 244 | ]; |
| 245 | |
| 246 | $response = wp_remote_post( |
| 247 | $endpoint, |
| 248 | [ |
| 249 | 'headers' => $headers, |
| 250 | 'body' => http_build_query( $data ), |
| 251 | 'timeout' => 30, |
| 252 | ] |
| 253 | ); |
| 254 | |
| 255 | if ( is_wp_error( $response ) ) { |
| 256 | /* translators: error message */ |
| 257 | throw new Exception( sprintf( __( 'Unable to verify domain - %s', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) ); |
| 258 | } |
| 259 | |
| 260 | if ( 200 !== $response['response']['code'] ) { |
| 261 | $parsed_response = json_decode( $response['body'] ); |
| 262 | |
| 263 | $this->apple_pay_verify_notice = $parsed_response->error->message; |
| 264 | |
| 265 | /* translators: error message */ |
| 266 | throw new Exception( sprintf( __( 'Unable to verify domain - %s', 'woocommerce-gateway-stripe' ), $parsed_response->error->message ) ); |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Processes the Apple Pay domain verification. |
| 272 | * |
| 273 | * @since 3.1.0 |
| 274 | * @version 4.5.4 |
| 275 | * |
| 276 | * @param string $secret_key |
| 277 | * |
| 278 | * @return bool Whether domain verification succeeded. |
| 279 | */ |
| 280 | public function register_domain_with_apple( $secret_key ) { |
| 281 | try { |
| 282 | $this->make_domain_registration_request( $secret_key ); |
| 283 | |
| 284 | // No errors to this point, verification success! |
| 285 | $this->stripe_settings['apple_pay_verified_domain'] = $this->domain_name; |
| 286 | $this->stripe_settings['apple_pay_domain_set'] = 'yes'; |
| 287 | $this->apple_pay_domain_set = true; |
| 288 | |
| 289 | update_option( 'woocommerce_stripe_settings', $this->stripe_settings ); |
| 290 | |
| 291 | WC_Stripe_Logger::log( 'Your domain has been verified with Apple Pay!' ); |
| 292 | |
| 293 | return true; |
| 294 | |
| 295 | } catch ( Exception $e ) { |
| 296 | $this->stripe_settings['apple_pay_verified_domain'] = $this->domain_name; |
| 297 | $this->stripe_settings['apple_pay_domain_set'] = 'no'; |
| 298 | $this->apple_pay_domain_set = false; |
| 299 | |
| 300 | update_option( 'woocommerce_stripe_settings', $this->stripe_settings ); |
| 301 | |
| 302 | WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() ); |
| 303 | |
| 304 | return false; |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | /** |
| 309 | * Process the Apple Pay domain verification if proper settings are configured. |
| 310 | * |
| 311 | * @since 4.5.4 |
| 312 | * @version 4.9.0 |
| 313 | */ |
| 314 | public function verify_domain_if_configured() { |
| 315 | $secret_key = $this->get_secret_key(); |
| 316 | |
| 317 | if ( ! $this->is_enabled() || empty( $secret_key ) ) { |
| 318 | return; |
| 319 | } |
| 320 | |
| 321 | // Ensure that domain association file will be served. |
| 322 | flush_rewrite_rules(); |
| 323 | |
| 324 | // The rewrite rule method doesn't work if permalinks are set to Plain. |
| 325 | // Create/update domain association file by copying it from the plugin folder as a fallback. |
| 326 | $this->update_domain_association_file(); |
| 327 | |
| 328 | // Register the domain with Apple Pay. |
| 329 | $verification_complete = $this->register_domain_with_apple( $secret_key ); |
| 330 | |
| 331 | // Show/hide notes if necessary. |
| 332 | WC_Stripe_Inbox_Notes::notify_on_apple_pay_domain_verification( $verification_complete ); |
| 333 | } |
| 334 | |
| 335 | /** |
| 336 | * Conditionally process the Apple Pay domain verification after settings are initially set. |
| 337 | * |
| 338 | * @since 4.5.4 |
| 339 | * @version 4.5.4 |
| 340 | */ |
| 341 | public function verify_domain_on_new_settings( $option, $settings ) { |
| 342 | $this->verify_domain_on_updated_settings( [], $settings ); |
| 343 | } |
| 344 | |
| 345 | /** |
| 346 | * Conditionally process the Apple Pay domain verification after settings are updated. |
| 347 | * |
| 348 | * @since 4.5.3 |
| 349 | * @version 4.5.4 |
| 350 | */ |
| 351 | public function verify_domain_on_updated_settings( $prev_settings, $settings ) { |
| 352 | // Grab previous state and then update cached settings. |
| 353 | $this->stripe_settings = $prev_settings; |
| 354 | $prev_secret_key = $this->get_secret_key(); |
| 355 | $prev_is_enabled = $this->is_enabled(); |
| 356 | $this->stripe_settings = $settings; |
| 357 | |
| 358 | // If Stripe or Payment Request Button wasn't enabled (or secret key was different) then might need to verify now. |
| 359 | if ( ! $prev_is_enabled || ( $this->get_secret_key() !== $prev_secret_key ) ) { |
| 360 | $this->verify_domain_if_configured(); |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | /** |
| 365 | * Display any admin notices to the user. |
| 366 | * |
| 367 | * @since 4.0.6 |
| 368 | */ |
| 369 | public function admin_notices() { |
| 370 | if ( ! $this->is_enabled() ) { |
| 371 | return; |
| 372 | } |
| 373 | |
| 374 | if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 375 | return; |
| 376 | } |
| 377 | |
| 378 | $empty_notice = empty( $this->apple_pay_verify_notice ); |
| 379 | if ( $empty_notice && ( $this->apple_pay_domain_set || empty( $this->secret_key ) ) ) { |
| 380 | return; |
| 381 | } |
| 382 | |
| 383 | /** |
| 384 | * Apple pay is enabled by default and domain verification initializes |
| 385 | * when setting screen is displayed. So if domain verification is not set, |
| 386 | * something went wrong so lets notify user. |
| 387 | */ |
| 388 | $allowed_html = [ |
| 389 | 'a' => [ |
| 390 | 'href' => [], |
| 391 | 'title' => [], |
| 392 | ], |
| 393 | ]; |
| 394 | $verification_failed_without_error = __( 'Apple Pay domain verification failed.', 'woocommerce-gateway-stripe' ); |
| 395 | $verification_failed_with_error = __( 'Apple Pay domain verification failed with the following error:', 'woocommerce-gateway-stripe' ); |
| 396 | $check_log_text = sprintf( |
| 397 | /* translators: 1) HTML anchor open tag 2) HTML anchor closing tag */ |
| 398 | 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' ), |
| 399 | '<a href="' . admin_url( 'admin.php?page=wc-status&tab=logs' ) . '">', |
| 400 | '</a>' |
| 401 | ); |
| 402 | |
| 403 | ?> |
| 404 | <div class="error stripe-apple-pay-message"> |
| 405 | <?php if ( $empty_notice ) : ?> |
| 406 | <p><?php echo esc_html( $verification_failed_without_error ); ?></p> |
| 407 | <?php else : ?> |
| 408 | <p><?php echo esc_html( $verification_failed_with_error ); ?></p> |
| 409 | <p><i><?php echo wp_kses( make_clickable( esc_html( $this->apple_pay_verify_notice ) ), $allowed_html ); ?></i></p> |
| 410 | <?php endif; ?> |
| 411 | <p><?php echo $check_log_text; ?></p> |
| 412 | </div> |
| 413 | <?php |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | new WC_Stripe_Apple_Pay_Registration(); |