1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Covery\Client\Envelopes; |
4
|
|
|
|
5
|
|
|
use Covery\Client\EnvelopeInterface; |
6
|
|
|
use Covery\Client\EnvelopeValidationException; |
7
|
|
|
use Covery\Client\IdentityNodeInterface; |
8
|
|
|
|
9
|
|
|
class ValidatorV1 |
10
|
|
|
{ |
11
|
|
|
private static $dataTypes = array( |
12
|
|
|
'billing_address' => 'string', |
13
|
|
|
'billing_city' => 'string', |
14
|
|
|
'billing_country' => 'string', |
15
|
|
|
'billing_firstname' => 'string', |
16
|
|
|
'billing_lastname' => 'string', |
17
|
|
|
'billing_fullname' => 'string', |
18
|
|
|
'billing_state' => 'string', |
19
|
|
|
'billing_zip' => 'string', |
20
|
|
|
'card_id' => 'string', |
21
|
|
|
'card_last4' => 'string', |
22
|
|
|
'country' => 'string', |
23
|
|
|
'cpu_class' => 'string', |
24
|
|
|
'device_fingerprint' => 'string', |
25
|
|
|
'firstname' => 'string', |
26
|
|
|
'gender' => 'string', |
27
|
|
|
'language' => 'string', |
28
|
|
|
'language_browser' => 'string', |
29
|
|
|
'language_system' => 'string', |
30
|
|
|
'language_user' => 'string', |
31
|
|
|
'languages' => 'string', |
32
|
|
|
'lastname' => 'string', |
33
|
|
|
'login_user_agent' => 'string', |
34
|
|
|
'os' => 'string', |
35
|
|
|
'payment_method' => 'string', |
36
|
|
|
'payment_mid' => 'string', |
37
|
|
|
'payment_system' => 'string', |
38
|
|
|
'product_description' => 'string', |
39
|
|
|
'product_name' => 'string', |
40
|
|
|
'registration_useragent' => 'string', |
41
|
|
|
'screen_orientation' => 'string', |
42
|
|
|
'screen_resolution' => 'string', |
43
|
|
|
'social_type' => 'string', |
44
|
|
|
'transaction_currency' => 'string', |
45
|
|
|
'transaction_id' => 'string', |
46
|
|
|
'transaction_mode' => 'string', |
47
|
|
|
'transaction_type' => 'string', |
48
|
|
|
'user_agent' => 'string', |
49
|
|
|
'user_merchant_id' => 'string', |
50
|
|
|
'user_name' => 'string', |
51
|
|
|
'website_url' => 'string', |
52
|
|
|
'transaction_source' => 'string', |
53
|
|
|
'ip' => 'string', |
54
|
|
|
'merchant_ip' => 'string', |
55
|
|
|
'real_ip' => 'string', |
56
|
|
|
'email' => 'string', |
57
|
|
|
'phone' => 'string', |
58
|
|
|
'age' => 'int', |
59
|
|
|
'card_bin' => 'int', |
60
|
|
|
'confirmation_timestamp' => 'int', |
61
|
|
|
'expiration_month' => 'int', |
62
|
|
|
'expiration_year' => 'int', |
63
|
|
|
'login_timestamp' => 'int', |
64
|
|
|
'registration_timestamp' => 'int', |
65
|
|
|
'timezone_offset' => 'int', |
66
|
|
|
'transaction_timestamp' => 'int', |
67
|
|
|
'product_quantity' => 'float', |
68
|
|
|
'transaction_amount' => 'float', |
69
|
|
|
'transaction_amount_converted' => 'float', |
70
|
|
|
'ajax_validation' => 'bool', |
71
|
|
|
'cookie_enabled' => 'bool', |
72
|
|
|
'do_not_track' => 'bool', |
73
|
|
|
'email_confirmed' => 'bool', |
74
|
|
|
'login_failed' => 'bool', |
75
|
|
|
'phone_confirmed' => 'bool', |
76
|
|
|
); |
77
|
|
|
|
78
|
|
|
private static $types = array( |
79
|
|
|
'confirmation' => array( |
80
|
|
|
'mandatory' => array('confirmation_timestamp', 'user_merchant_id'), |
81
|
|
|
'optional' => array('email_confirmed', 'phone_confirmed'), |
82
|
|
|
), |
83
|
|
|
'login' => array( |
84
|
|
|
'mandatory' => array('login_timestamp', 'user_merchant_id'), |
85
|
|
|
'optional' => array('email', 'login_failed', 'phone') |
86
|
|
|
), |
87
|
|
|
'registration' => array( |
88
|
|
|
'mandatory' => array('registration_timestamp', 'user_merchant_id'), |
89
|
|
|
'optional' => array( |
90
|
|
|
'age', |
91
|
|
|
'country', |
92
|
|
|
'email', |
93
|
|
|
'firstname', |
94
|
|
|
'gender', |
95
|
|
|
'lastname', |
96
|
|
|
'phone', |
97
|
|
|
'social_type', |
98
|
|
|
'user_name', |
99
|
|
|
), |
100
|
|
|
), |
101
|
|
|
'transaction' => array( |
102
|
|
|
'mandatory' => array( |
103
|
|
|
'transaction_amount', |
104
|
|
|
'transaction_currency', |
105
|
|
|
'transaction_id', |
106
|
|
|
'transaction_mode', |
107
|
|
|
'transaction_timestamp', |
108
|
|
|
'transaction_type', |
109
|
|
|
'card_bin', |
110
|
|
|
'card_id', |
111
|
|
|
'card_last4', |
112
|
|
|
'expiration_month', |
113
|
|
|
'expiration_year', |
114
|
|
|
'user_merchant_id', |
115
|
|
|
), |
116
|
|
|
'optional' => array( |
117
|
|
|
'age', |
118
|
|
|
'country', |
119
|
|
|
'email', |
120
|
|
|
'firstname', |
121
|
|
|
'gender', |
122
|
|
|
'lastname', |
123
|
|
|
'phone', |
124
|
|
|
'user_name', |
125
|
|
|
'payment_method', |
126
|
|
|
'payment_mid', |
127
|
|
|
'payment_system', |
128
|
|
|
'transaction_amount_converted', |
129
|
|
|
'transaction_source', |
130
|
|
|
'billing_address', |
131
|
|
|
'billing_city', |
132
|
|
|
'billing_country', |
133
|
|
|
'billing_firstname', |
134
|
|
|
'billing_lastname', |
135
|
|
|
'billing_fullname', |
136
|
|
|
'billing_state', |
137
|
|
|
'billing_zip', |
138
|
|
|
'product_description', |
139
|
|
|
'product_name', |
140
|
|
|
'product_quantity', |
141
|
|
|
'website_url', |
142
|
|
|
'merchant_ip', |
143
|
|
|
) |
144
|
|
|
), |
145
|
|
|
); |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Analyzes SequenceID |
149
|
|
|
* |
150
|
|
|
* @param string $sequenceId |
151
|
|
|
* @return string[] |
152
|
|
|
*/ |
153
|
|
|
public function analyzeSequenceId($sequenceId) |
154
|
|
|
{ |
155
|
|
|
if (!is_string($sequenceId)) { |
156
|
|
|
return array('SequenceID is not a string'); |
157
|
|
|
} |
158
|
|
|
$len = strlen($sequenceId); |
159
|
|
|
if ($len < 6 || $len > 40) { |
160
|
|
|
return array(sprintf( |
161
|
|
|
'Invalid SequenceID length. It must be in range [6, 40], but %d received.', |
162
|
|
|
$len |
163
|
|
|
)); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
return array(); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Analyzes identities from envelope |
171
|
|
|
* |
172
|
|
|
* @param IdentityNodeInterface[] $identities |
173
|
|
|
* @return string |
174
|
|
|
*/ |
175
|
|
|
public function analyzeIdentities(array $identities) |
176
|
|
|
{ |
177
|
|
|
$detail = array(); |
178
|
|
|
if (count($identities) === 0) { |
179
|
|
|
$detail[] = 'At least one Identity must be supplied'; |
180
|
|
|
} else { |
181
|
|
|
foreach ($identities as $i => $identity) { |
182
|
|
|
if (!$identity instanceof IdentityNodeInterface) { |
183
|
|
|
$detail[] = $i . '-th elements of Identities not implements IdentityNodeInterface'; |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
return $detail; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Analyzes envelope type and mandatory fields |
193
|
|
|
* |
194
|
|
|
* @param EnvelopeInterface $envelope |
195
|
|
|
* @return string[] |
196
|
|
|
*/ |
197
|
|
|
public function analyzeTypeAndMandatoryFields(EnvelopeInterface $envelope) |
198
|
|
|
{ |
199
|
|
|
$type = $envelope->getType(); |
200
|
|
|
if (!is_string($type)) { |
201
|
|
|
return array('Envelope type must be string'); |
202
|
|
|
} elseif (!isset(self::$types[$type])) { |
203
|
|
|
return array( |
204
|
|
|
sprintf('Envelope type "%s" not supported by this client version', $type) |
205
|
|
|
); |
206
|
|
|
} else { |
207
|
|
|
$details = array(); |
208
|
|
|
$typeInfo = self::$types[$type]; |
209
|
|
|
|
210
|
|
|
// Mandatory fields check |
211
|
|
|
foreach ($typeInfo['mandatory'] as $name) { |
212
|
|
|
if (!isset($envelope[$name]) || empty($envelope[$name])) { |
213
|
|
|
$details[] = sprintf( |
214
|
|
|
'Field "%s" is mandatory for "%s", but not provided', |
215
|
|
|
$name, |
216
|
|
|
$type |
217
|
|
|
); |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
// Field presence check |
222
|
|
|
$fields = array_merge($typeInfo['mandatory'], $typeInfo['optional']); |
223
|
|
|
$customCount = 0; |
224
|
|
|
foreach ($envelope as $key => $value) { |
225
|
|
|
if ($this->isCustom($type)) { |
226
|
|
|
$customCount++; |
227
|
|
|
} |
228
|
|
|
if (!in_array($key, $fields)) { |
229
|
|
|
$details[] = sprintf('Field "%s" not found in "%s"', $key, $envelope->getType()); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
if ($customCount > 0) { |
234
|
|
|
$details[] = sprintf('Expected 10 or less custom fields, but %d provided', $customCount); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
return $details; |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Analyzes field types |
243
|
|
|
* |
244
|
|
|
* @param EnvelopeInterface $envelope |
245
|
|
|
* @return array |
246
|
|
|
*/ |
247
|
|
|
public function analyzeFieldTypes(EnvelopeInterface $envelope) |
248
|
|
|
{ |
249
|
|
|
$type = $envelope->getType(); |
250
|
|
|
if (is_string($type) && isset(self::$types[$type])) { |
251
|
|
|
$details = array(); |
252
|
|
|
|
253
|
|
|
// Per field check |
254
|
|
|
foreach ($envelope as $key => $value) { |
255
|
|
|
// Is custom? |
256
|
|
|
if ($this->isCustom($key)) { |
257
|
|
|
if (!is_string($value)) { |
258
|
|
|
$details[] = sprintf( |
259
|
|
|
'All custom values must be string, but for "%s" %s was provided', |
260
|
|
|
$key, |
261
|
|
|
$value === null ? 'null' : gettype($value) |
262
|
|
|
); |
263
|
|
|
} |
264
|
|
|
} elseif (isset(self::$dataTypes[$key])) { |
265
|
|
|
// Checking type |
266
|
|
|
switch (self::$dataTypes[$key]) { |
267
|
|
|
case 'string': |
268
|
|
|
if (!is_string($value)) { |
269
|
|
|
$details[] = sprintf( |
270
|
|
|
'Field "%s" must be string, but %s provided', |
271
|
|
|
$key, |
272
|
|
|
$value === null ? 'null' : gettype($value) |
273
|
|
|
); |
274
|
|
|
} elseif (strlen($value) > 255) { |
275
|
|
|
$details[] = sprintf( |
276
|
|
|
'Received %d bytes to string key "%s" - value is too long', |
277
|
|
|
strlen($value), |
278
|
|
|
$key |
279
|
|
|
); |
280
|
|
|
} |
281
|
|
|
break; |
282
|
|
View Code Duplication |
case 'int': |
|
|
|
|
283
|
|
|
if (!is_int($value)) { |
284
|
|
|
$details[] = sprintf( |
285
|
|
|
'Field "%s" must be int, but %s provided', |
286
|
|
|
$key, |
287
|
|
|
$value === null ? 'null' : gettype($value) |
288
|
|
|
); |
289
|
|
|
} |
290
|
|
|
break; |
291
|
|
View Code Duplication |
case 'float': |
|
|
|
|
292
|
|
|
if (!is_float($value) && !is_int($value)) { |
293
|
|
|
$details[] = sprintf( |
294
|
|
|
'Field "%s" must be float/double, but %s provided', |
295
|
|
|
$key, |
296
|
|
|
$value === null ? 'null' : gettype($value) |
297
|
|
|
); |
298
|
|
|
} |
299
|
|
|
break; |
300
|
|
View Code Duplication |
case 'bool': |
|
|
|
|
301
|
|
|
if (!is_bool($value)) { |
302
|
|
|
$details[] = sprintf( |
303
|
|
|
'Field "%s" must be boolean, but %s provided', |
304
|
|
|
$key, |
305
|
|
|
$value === null ? 'null' : gettype($value) |
306
|
|
|
); |
307
|
|
|
} |
308
|
|
|
break; |
309
|
|
|
default: |
310
|
|
|
$details[] = sprintf('Unknown type for "%s"', $key); |
311
|
|
|
} |
312
|
|
|
} else { |
313
|
|
|
$details[] = sprintf('Unknown type for "%s"', $key); |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
return $details; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
return array(); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Checks envelope validity and throws an exception on error |
325
|
|
|
* |
326
|
|
|
* @param EnvelopeInterface $envelope |
327
|
|
|
* @throws EnvelopeValidationException |
328
|
|
|
*/ |
329
|
|
|
public function validate(EnvelopeInterface $envelope) |
330
|
|
|
{ |
331
|
|
|
$details = array_merge( |
332
|
|
|
$this->analyzeSequenceId($envelope->getSequenceId()), |
333
|
|
|
$this->analyzeIdentities($envelope->getIdentities()), |
334
|
|
|
$this->analyzeTypeAndMandatoryFields($envelope), |
335
|
|
|
$this->analyzeFieldTypes($envelope) |
336
|
|
|
); |
337
|
|
|
|
338
|
|
|
if (count($details) > 0) { |
339
|
|
|
throw new EnvelopeValidationException($details); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Returns true if provided key belongs to custom fields family |
345
|
|
|
* |
346
|
|
|
* @param string $key |
347
|
|
|
* @return bool |
348
|
|
|
*/ |
349
|
|
|
public function isCustom($key) |
350
|
|
|
{ |
351
|
|
|
return is_string($key) && strlen($key) >= 7 && substr($key, 0, 7) === 'custom_'; |
352
|
|
|
} |
353
|
|
|
} |
354
|
|
|
|
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.