diff --git a/CHANGELOG.md b/CHANGELOG.md index 465f25fd..ef3d79f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ CHANGELOG ========= +3.8.0 +------------------ + +* `with()` now rejects non-array sections and shopping cart items with + `InvalidInputException`, including when input validation is disabled. +* Custom input validation now rejects integer keys and array or object values + with `InvalidInputException` instead of a type error or conversion warning. + When validation is disabled, these values pass through unchanged. +* When input validation is disabled, billing, shipping, and credit card + country fields no longer fail in string validators. Billing and shipping + region and phone country code fields, and credit card bank phone country + codes, also pass through unchanged. +* The PHPDoc for the `with()` and `with*()` methods of `MaxMind\MinFraud` now + lists the `InvalidInputException` they throw when input validation is + enabled. + 3.7.0 (2026-07-21) ------------------ diff --git a/CLAUDE.md b/CLAUDE.md index 46ba4675..5f386ca8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -240,7 +240,7 @@ When adding a new input field to a `->with*()` method: return $new; ``` -5. **Update PHPDoc** with full documentation +5. **Update PHPDoc**, including `@throws InvalidInputException` on validation methods and their callers. 6. **Add tests** for the new field 7. **Update CHANGELOG.md** @@ -289,6 +289,9 @@ When adding input validation: 1. **Create a validation method** following the pattern: ```php + /** + * @throws InvalidInputException if validation is enabled and the value is invalid + */ private function verifyFieldName(string $value): void { if (!preg_match('/pattern/', $value)) { diff --git a/composer.json b/composer.json index 69509101..cbf54b06 100644 --- a/composer.json +++ b/composer.json @@ -22,10 +22,10 @@ "geoip2/geoip2": "^v3.4.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "3.*", + "friendsofphp/php-cs-fixer": "^3.95", "phpunit/phpunit": "^10.0", - "squizlabs/php_codesniffer": "*", - "phpstan/phpstan": "*" + "squizlabs/php_codesniffer": "^4.0", + "phpstan/phpstan": "^2.2" }, "suggest": { "ext-intl": "Enables improved email address IDN normalization" diff --git a/phpstan.neon b/phpstan.neon index 63aadbac..e433e52f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,3 +3,16 @@ parameters: paths: - src - tests + exceptions: + # These classes signal programmer errors, so callers need not declare them. + uncheckedExceptionClasses: + - Error + - LogicException + check: + missingCheckedExceptionInThrows: true + ignoreErrors: + # PHPUnit handles exceptions from test methods and their helpers. + - + identifier: missingType.checkedException + path: tests/* + reportUnmatched: false diff --git a/src/MinFraud.php b/src/MinFraud.php index 49eeebad..7b17b330 100644 --- a/src/MinFraud.php +++ b/src/MinFraud.php @@ -81,6 +81,9 @@ class MinFraud extends MinFraud\ServiceClient implements \JsonSerializable * to the `with*()` methods are validated. It is recommended that you * leave validation on while developing and only (optionally) disable it * before deployment. + * + * @throws \RuntimeException with older web-service-common releases, if HTTP client setup fails + * @throws WebServiceException if HTTP client setup fails */ public function __construct( int $accountId, @@ -119,11 +122,31 @@ public function jsonSerialize(): array * * @param array $values The request as a structured array * + * @throws InvalidInputException if a section or shopping cart item is not + * an array, or if validation is enabled and + * a value is invalid or a key is unknown + * * @return MinFraud A new immutable MinFraud object. This object is * a clone of the original with additional data. */ public function with(array $values): self { + foreach ([ + 'account', 'billing', 'credit_card', 'custom_inputs', 'device', + 'email', 'event', 'order', 'payment', 'shipping', 'shopping_cart', + ] as $section) { + if (\array_key_exists($section, $values) && !\is_array($values[$section])) { + throw new InvalidInputException("The $section section must be an array."); + } + } + if (isset($values['shopping_cart'])) { + foreach ($values['shopping_cart'] as $item) { + if (!\is_array($item)) { + throw new InvalidInputException('Each shopping_cart item must be an array.'); + } + } + } + $new = $this; if (\array_key_exists('account', $values)) { $new = $new->withAccount($this->remove($values, 'account', ['array'])); @@ -194,11 +217,15 @@ public function with(array $values): self * Device Tracking Add-on for explicit * device linking * - * @return MinFraud A new immutable MinFraud object. This object is a clone - * of the original with additional data. - * * @link https://dev.maxmind.com/minfraud/api-documentation/requests/?lang=en#schema--request--device * minFraud device API docs + * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown + * + * @return MinFraud A new immutable MinFraud object. This object is a clone + * of the original with additional data. */ public function withDevice( array $values = [], @@ -321,11 +348,15 @@ public function withDevice( * - `agent` * - `customer` * - * @return MinFraud A new immutable MinFraud object. This object is a clone of - * the original with additional data. - * * @link https://dev.maxmind.com/minfraud/api-documentation/requests/?lang=en#schema--request--event * minFraud event API docs + * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown + * + * @return MinFraud A new immutable MinFraud object. This object is a clone of + * the original with additional data. */ public function withEvent( array $values = [], @@ -403,7 +434,7 @@ public function withEvent( /** * This returns a `MinFraud` object with the `account` array set to - * the values provided. Existing `` data will be replaced. + * the values provided. Existing `account` data will be replaced. * * @link https://dev.maxmind.com/minfraud/api-documentation/requests/?lang=en#schema--request--account * minFraud account API docs @@ -417,6 +448,10 @@ public function withEvent( * the username or login name associated * with the account * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown + * * @return MinFraud A new immutable MinFraud object. This object is a clone * of the original with additional data. */ @@ -470,6 +505,10 @@ public function withAccount( * transaction. Do not include the `@` in this * field. * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown + * * @return MinFraud A new immutable MinFraud object. This object is a clone * of the original with additional data. */ @@ -550,6 +589,10 @@ public function withEmail( * @param string|null $region The ISO 3166-2 subdivision code for the user's * billing address * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown + * * @return MinFraud A new immutable MinFraud object. This object is a clone * of the original with additional data. */ @@ -606,7 +649,9 @@ public function withBilling( } if ($country !== null) { - $this->verifyCountryCode($country); + if ($this->validateInput) { + $this->verifyCountryCode($country); + } $values['country'] = $country; } @@ -619,7 +664,9 @@ public function withBilling( } if ($phoneCountryCode !== null) { - $this->verifyPhoneCountryCode($phoneCountryCode); + if ($this->validateInput) { + $this->verifyPhoneCountryCode($phoneCountryCode); + } $values['phone_country_code'] = $phoneCountryCode; } @@ -632,7 +679,9 @@ public function withBilling( } if ($region !== null) { - $this->verifyRegionCode($region); + if ($this->validateInput) { + $this->verifyRegionCode($region); + } $values['region'] = $region; } @@ -649,18 +698,29 @@ public function withBilling( * @link https://dev.maxmind.com/minfraud/api-documentation/requests/?lang=en#schema--request--shipping * minFraud shipping API docs * - * @param array $values An array of shipping data. The keys are the same as - * the JSON keys. You may use either this or the named - * arguments, but not both. - * @param string|null $company The company of the end user as provided in - * their shipping information - * @param string|null $address The first line of the user's shipping address - * @param string|null $city The city of the user's shipping address - * @param string|null $region The ISO 3166-2 subdivision code for the user's - * shipping address - * @param string|null $country The two character ISO 3166-1 alpha-2 country - * code of the user's shipping address - * @param string|null $postal The postal code of the user's shipping address + * @param array $values An array of shipping data. The keys are the same as + * the JSON keys. You may use either this or the named + * arguments, but not both. + * @param string|null $company The company of the end user as provided in + * their shipping information + * @param string|null $address The first line of the user's shipping address + * @param string|null $city The city of the user's shipping address + * @param string|null $region The ISO 3166-2 subdivision code for the user's + * shipping address + * @param string|null $country The two character ISO 3166-1 alpha-2 country + * code of the user's shipping address + * @param string|null $postal The postal code of the user's shipping address + * @param string|null $address2 the second line of the shipping address + * @param string|null $deliverySpeed the delivery speed: same_day, overnight, + * expedited, or standard + * @param string|null $firstName the recipient's first name + * @param string|null $lastName the recipient's last name + * @param string|null $phoneCountryCode the phone country code, with 1 to 4 digits + * @param string|null $phoneNumber the recipient's phone number + * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown * * @return MinFraud A new immutable MinFraud object. This object is * a clone of the original with additional data. @@ -720,7 +780,9 @@ public function withShipping( } if ($country !== null) { - $this->verifyCountryCode($country); + if ($this->validateInput) { + $this->verifyCountryCode($country); + } $values['country'] = $country; } @@ -741,7 +803,9 @@ public function withShipping( } if ($phoneCountryCode !== null) { - $this->verifyPhoneCountryCode($phoneCountryCode); + if ($this->validateInput) { + $this->verifyPhoneCountryCode($phoneCountryCode); + } $values['phone_country_code'] = $phoneCountryCode; } @@ -754,7 +818,9 @@ public function withShipping( } if ($region !== null) { - $this->verifyRegionCode($region); + if ($this->validateInput) { + $this->verifyRegionCode($region); + } $values['region'] = $region; } @@ -794,6 +860,10 @@ public function withShipping( * - `real_time_payment` * - `rewards` * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown + * * @return MinFraud A new immutable MinFraud object. This object is * a clone of the original with additional data. */ @@ -1059,6 +1129,10 @@ public function withPayment( * @param bool|null $was3dSecureSuccessful Whether the outcome of 3-D Secure * verification was successful * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown + * * @return MinFraud A new immutable MinFraud object. This object is a clone of * the original with additional data. */ @@ -1110,7 +1184,7 @@ public function withCreditCard( } if ($bankPhoneCountryCode !== null) { - if (!preg_match('/^[0-9]{1,4}$/', $bankPhoneCountryCode)) { + if ($this->validateInput && !preg_match('/^[0-9]{1,4}$/', $bankPhoneCountryCode)) { $this->maybeThrowInvalidInputException('Bank phone country code must be a string of 1 to 4 digits.'); } @@ -1122,7 +1196,9 @@ public function withCreditCard( } if ($country !== null) { - $this->verifyCountryCode($country); + if ($this->validateInput) { + $this->verifyCountryCode($country); + } $values['country'] = $country; } @@ -1177,41 +1253,47 @@ public function withCreditCard( * This returns a `MinFraud` object with the `custom_inputs` array set to * `$values`. Existing `custom_inputs` data will be replaced. * - * @param array $values the custom inputs to send in the request + * @param array $values the custom inputs to send in the request + * + * @throws InvalidInputException if input validation is enabled and a + * key or value is invalid * * @return MinFraud A new immutable MinFraud object. This object is * a clone of the original with additional data. */ public function withCustomInputs(array $values): self { - foreach ($values as $key => $value) { - if (\is_string($value)) { - if (str_contains($value, "\n")) { - $this->maybeThrowInvalidInputException( - "$value is invalid. String custom input values must not contain newline characters.", - ); - } - if ($value === '' || \strlen($value) > 255) { + if ($this->validateInput) { + foreach ($values as $key => $value) { + if (\is_string($value)) { + if (str_contains($value, "\n")) { + $this->maybeThrowInvalidInputException( + "$value is invalid. String custom input values must not contain newline characters.", + ); + } + if ($value === '' || \strlen($value) > 255) { + $this->maybeThrowInvalidInputException( + "$value is invalid. String custom input values must have a length between 1 and 255.", + ); + } + } elseif (is_numeric($value)) { + if ($value < -1e13 + 1 || $value > 1e13 - 1) { + $this->maybeThrowInvalidInputException( + "$value is invalid. Numeric custom input values must be between -1e13 and 1e13.", + ); + } + } elseif (!\is_bool($value)) { $this->maybeThrowInvalidInputException( - "$value is invalid. String custom input values must have a length between 1 and 255.", + 'Custom input values must be strings, numbers, or booleans. Received ' + . get_debug_type($value) . '.', ); } - } elseif (is_numeric($value)) { - if ($value < -1e13 + 1 || $value > 1e13 - 1) { + + if (!\is_string($key) || !preg_match('/^[a-z0-9_]{1,25}\Z/', $key)) { $this->maybeThrowInvalidInputException( - "$value is invalid. Numeric custom input values must be between -1e13 and 1e13.", + "$key is invalid. Custom input keys must be alphanumeric and have 25 characters or less.", ); } - } elseif (!\is_bool($value)) { - $this->maybeThrowInvalidInputException( - "$value is invalid. Custom input values must be strings, numbers, or booleans.", - ); - } - - if (!preg_match('/^[a-z0-9_]{1,25}\Z/', $key)) { - $this->maybeThrowInvalidInputException( - "$key is invalid. Custom input keys must be alphanumeric and have 25 characters or less.", - ); } } @@ -1241,9 +1323,13 @@ public function withCustomInputs(array $values): self * @param string|null $subaffiliateId The ID of the sub-affiliate where the order is coming from. * No specific format is required. * - * @return MinFraud A new immutable MinFraud object. This object is a clone of the original with additional data. - * * @see https://support.maxmind.com/knowledge-base/articles/order-and-shopping-cart-inputs-minfraud + * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown + * + * @return MinFraud A new immutable MinFraud object. This object is a clone of the original with additional data. */ public function withOrder( array $values = [], @@ -1339,6 +1425,11 @@ public function withOrder( * order currency. * @param int|null $quantity The quantity of the item in the shopping cart. * The value must be a whole number. + * @param string|null $itemId the identifier of the item in the shopping cart + * + * @throws InvalidInputException if input validation is enabled and a + * value is invalid, a value has the wrong + * type, or a key is unknown * * @return MinFraud A new immutable MinFraud object. This object is a clone * of the original with additional data. @@ -1413,8 +1504,8 @@ public function withShoppingCartItem( * @throws InvalidRequestException when the request is invalid for some * other reason, e.g., invalid JSON in the POST. * @throws HttpException when an unexpected HTTP error occurs - * @throws WebServiceException when some other error occurs. This also - * serves as the base class for the above exceptions. + * @throws \RuntimeException with older web-service-common releases, if cURL setup fails + * @throws WebServiceException when another web service error occurs * * @return Score minFraud Score model object */ @@ -1435,8 +1526,8 @@ public function score(): Score * @throws InvalidRequestException when the request is invalid for some * other reason, e.g., invalid JSON in the POST. * @throws HttpException when an unexpected HTTP error occurs - * @throws WebServiceException when some other error occurs. This also - * serves as the base class for the above exceptions. + * @throws \RuntimeException with older web-service-common releases, if cURL setup fails + * @throws WebServiceException when another web service error occurs * * @return Insights minFraud Insights model object */ @@ -1457,8 +1548,8 @@ public function insights(): Insights * @throws InvalidRequestException when the request is invalid for some * other reason, e.g., invalid JSON in the POST. * @throws HttpException when an unexpected HTTP error occurs - * @throws WebServiceException when some other error occurs. This also - * serves as the base class for the above exceptions. + * @throws \RuntimeException with older web-service-common releases, if cURL setup fails + * @throws WebServiceException when another web service error occurs * * @return Factors minFraud Factors model object */ @@ -1479,8 +1570,8 @@ public function factors(): Factors * @throws InvalidRequestException when the request is invalid for some * other reason, e.g., invalid JSON in the POST. * @throws HttpException when an unexpected HTTP error occurs - * @throws WebServiceException when some other error occurs. This also - * serves as the base class for the above exceptions. + * @throws \RuntimeException with older web-service-common releases, if cURL setup fails + * @throws WebServiceException when another web service error occurs * * @return mixed the model class for the service */ @@ -1496,6 +1587,10 @@ private function post(string $class, string $path) ); } + /** + * @throws InvalidInputException if the value is invalid and input + * validation is enabled + */ private function verifyCountryCode(string $country): void { if (!preg_match('/^[A-Z]{2}$/', $country)) { @@ -1503,6 +1598,10 @@ private function verifyCountryCode(string $country): void } } + /** + * @throws InvalidInputException if the value is invalid and input + * validation is enabled + */ private function verifyPhoneCountryCode(string $phoneCountryCode): void { if (!preg_match('/^[0-9]{1,4}$/', $phoneCountryCode)) { @@ -1510,6 +1609,10 @@ private function verifyPhoneCountryCode(string $phoneCountryCode): void } } + /** + * @throws InvalidInputException if the value is invalid and input + * validation is enabled + */ private function verifyRegionCode(string $region): void { if (!preg_match('/^[0-9A-Z]{1,4}$/', $region)) { diff --git a/src/MinFraud/ReportTransaction.php b/src/MinFraud/ReportTransaction.php index 39072ba6..fc8e470f 100644 --- a/src/MinFraud/ReportTransaction.php +++ b/src/MinFraud/ReportTransaction.php @@ -29,6 +29,9 @@ class ReportTransaction extends ServiceClient * to the `with*()` methods are validated. It is recommended that you * leave validation on while developing and only (optionally) disable it * before deployment. + * + * @throws \RuntimeException with older web-service-common releases, if HTTP client setup fails + * @throws WebServiceException if HTTP client setup fails */ public function __construct( int $accountId, @@ -90,9 +93,8 @@ public function __construct( * other reason, e.g., invalid JSON in the * POST. * @throws HttpException when an unexpected HTTP error occurs - * @throws WebServiceException when some other error occurs. This also - * serves as the base class for the above - * exceptions. + * @throws \RuntimeException with older web-service-common releases, if cURL setup fails + * @throws WebServiceException when another web service error occurs */ public function report( array $values = [], diff --git a/src/MinFraud/ServiceClient.php b/src/MinFraud/ServiceClient.php index b1b6e51e..1699edc3 100644 --- a/src/MinFraud/ServiceClient.php +++ b/src/MinFraud/ServiceClient.php @@ -5,6 +5,7 @@ namespace MaxMind\MinFraud; use MaxMind\Exception\InvalidInputException; +use MaxMind\Exception\WebServiceException; use MaxMind\WebService\Client; abstract class ServiceClient @@ -35,6 +36,9 @@ abstract class ServiceClient * @param int $accountId your account ID * @param string $licenseKey your license key * @param array $options options for the client + * + * @throws \RuntimeException with older web-service-common releases, if HTTP client setup fails + * @throws WebServiceException if HTTP client setup fails */ public function __construct( int $accountId, @@ -60,6 +64,9 @@ protected function userAgent(): string return 'minFraud-API/' . self::VERSION; } + /** + * @throws InvalidInputException if input validation is enabled + */ protected function maybeThrowInvalidInputException(string $msg): void { if ($this->validateInput) { @@ -73,6 +80,9 @@ protected function maybeThrowInvalidInputException(string $msg): void * @param array $array the parent array * @param string $key the key to remove * @param list $types the expected types + * + * @throws InvalidInputException if the value has an unexpected type and + * input validation is enabled */ protected function remove(array &$array, string $key, array $types = ['string']): mixed { @@ -94,6 +104,9 @@ protected function remove(array &$array, string $key, array $types = ['string']) /** * @param array $values + * + * @throws InvalidInputException if the array has unknown keys and input + * validation is enabled */ protected function verifyEmpty(array $values): void { diff --git a/tests/MaxMind/Test/MinFraud/ReportTransaction/ReportTransactionTest.php b/tests/MaxMind/Test/MinFraud/ReportTransaction/ReportTransactionTest.php index 979a079f..32af1e88 100644 --- a/tests/MaxMind/Test/MinFraud/ReportTransaction/ReportTransactionTest.php +++ b/tests/MaxMind/Test/MinFraud/ReportTransaction/ReportTransactionTest.php @@ -137,6 +137,18 @@ public static function requestsMissingRequiredFields(): array ]; } + public function testValuesWithNamedArgs(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('not both'); + + $req = Data::minimalRequest(); + $this->createReportTransactionRequest( + $req, + 0 + )->report($req, notes: 'some notes'); + } + public function testUnknownKey(): void { $this->expectException(InvalidInputException::class); diff --git a/tests/MaxMind/Test/MinFraudTest.php b/tests/MaxMind/Test/MinFraudTest.php index ee9eeeff..410a90ce 100644 --- a/tests/MaxMind/Test/MinFraudTest.php +++ b/tests/MaxMind/Test/MinFraudTest.php @@ -16,6 +16,98 @@ */ class MinFraudTest extends ServiceClientTester { + /** + * @dataProvider invalidRequestStructures + * + * @param array $values + */ + public function testInvalidRequestStructure(array $values, bool $validate): void + { + $client = new MinFraud(1, 'key', ['validateInput' => $validate]); + $this->expectException(InvalidInputException::class); + $this->expectExceptionMessage('must be an array'); + $client->with($values); + } + + /** + * @return array, bool}> + */ + public static function invalidRequestStructures(): array + { + $cases = []; + foreach ([true, false] as $validate) { + foreach ([ + ['device' => null], + ['device' => 'x'], + ['shopping_cart' => null], + ['shopping_cart' => ['x']], + ] as $values) { + $cases[] = [$values, $validate]; + } + } + + return $cases; + } + + /** + * @dataProvider invalidCustomInputs + * + * @param array $values + */ + public function testInvalidCustomInput(array $values): void + { + $client = new MinFraud(1, 'key'); + $this->expectException(InvalidInputException::class); + $client->withCustomInputs($values); + } + + /** + * @dataProvider invalidCustomInputs + * + * @param array $values + */ + public function testCustomInputsWithoutValidation(array $values): void + { + $client = new MinFraud(1, 'key', ['validateInput' => false]); + $result = $client->withCustomInputs($values); + $this->assertSame($values, $result->jsonSerialize()['content']['custom_inputs']); + } + + /** + * @return array}> + */ + public static function invalidCustomInputs(): array + { + return [[['value']], [['a' => new \stdClass()]], [['a' => ['x']]]]; + } + + /** + * @dataProvider unvalidatedAddressFields + */ + public function testAddressFieldWithoutValidation(string $method, string $section, string $field): void + { + $client = new MinFraud(1, 'key', ['validateInput' => false]); + $result = $client->{$method}([$field => 12]); + $this->assertSame(12, $result->jsonSerialize()['content'][$section][$field]); + } + + /** + * @return array + */ + public static function unvalidatedAddressFields(): array + { + return [ + ['withBilling', 'billing', 'country'], + ['withBilling', 'billing', 'region'], + ['withBilling', 'billing', 'phone_country_code'], + ['withShipping', 'shipping', 'country'], + ['withShipping', 'shipping', 'region'], + ['withShipping', 'shipping', 'phone_country_code'], + ['withCreditCard', 'credit_card', 'country'], + ['withCreditCard', 'credit_card', 'bank_phone_country_code'], + ]; + } + public function testMinFraud(): void { $minFraud = new MinFraud(0, '', ['hashEmail' => true, 'locales' => ['en', 'fr']]); @@ -442,6 +534,7 @@ public function testUnknownKeys(string $method): void public static function withMethods(): array { return [ + ['withDevice'], ['withEvent'], ['withAccount'], ['withEmail'], @@ -454,6 +547,39 @@ public static function withMethods(): array ]; } + /** + * @dataProvider withNamedArguments + */ + public function testValuesWithNamedArgs(string $method, string $parameter): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('not both'); + + $this->createMinFraudRequestWithFullResponse( + 'insights', + 0 + )->{$method}(['unknown' => 'some value'], ...[$parameter => 'value']); + } + + /** + * @return array + */ + public static function withNamedArguments(): array + { + return [ + ['withDevice', 'userAgent'], + ['withEvent', 'transactionId'], + ['withAccount', 'userId'], + ['withEmail', 'domain'], + ['withBilling', 'country'], + ['withShipping', 'country'], + ['withPayment', 'processor'], + ['withCreditCard', 'country'], + ['withOrder', 'currency'], + ['withShoppingCartItem', 'itemId'], + ]; + } + /** * @dataProvider badMd5s */