omnifraud / omnifraud
一个框架无关的多网关欺诈预防库,适用于 PHP 7.1+
Requires
- php: ^7.1
- omnifraud/common: ^0.1
- omnifraud/kount: ~0.1
- omnifraud/signifyd: ~0.1
This package is auto-updated.
Last update: 2023-08-29 18:25:24 UTC
README
Omnifraud ·

一个易于使用、一致的欺诈预防库,适用于 PHP 7.1+。受 Omnipay 启发。
Omnifraud 是一个用于 PHP 的电子商务欺诈预防库。该项目旨在提供清晰、一致的 API,以与不同的欺诈预防、风险评估和责任转移服务交互。
目录
动机
市面上有大量的风险评估服务,尽管一些细节不同,但流程几乎总是相同的。
- 生成一个随机会话 ID
- 将上述会话 ID 的 JavaScript 跟踪代码插入前端
- 在结账时,将带有会话 ID 和订单信息的请求发送到风险评估服务
我们创建了 Omnifraud 来满足我们自己的需求。使用 Omnifraud 库的好处包括
- 学习一个接口,在整个项目中使用不同提供商。
- 清晰的分离。无需更改结账代码即可轻松切换提供商。
- 风险评估服务的文档并不总是清晰。输入你的 API 密钥即可。
安装
通常您只需安装所需的服务的。例如
composer require omnifraud/signifyd
每个包都已需要 omnifraud/common,因此您不需要要求它。
要安装所有支持的服务的 全部
composer require omnifraud/omnifraud
基本示例
<?php use Omnifraud\Omnifraud; /** @var \Omnifraud\Contracts\ServiceInterface $fraudService */ $fraudService = Omnifraud::create('Signifyd', [ 'api_key' => 'XXX', ]); // Build request, with data from the current sale $request = new Omnifraud\Request\Request(); $request->getPurchase()->setId('1'); $request->getPurchase()->setTotal(25100); $request->getPurchase()->setCurrencyCode('CAD'); $request->getAccount()->setEmail('jane@example.com'); //... // Send the request to the service $response = $fraudService->validateRequest($request); // Does it need to be updated later? if ($response->isPending()) { // Queue for later update $this->queueFraudUpdate($response->getRequestUid()); } if ($response->isGuaranteed()) { // The order is guaranteed by our fraud service // ... } if ($response->getScore() < 10.0) { // That's a pretty bad score. Let's bail! // ... }
注意:请参阅 MakesTestRequest@makeTestRequest() 以获取请求的完整示例。服务可能不同,哪些字段是可选的,哪些是必需的,但每个服务都可以处理一个完全完整的请求。
欺诈服务/驱动程序
所有驱动程序都实现了ServiceInterface。
以下服务目前官方支持
| 服务 | Composer 包 | 别名 | 维护者 |
|---|---|---|---|
| Kount | omnifraud/kount | Kount | LXRandCo |
| Signifyd | omnifraud/signifyd | Signifyd | LXRandCo |
| 空 | omnifraud/common | 空 | LXRandCo |
注意:有兴趣贡献自己的实现?我们非常乐意将其包括在内!将其添加到上述列表中,并发送PR。
《code>Null驱动程序
Null驱动程序什么都不做。你还期望什么?
使用方法
欺诈请求的生命周期如下
- 用户访问www.example.com
- 用户被分配一个随机的会话ID或重用现有的会话ID(通过本地存储、cookie等)
- 用户尝试使用付款结账
- 失败的付款和成功的付款都报告给欺诈服务
- 一旦结账成功,就会给欺诈请求评分(同步或异步)并将其与订单一起保存
按照实施顺序,这转化为
- 驱动程序实例化
- 前端JavaScript实现
- 创建欺诈“请求”
- 在结账时,将请求中的订单、会话、账户和付款信息记录下来
- 将响应和欺诈评分存储在数据库中,或者如果异步,则排队等待以后检索
会话ID
如何生成会话ID并在欺诈请求的生命周期中重用它完全取决于你。大多数服务对它的外观没有意见,但为了安全起见,你应该确保它
- 是字母数字的
- 长度合理(超过255可能有些过分)
- 具有一些唯一属性(提示:不要像
crc32(time())那样做) - 页面加载之间不改变
- 在客户端/服务器之间共享
一旦你有你的随机会话ID,将其放入cookie中,并将其用于前端跟踪和后端欺诈请求。
实例化
驱动程序的实例化非常简单。
你可以自己创建它们,将所有必要的配置作为第一个也是唯一的构造函数参数传递。
<?php use Omnifraud\Kount\KountService; /** @var \Omnifraud\Contracts\ServiceInterface $fraudService */ $fraudService = new KountService([ 'apiKey' => 'XXX', 'merchantId' => '123456', ]);
或者,使用Omnifraud\Omnifraud提供的静态create()方法。第一个参数是驱动程序的别名(如上表所述),第二个是通常提供的相同配置数组。
<?php use Omnifraud\Omnifraud; /** @var \Omnifraud\Contracts\ServiceInterface $fraudService */ $fraudService = Omnifraud::create('Kount', [ 'apiKey' => 'XXX', 'merchantId' => '123456', ]);
由于每个驱动程序都是Omnifraud\Contracts\ServiceInterface的实现,因此你应该小心确保使用此契约而不是任何具体实现。做任何其他事情都会违背这个库的目的。
前端实现
所有服务都公开了一个返回JavaScript片段字符串的trackingCode(string $pageType, string $sessionId)方法。您可以调用此方法来插入必要的代码以对欺诈服务的客户端进行前端跟踪。两个必需的参数是一个常量,用于指定我们插入片段的页面类型,以及客户端的会话ID。
<script> <?= $fraudService->trackingCode(ServiceInterface::PAGE_CHECKOUT, $request->cookies->get('session_id')); ?> </script>
请确保传递适当的常量,因为某些服务会区分两种类型的页面。它可以是指定的这两个值之一
ServiceInterface::PAGE_ALLServiceInterface::PAGE_CHECKOUT
简单来说,返回的字符串是一个包含会话ID的自执行函数(IIFE)。为了帮助说明其工作原理,让我们看看SignifydService。它的trackingCode()方法将返回以下JavaScript片段
(function(sid) { var script = document.createElement('script'); script.setAttribute('src', 'https://cdn-scripts.signifyd.com/api/script-tag.js'); script.setAttribute('data-order-session-id', sid); script.setAttribute('id', 'sig-api'); document.body.appendChild(script); })("{{ $sessionId }}");
注意:{{ $sessionId }}将被会话ID参数替换。
默认情况下,您传递给$sessionId的任何内容都将被引号括起来并转义(通过json_encode)。如果您想传递原始JS(例如变量名、全局函数等),请利用trackingCode的第三个可选参数bool $quote,将其设置为false。
服务方法
该接口相当直观,我们鼓励您通过查看ServiceInterface.php来熟悉它。所有方法在参数和返回值中都进行了类型提示。
所有服务公开的方法包括
public function validateRequest(Request $request): ResponseInterface;public function updateRequest(Request $request): ResponseInterface;public function cancelRequest(Request $request): void;public function logRefusedPayment(Request $request): void;public function getRequestExternalLink(string $requestUid): ?string;public function trackingCode(string $pageType, string $sessionId, bool $quote = true): string;
创建请求
为了以一致的方式与所有服务进行通信,所有实现都接受一个Request对象。
服务可能不同,有些字段被认为是可选的,有些是必需的,但每个服务都可以接受一个完整的请求。建议始终填写Request对象进行初始的validateRequest,让驱动程序决定哪些字段需要保留/丢弃。
请求是以下普通PHP对象的容器
一个完整的Request看起来可能如下所示
<?php use Omnifraud\Request\Request; use Omnifraud\Request\Data\Account; use Omnifraud\Request\Data\Address; use Omnifraud\Request\Data\Payment; use Omnifraud\Request\Data\Product; use Omnifraud\Request\Data\Purchase; use Omnifraud\Request\Data\Session; $request = new Request(); $purchase = new Purchase(); $purchase->setId('1'); // Unique identifier for this sale $purchase->setCreatedAt(new \DateTime('2017-09-02 12:12:12')); // Date the order was created at $purchase->setCurrencyCode('CAD'); // ISO 4217 currency code $purchase->setTotal(56025); // Total amount of the purchase, NO DECIMAL POINT. $request->setPurchase($purchase); $product1 = new Product(); $product1->setSku('SKU1'); // Product unique identifier $product1->setName('Product number 1'); // Product name $product1->setUrl('http://www.example.com/product-1'); // Product page $product1->setImage('http://www.example.com/product-1/cover.jpg'); // Image of the product $product1->setQuantity(1); // Quantity purchased $product1->setPrice(6025); // Price of the product, NO DECIMAL POINT. $product1->setWeight(100); // Weight in grams $product1->setIsDigital(false); // Is this a digital product $product1->setCategory('Category1'); // Category name $product1->setSubCategory('Sub Category 1'); // Sub category name $purchase->addProduct($product1); $payment = new Payment(); $payment->setBin(457173); // First six numbers of the card $payment->setLast4('9000'); // Last four numbers of the card $payment->setExpiryMonth(9); // Expiration month 1-12 $payment->setExpiryYear(2020); // Expiration year $payment->setAvs('Y'); // AVS response code, see http://www.emsecommerce.net/avs_cvv2_response_codes.htm $payment->setCvv('M'); // CVV response code, see http://www.emsecommerce.net/avs_cvv2_response_codes.htm $request->setPayment($payment); $account = new Account(); // Customer account $account->setId('ACCOUNT_ID'); // Account identifier $account->setUsername('username'); // Username $account->setEmail('test@example.com'); // Email address $account->setPhone('1234567890'); // Phone number $account->setCreatedAt(new \DateTime('2017-01-01 01:01:01')); // Account creation date $account->setUpdatedAt(new \DateTime('2017-05-12 02:02:02')); // Account last edition date $account->setLastOrderId('LAST_ORDER_ID'); // Previous sale identifier $account->setTotalOrderCount(5); // Total number of orders made by this customer in the past $account->setTotalOrderAmount(128700); // Total amount purchased by this customer, NO DECIMAL POINT. $request->setAccount($account); $session = new Session(); $session->setIp('1.2.3.4'); // Browser IP address $session->setId('SESSION_ID'); // Session ID (same that was passed to the frontend code $request->setSession($session); $shippingAddress = new Address(); $shippingAddress->setFullName('John Shipping'); // Shipping name $shippingAddress->setStreetAddress('1 shipping street'); $shippingAddress->setUnit('25'); $shippingAddress->setCity('Shipping Town'); $shippingAddress->setState('Shipping State'); $shippingAddress->setPostalCode('12345'); $shippingAddress->setCountryCode('US'); // ISO Alpha-2 country code $shippingAddress->setPhone('1234567891'); // Use as main phone number $request->setShippingAddress($shippingAddress); $billingAddress = new Address(); $billingAddress->setFullName('John Billing'); // Name on the card $billingAddress->setStreetAddress('1 billing street'); $billingAddress->setUnit('1A'); $billingAddress->setCity('Billing Town'); $billingAddress->setState('Billing State'); $billingAddress->setPostalCode('54321'); $billingAddress->setCountryCode('CA'); // ISO Alpha-2 country code $billingAddress->setPhone('0987654321'); $request->setBillingAddress($billingAddress);
替代方案
如果您不喜欢自己创建创建Request所需的所有对象,您可以直接通过Request的构造函数将所有内容作为多维数组传递。
<?php use Omnifraud\Request\Request; $request = new Request([ 'session' => [ 'id' => 'ABC123', 'ip' => '127.0.0.1', ], 'purchase' => [ 'id' => 1, 'createdAt' => new \DateTime('2017-09-02 12:12:12'), // ... ], // "products" is an array of products 'products' => [ [ 'sku' => 'SKU1', 'url' => 'http://www.example.com/product-1', 'price' => 6025, 'category' => 'Shoes', // ... ], [ 'sku' => 'SKU99', 'url' => 'http://www.example.com/product-99', 'price' => 2050, 'category' => 'Hats', // ... ], ], 'payment' => [ 'bin' => '457173', 'last4' => '9000', 'avs' => 'Y', 'cvv' => 'M', // ... ], // ... ]);
响应
Response接口公开了一些简洁的方法。
public function getRequestUid(): string;- 服务为该请求生成的UID。对于未来的更新(见下面的异步响应),取消和生成指向此欺诈请求Web视图的URL很有用public function isPending(): bool;- 是否该响应包含一个准备好的响应。一些服务可能需要额外的时间来手动审查请求,然后给出评分和保证public function getScore(): ?float;- 如果不是挂起的,将包含从0到100的评分(0 = 最差,100 = 最好)public function isGuaranteed(): bool;- 如果支持该服务,确定是否可以将此订单的责任转移给服务。通常称为“Chargeback保证”
异步响应
当你得到一个待处理(isPending() === true 如上所示)的响应时,你还不能获取它的分数和保证。在这种情况下,你应该记录下请求的UID,并稍后再尝试获取状态。
例如,你可以使用订单ID和请求ID调度一个后台任务,并尝试在稍后的日期再次更新它。
if ($response->isPending()) { $this->dispatchForLaterUpdate($response->getRequestUid(), $order->id); }