blob: 9df61247d5861bdc84485cb9a24eb0391be853df [file] [log] [blame]
swissChilif0cbdc32023-01-05 17:21:38 -05001<?php
2/**
3 * Stripe Apple Pay Registration Class.
4 *
5 * @since 4.0.6
6 */
7
8if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10}
11
12class 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
417new WC_Stripe_Apple_Pay_Registration();