1: <?php
2:
3: namespace MaxMind;
4:
5: use MaxMind\Exception\AuthenticationException;
6: use MaxMind\Exception\HttpException;
7: use MaxMind\Exception\InsufficientFundsException;
8: use MaxMind\Exception\InvalidInputException;
9: use MaxMind\Exception\InvalidRequestException;
10: use MaxMind\Exception\WebServiceException;
11: use MaxMind\MinFraud\Validation;
12: use MaxMind\WebService\Client;
13: use Respect\Validation\Exceptions\ValidationException;
14:
15: /**
16: * This class provides a client API for accessing MaxMind minFraud Score
17: * and Insights.
18: *
19: * ## Usage ##
20: *
21: * The constructor takes your MaxMind user ID and license key. The object
22: * returned is immutable. To build up a request, call the `->with*()` methods.
23: * Each of these returns a new object (a clone of the original) with the
24: * additional data. These can be chained together:
25: *
26: * ```
27: * $client = new MinFraud(6, 'LICENSE_KEY');
28: *
29: * $score = $client->withDevice(['ip_address' => '1.1.1.1',
30: * 'session_age' => 3600.5,
31: * 'session_id' => 'foobar',
32: * 'accept_language' => 'en-US'])
33: * ->withEmail(['domain' => 'maxmind.com'])
34: * ->score();
35: * ```
36: *
37: * If the request fails, an exception is thrown.
38: */
39: class MinFraud
40: {
41: const VERSION = 'v1.4.0';
42:
43: private $client;
44: private static $host = 'minfraud.maxmind.com';
45:
46: private static $basePath = '/minfraud/v2.0/';
47: private $content;
48: private $locales;
49: private $validateInput = true;
50:
51: /**
52: * @param int $userId Your MaxMind user ID
53: * @param string $licenseKey Your MaxMind license key
54: * @param array $options An array of options. Possible keys:
55: *
56: * * `host` - The host to use when connecting to the web service.
57: * * `userAgent` - The prefix for the User-Agent header to use in the
58: * request.
59: * * `caBundle` - The bundle of CA root certificates to use in the request.
60: * * `connectTimeout` - The connect timeout to use for the request.
61: * * `timeout` - The timeout to use for the request.
62: * * `proxy` - The HTTP proxy to use. May include a schema, port,
63: * username, and password, e.g., `http://username:password@127.0.0.1:10`.
64: * * `locales` - An array of locale codes to use for the location name
65: * properties.
66: * * `validateInput` - Default is `true`. Determines whether values passed
67: * to the `with*()` methods are validated. It is recommended that you
68: * leave validation on while developing and only (optionally) disable it
69: * before deployment.
70: */
71: public function __construct(
72: $userId,
73: $licenseKey,
74: $options = []
75: ) {
76: if (isset($options['locales'])) {
77: $this->locales = $options['locales'];
78: } else {
79: $this->locales = ['en'];
80: }
81:
82: if (isset($options['validateInput'])) {
83: $this->validateInput = $options['validateInput'];
84: }
85:
86: if (!isset($options['host'])) {
87: $options['host'] = self::$host;
88: }
89: $options['userAgent'] = $this->userAgent();
90: $this->client = new Client($userId, $licenseKey, $options);
91: }
92:
93: /**
94: * This returns a `MinFraud` object with the array to be sent to the web
95: * service set to `$values`. Existing values will be replaced.
96: *
97: * @link https://dev.maxmind.com/minfraud/ minFraud API docs
98: *
99: * @param $values
100: *
101: * @return MinFraud
102: */
103: public function with($values)
104: {
105: $values = $this->cleanAndValidate('Transaction', $values);
106:
107: $new = clone $this;
108: $new->content = $values;
109:
110: return $new;
111: }
112:
113: /**
114: * This returns a `MinFraud` object with the `device` array set to
115: * `$values`. Existing `device` data will be replaced.
116: *
117: * @link https://dev.maxmind.com/minfraud/#Device_device
118: * minFraud device API docs
119: *
120: * @param $values
121: *
122: * @return MinFraud
123: */
124: public function withDevice($values)
125: {
126: return $this->validateAndAdd('Device', 'device', $values);
127: }
128:
129: /**
130: * This returns a `MinFraud` object with the `events` array set to
131: * `$values`. Existing `event` data will be replaced.
132: *
133: * @link https://dev.maxmind.com/minfraud/#Event_event
134: * minFraud event API docs
135: *
136: * @param $values
137: *
138: * @return MinFraud
139: */
140: public function withEvent($values)
141: {
142: return $this->validateAndAdd('Event', 'event', $values);
143: }
144:
145: /**
146: * This returns a `MinFraud` object with the `account` array set to
147: * `$values`. Existing `account` data will be replaced.
148: *
149: * @link https://dev.maxmind.com/minfraud/#Account_account
150: * minFraud account API docs
151: *
152: * @param $values
153: *
154: * @return MinFraud
155: */
156: public function withAccount($values)
157: {
158: return $this->validateAndAdd('Account', 'account', $values);
159: }
160:
161: /**
162: * This returns a `MinFraud` object with the `email` array set to
163: * `$values`. Existing `email` data will be replaced.
164: *
165: * @link https://dev.maxmind.com/minfraud/#Email_email
166: * minFraud email API docs
167: *
168: * @param $values
169: *
170: * @return MinFraud
171: */
172: public function withEmail($values)
173: {
174: return $this->validateAndAdd('Email', 'email', $values);
175: }
176:
177: /**
178: * This returns a `MinFraud` object with the `billing` array set to
179: * `$values`. Existing `billing` data will be replaced.
180: *
181: * @link https://dev.maxmind.com/minfraud/#Billing_billing
182: * minFraud billing API docs
183: *
184: * @param $values
185: *
186: * @return MinFraud
187: */
188: public function withBilling($values)
189: {
190: return $this->validateAndAdd('Billing', 'billing', $values);
191: }
192:
193: /**
194: * This returns a `MinFraud` object with the `shipping` array set to
195: * `$values`. Existing `shipping` data will be replaced.
196: *
197: * @link https://dev.maxmind.com/minfraud/#Shipping_shipping
198: * minFraud shipping API docs
199: *
200: * @param $values
201: *
202: * @return MinFraud
203: */
204: public function withShipping($values)
205: {
206: return $this->validateAndAdd('Shipping', 'shipping', $values);
207: }
208:
209: /**
210: * This returns a `MinFraud` object with the `payment` array set to
211: * `$values`. Existing `payment` data will be replaced.
212: *
213: * @link https://dev.maxmind.com/minfraud/#Payment_payment
214: * minFraud payment API docs
215: *
216: * @param $values
217: *
218: * @return MinFraud
219: */
220: public function withPayment($values)
221: {
222: return $this->validateAndAdd('Payment', 'payment', $values);
223: }
224:
225: /**
226: * This returns a `MinFraud` object with the `credit_card` array set to
227: * `$values`. Existing `credit_card` data will be replaced.
228: *
229: * @link https://dev.maxmind.com/minfraud/#Credit_Card_credit_card
230: * minFraud credit_card API docs
231: *
232: * @param $values
233: *
234: * @return MinFraud
235: */
236: public function withCreditCard($values)
237: {
238: return $this->validateAndAdd('CreditCard', 'credit_card', $values);
239: }
240:
241: /**
242: * This returns a `MinFraud` object with the `custom_inputs` array set to
243: * `$values`. Existing `custom_inputs` data will be replaced.
244: *
245: * @param $values
246: *
247: * @return MinFraud
248: */
249: public function withCustomInputs($values)
250: {
251: return $this->validateAndAdd('CustomInputs', 'custom_inputs', $values);
252: }
253:
254: /**
255: * This returns a `MinFraud` object with the `order` array set to
256: * `$values`. Existing `order` data will be replaced.
257: *
258: * @link https://dev.maxmind.com/minfraud/#Order_order
259: * minFraud order API docs
260: *
261: * @param $values
262: *
263: * @return MinFraud
264: */
265: public function withOrder($values)
266: {
267: return $this->validateAndAdd('Order', 'order', $values);
268: }
269:
270: /**
271: * This returns a `MinFraud` object with `$values` added to the shopping
272: * cart array.
273: *
274: * @link https://dev.maxmind.com/minfraud/#Shopping_Cart_Item
275: * minFraud shopping cart item API docs
276: *
277: * @param $values
278: *
279: * @return MinFraud
280: */
281: public function withShoppingCartItem($values)
282: {
283: $values = $this->cleanAndValidate('ShoppingCartItem', $values);
284:
285: $new = clone $this;
286: if (!isset($new->content['shopping_cart'])) {
287: $new->content['shopping_cart'] = [];
288: }
289: array_push($new->content['shopping_cart'], $values);
290:
291: return $new;
292: }
293:
294: /**
295: * This method performs a minFraud Score lookup using the request data in
296: * the current object and returns a model object for minFraud Score.
297: *
298: * @throws InvalidInputException when the request has missing or invalid
299: * data
300: * @throws AuthenticationException when there is an issue authenticating
301: * the request
302: * @throws InsufficientFundsException when your account is out of funds
303: * @throws InvalidRequestException when the request is invalid for some
304: * other reason, e.g., invalid JSON in the POST.
305: * @throws HttpException when an unexpected HTTP error occurs
306: * @throws WebServiceException when some other error occurs. This also
307: * serves as the base class for the above exceptions.
308: *
309: * @return MinFraud\Model\Score minFraud Score model object
310: */
311: public function score()
312: {
313: return $this->post('Score');
314: }
315:
316: /**
317: * This method performs a minFraud Insights lookup using the request data
318: * in the current object and returns a model object for minFraud Insights.
319: *
320: * @throws InvalidInputException when the request has missing or invalid
321: * data
322: * @throws AuthenticationException when there is an issue authenticating
323: * the request
324: * @throws InsufficientFundsException when your account is out of funds
325: * @throws InvalidRequestException when the request is invalid for some
326: * other reason, e.g., invalid JSON in the POST.
327: * @throws HttpException when an unexpected HTTP error occurs
328: * @throws WebServiceException when some other error occurs. This also
329: * serves as the base class for the above exceptions.
330: *
331: * @return MinFraud\Model\Insights minFraud Insights model object
332: */
333: public function insights()
334: {
335: return $this->post('Insights');
336: }
337:
338: /**
339: * This method performs a minFraud Factors lookup using the request data
340: * in the current object and returns a model object for minFraud Factors.
341: *
342: * @throws InvalidInputException when the request has missing or invalid
343: * data
344: * @throws AuthenticationException when there is an issue authenticating
345: * the request
346: * @throws InsufficientFundsException when your account is out of funds
347: * @throws InvalidRequestException when the request is invalid for some
348: * other reason, e.g., invalid JSON in the POST.
349: * @throws HttpException when an unexpected HTTP error occurs
350: * @throws WebServiceException when some other error occurs. This also
351: * serves as the base class for the above exceptions.
352: *
353: * @return MinFraud\Model\Factors minFraud Factors model object
354: */
355: public function factors()
356: {
357: return $this->post('Factors');
358: }
359:
360: /**
361: * @param $service $service the name of the service to use
362: *
363: * @throws InvalidInputException when the request has missing or invalid
364: * data
365: * @throws AuthenticationException when there is an issue authenticating the
366: * request
367: * @throws InsufficientFundsException when your account is out of funds
368: * @throws InvalidRequestException when the request is invalid for some
369: * other reason, e.g., invalid JSON in the POST.
370: * @throws HttpException when an unexpected HTTP error occurs
371: * @throws WebServiceException when some other error occurs. This also
372: * serves as the base class for the above exceptions.
373: *
374: * @return mixed the model class for the service
375: */
376: private function post($service)
377: {
378: if (!isset($this->content['device']['ip_address'])) {
379: throw new InvalidInputException(
380: 'Key ip_address must be present in device'
381: );
382: }
383: $url = self::$basePath . strtolower($service);
384: $class = 'MaxMind\\MinFraud\\Model\\' . $service;
385:
386: return new $class(
387: $this->client->post($service, $url, $this->content),
388: $this->locales
389: );
390: }
391:
392: /**
393: * @return string the prefix for the User-Agent header
394: */
395: private function userAgent()
396: {
397: return 'minFraud-API/' . self::VERSION;
398: }
399:
400: /**
401: * @param string $className The name of the class (but not the namespace)
402: * @param string $key The key in the transaction array to set
403: * @param array $values The values to validate
404: *
405: * @throws InvalidInputException when $values does not validate
406: *
407: * @return MinFraud
408: */
409: private function validateAndAdd($className, $key, $values)
410: {
411: $values = $this->cleanAndValidate($className, $values);
412: $new = clone $this;
413: $new->content[$key] = $values;
414:
415: return $new;
416: }
417:
418: /**
419: * @param string $className The name of the class (but not the namespace)
420: * @param array $values The values to validate
421: *
422: * @throws InvalidInputException when $values does not validate
423: *
424: * @return array The cleaned values
425: */
426: private function cleanAndValidate($className, $values)
427: {
428: $values = $this->clean($values);
429:
430: if (!$this->validateInput) {
431: return $values;
432: }
433:
434: $class = '\\MaxMind\\MinFraud\\Validation\\Rules\\' . $className;
435: $validator = new $class();
436: try {
437: $validator->check($values);
438: } catch (ValidationException $exception) {
439: throw new InvalidInputException(
440: $exception->getMessage(),
441: $exception->getCode()
442: );
443: }
444:
445: return $values;
446: }
447:
448: private function clean($array)
449: {
450: $cleaned = [];
451: foreach ($array as $key => $value) {
452: if (is_array($value)) {
453: $cleaned[$key] = $this->clean($array[$key]);
454: } elseif ($array[$key] !== null) {
455: $cleaned[$key] = $array[$key];
456: }
457: }
458:
459: return $cleaned;
460: }
461: }
462: