class-wc-payments-fraud-service.php
7.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
<?php
/**
* WC_Payments_Fraud_Service class
*
* @package WooCommerce\Payments
*/
defined( 'ABSPATH' ) || exit;
use WCPay\Exceptions\API_Exception;
use WCPay\Logger;
/**
* Class which includes all the fraud-specific logic.
*/
class WC_Payments_Fraud_Service {
/**
* Client for making requests to the WooCommerce Payments API
*
* @var WC_Payments_API_Client
*/
private $payments_api_client;
/**
* WC_Payments_Account instance to get information about the account
*
* @var WC_Payments_Account
*/
private $account;
/**
* WC_Payments_Customer instance for working with customer information
*
* @var WC_Payments_Customer_Service
*/
private $customer_service;
/**
* Constructor for WC_Payments_Fraud_Service.
*
* @param WC_Payments_API_Client $payments_api_client - WooCommerce Payments API client.
* @param WC_Payments_Customer_Service $customer_service - Customer class instance.
* @param WC_Payments_Account $account - Account class instance.
*/
public function __construct(
WC_Payments_API_Client $payments_api_client,
WC_Payments_Customer_Service $customer_service,
WC_Payments_Account $account
) {
$this->payments_api_client = $payments_api_client;
$this->customer_service = $customer_service;
$this->account = $account;
add_filter( 'wcpay_prepare_fraud_config', [ $this, 'prepare_fraud_config' ], 10, 2 );
add_action( 'init', [ $this, 'link_session_if_user_just_logged_in' ] );
add_action( 'admin_print_footer_scripts', [ $this, 'add_sift_js_tracker' ] );
}
/**
* Prepares the fraud config for a service.
*
* @param array $config Existing config data for the given anti-fraud service.
* @param string $service_id Identifier of the anti-fraud service provider.
*
* @return array|NULL Array with all the required data to initialize the anti-fraud script, or NULL if the service shouldn't be used.
*/
public function prepare_fraud_config( $config, $service_id ) {
switch ( $service_id ) {
case 'sift':
return $this->prepare_sift_config( $config );
}
return $config;
}
/**
* Adds site-specific config needed to initialize the SIFT anti-fraud JS.
*
* @param array $config Associative array with the SIFT-related configuration returned from the server.
*
* @return array|NULL Assoc array, ready for the client to consume, or NULL if the client shouldn't enqueue this script.
*/
private function prepare_sift_config( $config ) {
// The server returns both production and sandbox beacon keys. Use the sandbox one if test mode is enabled.
if ( WC_Payments::get_gateway()->is_in_test_mode() ) {
$config['beacon_key'] = $config['sandbox_beacon_key'];
}
unset( $config['sandbox_beacon_key'] );
$wpcom_blog_id = $this->payments_api_client->get_blog_id();
if ( ! $wpcom_blog_id ) {
// Don't enqueue the SIFT script if Jetpack hasn't been connected yet.
return null;
}
$config['user_id'] = '';
if ( is_user_logged_in() ) {
if ( is_admin() ) {
$config['user_id'] = $this->account->get_stripe_account_id();
} else {
$customer_id = $this->customer_service->get_customer_id_by_user_id( get_current_user_id() );
if ( isset( $customer_id ) ) {
$config['user_id'] = $customer_id;
}
}
}
if ( $this->check_if_user_just_logged_in() ) {
$config['session_id'] = $this->get_cookie_session_id();
} else {
if ( is_a( WC()->session, 'WC_Session' ) ) {
$config['session_id'] = $wpcom_blog_id . '_' . WC()->session->get_customer_id();
} else {
return null; // we do not have a valid session for the current process.
}
}
return $config;
}
/**
* Called after the WooCommerce session has been initialized. Check if the current user has just logged in,
* and sends that information to the server to link the current browser session with the user.
*
* @return boolean True if the user has just logged in, false in any other case.
*/
public function check_if_user_just_logged_in() {
if ( ! get_current_user_id() ) {
return false;
}
WC()->initialize_session();
$session_handler = WC()->session;
$cookie = $session_handler->get_session_cookie();
if ( ! $cookie ) {
return false;
}
$cookie_customer_id = $cookie[0];
return $session_handler->get_customer_id() !== $cookie_customer_id;
}
/**
* Called after the WooCommerce session has been initialized. Check if the current user has just logged in,
* and sends that information to the server to link the current browser session with the user.
*/
public function link_session_if_user_just_logged_in() {
$wpcom_blog_id = $this->payments_api_client->get_blog_id();
if ( ! $wpcom_blog_id ) {
// Don't do anything if Jetpack hasn't been connected yet.
return;
}
if ( ! $this->check_if_user_just_logged_in() ) {
return;
}
$fraud_config = $this->account->get_fraud_services_config();
if ( ! isset( $fraud_config['sift'] ) ) {
// If Sift isn't enabled, we don't need to link the session.
return;
}
// The session changed during the current page load, for example if the user just logged in.
// In this case, send the old session's customer ID alongside the new user_id so SIFT can link them.
$customer_id = $this->customer_service->get_customer_id_by_user_id( get_current_user_id() );
if ( ! isset( $customer_id ) ) {
return;
}
try {
$this->payments_api_client->link_session_to_customer( $this->get_cookie_session_id(), $customer_id );
} catch ( API_Exception $e ) {
Logger::log( '[Tracking] Error when linking session with user: ' . $e->getMessage() );
}
}
/**
* Get the session ID used until now for the current browsing session.
*
* @return string|NULL Session ID, or NULL if unknown.
*/
private function get_cookie_session_id() {
$wpcom_blog_id = $this->payments_api_client->get_blog_id();
if ( ! $wpcom_blog_id ) {
return null;
}
$session_handler = WC()->session;
if ( ! $session_handler ) {
return null;
}
$cookie = $session_handler->get_session_cookie();
if ( ! $cookie ) {
return null;
}
$cookie_customer_id = $cookie[0];
return $wpcom_blog_id . '_' . $cookie_customer_id;
}
/**
* Adds the Sift JS page tracker if needed. See the comments for the detailed logic.
*
* @return void
*/
public function add_sift_js_tracker() {
if ( ! isset( $_GET['wcpay-connection-success'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
// Only enqueue the tracker if the merchant has just finished the Stripe KYC.
return;
}
$fraud_config = $this->account->get_fraud_services_config();
if ( ! isset( $fraud_config['sift'] ) ) {
// Abort if Sift is not enabled for this account.
return;
}
if ( isset( $_GET['path'] ) && strpos( $_GET['path'], '/payments/' ) === 0 ) { // phpcs:ignore WordPress.Security
// If the current page is a WCPay dashboard, there's separate logic that will include the Sift JS tracker on its own.
return;
}
?>
<script type="text/javascript">
var src = 'https://cdn.sift.com/s.js';
var _sift = ( window._sift = window._sift || [] );
_sift.push( [ '_setAccount', '<?php echo esc_attr( $fraud_config['sift']['beacon_key'] ); ?>' ] );
_sift.push( [ '_setUserId', '<?php echo esc_attr( $fraud_config['sift']['user_id'] ); ?>' ] );
_sift.push( [ '_setSessionId', '<?php echo esc_attr( $fraud_config['sift']['session_id'] ); ?>' ] );
_sift.push( [ '_trackPageview' ] );
if ( ! document.querySelector( '[src="' + src + '"]' ) ) {
var script = document.createElement( 'script' );
script.src = src;
script.async = true;
document.body.appendChild( script );
}
</script>
<?php
}
}