blob: 0f20e87cc5060487e19e8db5323ccb394c4b62b3 [file] [log] [blame]
swissChilif0cbdc32023-01-05 17:21:38 -05001<?php
2if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4}
5
6/**
7 * Class WC_Stripe_Webhook_State.
8 *
9 * Tracks the most recent successful and unsuccessful webhooks in test and live modes.
10 *
11 * @since 5.0.0
12 */
13class WC_Stripe_Webhook_State {
14 const OPTION_LIVE_MONITORING_BEGAN_AT = 'wc_stripe_wh_monitor_began_at';
15 const OPTION_LIVE_LAST_SUCCESS_AT = 'wc_stripe_wh_last_success_at';
16 const OPTION_LIVE_LAST_FAILURE_AT = 'wc_stripe_wh_last_failure_at';
17 const OPTION_LIVE_LAST_ERROR = 'wc_stripe_wh_last_error';
18
19 const OPTION_TEST_MONITORING_BEGAN_AT = 'wc_stripe_wh_test_monitor_began_at';
20 const OPTION_TEST_LAST_SUCCESS_AT = 'wc_stripe_wh_test_last_success_at';
21 const OPTION_TEST_LAST_FAILURE_AT = 'wc_stripe_wh_test_last_failure_at';
22 const OPTION_TEST_LAST_ERROR = 'wc_stripe_wh_test_last_error';
23
24 const VALIDATION_SUCCEEDED = 'validation_succeeded';
25 const VALIDATION_FAILED_EMPTY_HEADERS = 'empty_headers';
26 const VALIDATION_FAILED_EMPTY_BODY = 'empty_body';
27 const VALIDATION_FAILED_USER_AGENT_INVALID = 'user_agent_invalid';
28 const VALIDATION_FAILED_SIGNATURE_INVALID = 'signature_invalid';
29 const VALIDATION_FAILED_TIMESTAMP_MISMATCH = 'timestamp_out_of_range';
30 const VALIDATION_FAILED_SIGNATURE_MISMATCH = 'signature_mismatch';
31
32 /**
33 * Gets whether Stripe is in test mode or not
34 *
35 * @since 5.0.0
36 * @return bool
37 */
38 public static function get_testmode() {
39 $stripe_settings = get_option( 'woocommerce_stripe_settings', [] );
40 return ( ! empty( $stripe_settings['testmode'] ) && 'yes' === $stripe_settings['testmode'] ) ? true : false;
41 }
42
43 /**
44 * Gets (and sets, if unset) the timestamp the plugin first
45 * started tracking webhook failure and successes.
46 *
47 * @since 5.0.0
48 * @return integer UTC seconds since 1970.
49 */
50 public static function get_monitoring_began_at() {
51 $option = self::get_testmode() ? self::OPTION_TEST_MONITORING_BEGAN_AT : self::OPTION_LIVE_MONITORING_BEGAN_AT;
52 $monitoring_began_at = get_option( $option, 0 );
53 if ( 0 == $monitoring_began_at ) {
54 $monitoring_began_at = time();
55 update_option( $option, $monitoring_began_at );
56
57 // Enforce database consistency. This should only be needed if the user
58 // has modified the database directly. We should not allow timestamps
59 // before monitoring began.
60 self::set_last_webhook_success_at( 0 );
61 self::set_last_webhook_failure_at( 0 );
62 self::set_last_error_reason( self::VALIDATION_SUCCEEDED );
63 }
64 return $monitoring_began_at;
65 }
66
67 /**
68 * Sets the timestamp of the last successfully processed webhook.
69 *
70 * @since 5.0.0
71 * @param integer UTC seconds since 1970.
72 */
73 public static function set_last_webhook_success_at( $timestamp ) {
74 $option = self::get_testmode() ? self::OPTION_TEST_LAST_SUCCESS_AT : self::OPTION_LIVE_LAST_SUCCESS_AT;
75 update_option( $option, $timestamp );
76 }
77
78 /**
79 * Gets the timestamp of the last successfully processed webhook,
80 * or returns 0 if no webhook has ever been successfully processed.
81 *
82 * @since 5.0.0
83 * @return integer UTC seconds since 1970 | 0.
84 */
85 public static function get_last_webhook_success_at() {
86 $option = self::get_testmode() ? self::OPTION_TEST_LAST_SUCCESS_AT : self::OPTION_LIVE_LAST_SUCCESS_AT;
87 return get_option( $option, 0 );
88 }
89
90 /**
91 * Sets the timestamp of the last failed webhook.
92 *
93 * @since 5.0.0
94 * @param integer UTC seconds since 1970.
95 */
96 public static function set_last_webhook_failure_at( $timestamp ) {
97 $option = self::get_testmode() ? self::OPTION_TEST_LAST_FAILURE_AT : self::OPTION_LIVE_LAST_FAILURE_AT;
98 update_option( $option, $timestamp );
99 }
100
101 /**
102 * Gets the timestamp of the last failed webhook,
103 * or returns 0 if no webhook has ever failed to process.
104 *
105 * @since 5.0.0
106 * @return integer UTC seconds since 1970 | 0.
107 */
108 public static function get_last_webhook_failure_at() {
109 $option = self::get_testmode() ? self::OPTION_TEST_LAST_FAILURE_AT : self::OPTION_LIVE_LAST_FAILURE_AT;
110 return get_option( $option, 0 );
111 }
112
113 /**
114 * Sets the reason for the last failed webhook.
115 *
116 * @since 5.0.0
117 * @param string Reason code.
118 */
119 public static function set_last_error_reason( $reason ) {
120 $option = self::get_testmode() ? self::OPTION_TEST_LAST_ERROR : self::OPTION_LIVE_LAST_ERROR;
121 update_option( $option, $reason );
122 }
123
124 /**
125 * Returns the localized reason the last webhook failed.
126 *
127 * @since 5.0.0
128 * @return string Reason the last webhook failed.
129 */
130 public static function get_last_error_reason() {
131 $option = self::get_testmode() ? self::OPTION_TEST_LAST_ERROR : self::OPTION_LIVE_LAST_ERROR;
132 $last_error = get_option( $option, false );
133
134 if ( self::VALIDATION_SUCCEEDED == $last_error ) {
135 return( __( 'No error', 'woocommerce-gateway-stripe' ) );
136 }
137
138 if ( self::VALIDATION_FAILED_EMPTY_HEADERS == $last_error ) {
139 return( __( 'The webhook was missing expected headers', 'woocommerce-gateway-stripe' ) );
140 }
141
142 if ( self::VALIDATION_FAILED_EMPTY_BODY == $last_error ) {
143 return( __( 'The webhook was missing expected body', 'woocommerce-gateway-stripe' ) );
144 }
145
146 if ( self::VALIDATION_FAILED_USER_AGENT_INVALID == $last_error ) {
147 return( __( 'The webhook received did not come from Stripe', 'woocommerce-gateway-stripe' ) );
148 }
149
150 if ( self::VALIDATION_FAILED_SIGNATURE_INVALID == $last_error ) {
151 return( __( 'The webhook signature was missing or was incorrectly formatted', 'woocommerce-gateway-stripe' ) );
152 }
153
154 if ( self::VALIDATION_FAILED_TIMESTAMP_MISMATCH == $last_error ) {
155 return( __( 'The timestamp in the webhook differed more than five minutes from the site time', 'woocommerce-gateway-stripe' ) );
156 }
157
158 if ( self::VALIDATION_FAILED_SIGNATURE_MISMATCH == $last_error ) {
159 return( __( 'The webhook was not signed with the expected signing secret', 'woocommerce-gateway-stripe' ) );
160 }
161
162 return( __( 'Unknown error.', 'woocommerce-gateway-stripe' ) );
163 }
164
165 /**
166 * Gets the state of webhook processing in a human readable format.
167 *
168 * @since 5.0.0
169 * @return string Details on recent webhook successes and failures.
170 */
171 public static function get_webhook_status_message() {
172 $monitoring_began_at = self::get_monitoring_began_at();
173 $last_success_at = self::get_last_webhook_success_at();
174 $last_failure_at = self::get_last_webhook_failure_at();
175 $last_error = self::get_last_error_reason();
176 $test_mode = self::get_testmode();
177
178 $date_format = 'Y-m-d H:i:s e';
179
180 // Case 1 (Nominal case): Most recent = success
181 if ( $last_success_at > $last_failure_at ) {
182 $message = sprintf(
183 $test_mode ?
184 /* translators: 1) date and time of last webhook received, e.g. 2020-06-28 10:30:50 UTC */
185 __( 'The most recent test webhook, timestamped %s, was processed successfully.', 'woocommerce-gateway-stripe' ) :
186 /* translators: 1) date and time of last webhook received, e.g. 2020-06-28 10:30:50 UTC */
187 __( 'The most recent live webhook, timestamped %s, was processed successfully.', 'woocommerce-gateway-stripe' ),
188 gmdate( $date_format, $last_success_at )
189 );
190 return $message;
191 }
192
193 // Case 2: No webhooks received yet
194 if ( ( 0 == $last_success_at ) && ( 0 == $last_failure_at ) ) {
195 $message = sprintf(
196 $test_mode ?
197 /* translators: 1) date and time webhook monitoring began, e.g. 2020-06-28 10:30:50 UTC */
198 __( 'No test webhooks have been received since monitoring began at %s.', 'woocommerce-gateway-stripe' ) :
199 /* translators: 1) date and time webhook monitoring began, e.g. 2020-06-28 10:30:50 UTC */
200 __( 'No live webhooks have been received since monitoring began at %s.', 'woocommerce-gateway-stripe' ),
201 gmdate( $date_format, $monitoring_began_at )
202 );
203 return $message;
204 }
205
206 // Case 3: Failure after success
207 if ( $last_success_at > 0 ) {
208 $message = sprintf(
209 $test_mode ?
210 /*
211 * translators: 1) date and time of last failed webhook e.g. 2020-06-28 10:30:50 UTC
212 * translators: 2) reason webhook failed
213 * translators: 3) date and time of last successful webhook e.g. 2020-05-28 10:30:50 UTC
214 */
215 __( 'Warning: The most recent test webhook, received at %1$s, could not be processed. Reason: %2$s. (The last test webhook to process successfully was timestamped %3$s.)', 'woocommerce-gateway-stripe' ) :
216 /*
217 * translators: 1) date and time of last failed webhook e.g. 2020-06-28 10:30:50 UTC
218 * translators: 2) reason webhook failed
219 * translators: 3) date and time of last successful webhook e.g. 2020-05-28 10:30:50 UTC
220 */
221 __( 'Warning: The most recent live webhook, received at %1$s, could not be processed. Reason: %2$s. (The last live webhook to process successfully was timestamped %3$s.)', 'woocommerce-gateway-stripe' ),
222 gmdate( $date_format, $last_failure_at ),
223 $last_error,
224 gmdate( $date_format, $last_success_at )
225 );
226 return $message;
227 }
228
229 // Case 4: Failure with no prior success
230 $message = sprintf(
231 $test_mode ?
232 /* translators: 1) date and time of last failed webhook e.g. 2020-06-28 10:30:50 UTC
233 * translators: 2) reason webhook failed
234 * translators: 3) date and time webhook monitoring began e.g. 2020-05-28 10:30:50 UTC
235 */
236 __( 'Warning: The most recent test webhook, received at %1$s, could not be processed. Reason: %2$s. (No test webhooks have been processed successfully since monitoring began at %3$s.)', 'woocommerce-gateway-stripe' ) :
237 /* translators: 1) date and time of last failed webhook e.g. 2020-06-28 10:30:50 UTC
238 * translators: 2) reason webhook failed
239 * translators: 3) date and time webhook monitoring began e.g. 2020-05-28 10:30:50 UTC
240 */
241 __( 'Warning: The most recent live webhook, received at %1$s, could not be processed. Reason: %2$s. (No live webhooks have been processed successfully since monitoring began at %3$s.)', 'woocommerce-gateway-stripe' ),
242 gmdate( $date_format, $last_failure_at ),
243 $last_error,
244 gmdate( $date_format, $monitoring_began_at )
245 );
246 return $message;
247 }
248};