|
1
|
|
|
<?php |
|
2
|
|
|
if ( ! 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
|
|
|
* @since 4.4.2 |
|
11
|
|
|
*/ |
|
12
|
|
|
class WC_Stripe_Webhook_State { |
|
13
|
|
|
const OPTION_LIVE_MONITORING_BEGAN_AT = 'wc_stripe_wh_monitor_began_at'; |
|
14
|
|
|
const OPTION_LIVE_LAST_SUCCESS_AT = 'wc_stripe_wh_last_success_at'; |
|
15
|
|
|
const OPTION_LIVE_LAST_FAILURE_AT = 'wc_stripe_wh_last_failure_at'; |
|
16
|
|
|
const OPTION_LIVE_LAST_ERROR = 'wc_stripe_wh_last_error'; |
|
17
|
|
|
|
|
18
|
|
|
const OPTION_TEST_MONITORING_BEGAN_AT = 'wc_stripe_wh_test_monitor_began_at'; |
|
19
|
|
|
const OPTION_TEST_LAST_SUCCESS_AT = 'wc_stripe_wh_test_last_success_at'; |
|
20
|
|
|
const OPTION_TEST_LAST_FAILURE_AT = 'wc_stripe_wh_test_last_failure_at'; |
|
21
|
|
|
const OPTION_TEST_LAST_ERROR = 'wc_stripe_wh_test_last_error'; |
|
22
|
|
|
|
|
23
|
|
|
const VALIDATION_SUCCEEDED = 'validation_succeeded'; |
|
24
|
|
|
const VALIDATION_FAILED_HEADERS_NULL = 'headers_null'; |
|
25
|
|
|
const VALIDATION_FAILED_BODY_NULL = 'body_null'; |
|
26
|
|
|
const VALIDATION_FAILED_USER_AGENT_INVALID = 'user_agent_invalid'; |
|
27
|
|
|
const VALIDATION_FAILED_SIGNATURE_INVALID = 'signature_invalid'; |
|
28
|
|
|
const VALIDATION_FAILED_TIMESTAMP_MISMATCH = 'timestamp_out_of_range'; |
|
29
|
|
|
const VALIDATION_FAILED_SIGNATURE_MISMATCH = 'signature_mismatch'; |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* Gets whether Stripe is in test mode or not |
|
33
|
|
|
* |
|
34
|
|
|
* @since 4.4.2 |
|
35
|
|
|
* @return bool |
|
36
|
|
|
*/ |
|
37
|
|
|
public static function get_testmode() { |
|
38
|
|
|
$stripe_settings = get_option( 'woocommerce_stripe_settings', array() ); |
|
39
|
|
|
return ( ! empty( $stripe_settings['testmode'] ) && 'yes' === $stripe_settings['testmode'] ) ? true : false; |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* Gets (and sets, if unset) the timestamp the plugin first |
|
44
|
|
|
* started tracking webhook failure and successes. |
|
45
|
|
|
* |
|
46
|
|
|
* @since 4.4.2 |
|
47
|
|
|
* @return integer UTC seconds since 1970. |
|
48
|
|
|
*/ |
|
49
|
|
|
public static function get_monitoring_began_at() { |
|
50
|
|
|
$option = self::get_testmode() ? self::OPTION_TEST_MONITORING_BEGAN_AT : self::OPTION_LIVE_MONITORING_BEGAN_AT; |
|
51
|
|
|
$monitoring_began_at = get_option( $option, 0 ); |
|
52
|
|
|
if ( 0 == $monitoring_began_at ) { |
|
53
|
|
|
$monitoring_began_at = current_time( 'timestamp', true ); |
|
54
|
|
|
update_option( $option, $monitoring_began_at ); |
|
55
|
|
|
|
|
56
|
|
|
// Enforce database consistency. This should only be needed if the user |
|
57
|
|
|
// has modified the database directly. We should not allow timestamps |
|
58
|
|
|
// before monitoring began. |
|
59
|
|
|
self::set_last_webhook_success_at( 0 ); |
|
60
|
|
|
self::set_last_webhook_failure_at( 0 ); |
|
61
|
|
|
self::set_last_error_reason( self::VALIDATION_SUCCEEDED ); |
|
62
|
|
|
} |
|
63
|
|
|
return $monitoring_began_at; |
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* Sets the timestamp of the last successfully processed webhook. |
|
68
|
|
|
* |
|
69
|
|
|
* @since 4.4.2 |
|
70
|
|
|
* @param integer UTC seconds since 1970. |
|
71
|
|
|
*/ |
|
72
|
|
|
public static function set_last_webhook_success_at( $timestamp ) { |
|
73
|
|
|
$option = self::get_testmode() ? self::OPTION_TEST_LAST_SUCCESS_AT : self::OPTION_LIVE_LAST_SUCCESS_AT; |
|
74
|
|
|
update_option( $option, $timestamp ); |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* Gets the timestamp of the last successfully processed webhook, |
|
79
|
|
|
* or returns 0 if no webhook has ever been successfully processed. |
|
80
|
|
|
* |
|
81
|
|
|
* @since 4.4.2 |
|
82
|
|
|
* @return integer UTC seconds since 1970 | 0. |
|
83
|
|
|
*/ |
|
84
|
|
|
public static function get_last_webhook_success_at() { |
|
85
|
|
|
$option = self::get_testmode() ? self::OPTION_TEST_LAST_SUCCESS_AT : self::OPTION_LIVE_LAST_FAILURE_AT; |
|
86
|
|
|
return get_option( $option, 0 ); |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
|
|
/** |
|
90
|
|
|
* Sets the timestamp of the last failed webhook. |
|
91
|
|
|
* |
|
92
|
|
|
* @since 4.4.2 |
|
93
|
|
|
* @param integer UTC seconds since 1970. |
|
94
|
|
|
*/ |
|
95
|
|
|
public static function set_last_webhook_failure_at( $timestamp ) { |
|
96
|
|
|
$option = self::get_testmode() ? self::OPTION_TEST_LAST_FAILURE_AT : self::OPTION_LIVE_LAST_FAILURE_AT; |
|
97
|
|
|
update_option( $option, $timestamp ); |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
/** |
|
101
|
|
|
* Gets the timestamp of the last failed webhook, |
|
102
|
|
|
* or returns 0 if no webhook has ever failed to process. |
|
103
|
|
|
* |
|
104
|
|
|
* @since 4.4.2 |
|
105
|
|
|
* @return integer UTC seconds since 1970 | 0. |
|
106
|
|
|
*/ |
|
107
|
|
|
public static function get_last_webhook_failure_at() { |
|
108
|
|
|
$option = self::get_testmode() ? self::OPTION_TEST_LAST_FAILURE_AT : self::OPTION_LIVE_LAST_FAILURE_AT; |
|
109
|
|
|
return get_option( $option, 0 ); |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
/** |
|
113
|
|
|
* Sets the reason for the last failed webhook. |
|
114
|
|
|
* |
|
115
|
|
|
* @since 4.4.2 |
|
116
|
|
|
* @param string Reason code. |
|
117
|
|
|
* |
|
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 4.4.2 |
|
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 ( ! $last_error ) { |
|
135
|
|
|
return( __( 'No error', 'woocommerce-gateway-stripe' ) ); |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
if ( self::VALIDATION_FAILED_BODY_NULL == $last_error ) { |
|
139
|
|
|
return( __( 'The webhook was missing expected body', 'woocommerce-gateway-stripe' ) ); |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
if ( self::VALIDATION_FAILED_HEADERS_NULL == $last_error ) { |
|
143
|
|
|
return( __( 'The webhook was missing expected headers', 'woocommerce-gateway-stripe' ) ); |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
if ( self::VALIDATION_FAILED_SIGNATURE_INVALID == $last_error ) { |
|
147
|
|
|
return( __( 'The webhook signature was missing or was incorrectly formatted', 'woocommerce-gateway-stripe' ) ); |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
if ( self::VALIDATION_FAILED_SIGNATURE_MISMATCH == $last_error ) { |
|
151
|
|
|
return( __( 'The webhook was not signed with the expected signing secret', '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_USER_AGENT_INVALID == $last_error ) { |
|
159
|
|
|
return( __( 'The webhook received did not come from Stripe', '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 4.4.2 |
|
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
|
|
View Code Duplication |
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
|
|
|
date( $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
|
|
|
date( $date_format, $monitoring_began_at ) |
|
202
|
|
|
); |
|
203
|
|
|
return $message; |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
// Case 3: Failure after success |
|
207
|
|
View Code Duplication |
if ( $last_success_at > 0 ) { |
|
|
|
|
|
|
208
|
|
|
$message = sprintf( |
|
209
|
|
|
$test_mode ? |
|
210
|
|
|
/* translators: 1) date and time of last failed webhook e.g. 2020-06-28 10:30:50 UTC */ |
|
211
|
|
|
/* translators: 2) reason webhook failed */ |
|
212
|
|
|
/* translators: 3) date and time of last successful webhook e.g. 2020-05-28 10:30:50 UTC */ |
|
213
|
|
|
__( 'Warning: The most recent test webhook, received at %s, could not be processed. Reason: %s. (The last test webhook to process successfully was timestamped %s.)', 'woocommerce-gateway-stripe' ) : |
|
214
|
|
|
/* translators: 1) date and time of last failed webhook e.g. 2020-06-28 10:30:50 UTC */ |
|
215
|
|
|
/* translators: 2) reason webhook failed */ |
|
216
|
|
|
/* translators: 3) date and time of last successful webhook e.g. 2020-05-28 10:30:50 UTC */ |
|
217
|
|
|
__( 'Warning: The most recent live webhook, received at %s, could not be processed. Reason: %s. (The last live webhook to process successfully was timestamped %s.)', 'woocommerce-gateway-stripe' ), |
|
218
|
|
|
date( $date_format, $last_failure_at ), |
|
219
|
|
|
$last_error, |
|
220
|
|
|
date( $date_format, $last_success_at ) |
|
221
|
|
|
); |
|
222
|
|
|
return $message; |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
// Case 4: Failure with no prior success |
|
226
|
|
|
$message = sprintf( |
|
227
|
|
|
$test_mode ? |
|
228
|
|
|
/* translators: 1) date and time of last failed webhook e.g. 2020-06-28 10:30:50 UTC */ |
|
229
|
|
|
/* translators: 2) reason webhook failed */ |
|
230
|
|
|
/* translators: 3) date and time webhook monitoring began e.g. 2020-05-28 10:30:50 UTC */ |
|
231
|
|
|
__( 'Warning: The most recent test webhook, received at %s, could not be processed. Reason: %s. (No test webhooks have been processed successfully since monitoring began at %s.)', 'woocommerce-gateway-stripe' ) : |
|
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
|
|
|
__( 'Warning: The most recent live webhook, received at %s, could not be processed. Reason: %s. (No live webhooks have been processed successfully since monitoring began at %s.)', 'woocommerce-gateway-stripe' ), |
|
236
|
|
|
date( $date_format, $last_failure_at ), |
|
237
|
|
|
$last_error, |
|
238
|
|
|
date( $date_format, $monitoring_began_at ) |
|
239
|
|
|
); |
|
240
|
|
|
return $message; |
|
241
|
|
|
} |
|
242
|
|
|
}; |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.