Authorize.net 开发

时间:2020-04-02

Authorize.Net 是一家位于美国的支付网关服务提供商,使商户可以通过其网站并通过 Internet 协议(IP)连接接受信用卡和电子支票付款。Authorize.Net 成立于 1996 年,现在是 Visa Inc 的子公司。其服务允许客户将信用卡和运送信息直接输入到网页上,而有些替代方法则要求客户在执行之前注册支付服务交易。

Authorize.Net 为中小型商人处理公司的卡和 ACH 付款。它提供欺诈防护服务,定期计费订阅以及简单的结帐选项。对于开发人员,它提供了适用于Android 和 iOS 的应用程序编程接口(API)和软件开发套件。它的虚拟终端和发票功能可以处理手动付款。它还提供定期计费和用于与 Authorize.Net 集成的插件,并且为商人提供技术支持。

在使用 Authorize.Net 之前,请前往 Authorize.Net 官网注册商家中心。具体流程请自行 Google。

PCI 标准

PCI DSS 由 PCI 安全标准委员会的创始成员(包括 American Express、Discover Financial Services、JCB、MasterCard Worldwide和 Visa International)制定,旨在鼓励国际上采用一致的数据安全措施。

PCI 标准是为了最大限度保护持卡人数据的一套标准。要求很多,可以到 PCI标准 站点了解。通俗点讲,就是保证用户的任何支付信息,都不走自己的服务器,不保存在自己的数据库。

商家中心

正式环境:https://account.authorize.net/
沙盒环境:https://sandbox.authorize.net/

配置开发所需信息

Current API Login ID:请求 api 使用
Current Transaction Key:请求 api 使用
Current Signature Key:请求 api 使用
Public Client Key:客户端 Accept.js 使用

使用文档

开发文档:https://developer.authorize.net/api/reference/index.html
Webhooks:https://developer.authorize.net/api/reference/features/webhooks.html
测试指南: https://developer.authorize.net/hello_world/testing_guide/
防欺诈介绍:https://sandbox.authorize.net/help/whgdata/whlstt11.htm#11
SDK 示例:https://github.com/AuthorizeNet/sample-code-php

支付流程

客户端使用 Accept.js。Accept.js 是一个 JavaScript 库,用于直接将安全付款数据发送到Authorize.Net。Accept.js 捕获付款数据并将其直接提交给我们,以换取一次性令牌或付款随机数。

通俗点讲,就是我们在客户端把用户的信用卡信息(如:卡号、CVV 等)发送给 Authorize.Net,Authorize.Net 接收并正确处理后,向客户端返回一次性令牌数据。后端在请求发起支付 api 时,将使用这些一次性令牌数据。这样,涉及到客户的个人信息,就没有经过我们自己的服务器,从而也遵从了 PCI 标准。

后端请求 Authorize.net 支付 api, 验证响应结果;后端接收 Wenhook 通知,根据结果处理订单状态。

使用类库

# 发起支付 官方提供 sdk 包
composer require authorizenet/authorizenet

# Webhooks 校验
composer require stymiee/authnetjson

客户端代码示例

<script type="text/javascript" src="https://jstest.authorize.net/v1/Accept.js"></script>

# 获取一次性令牌
function sendPaymentDataToAnet()
{
    # 整合数据
    var authData = {};
    authData.clientKey = "xxxxxx";
    authData.apiLoginID = "xxxxxx";

    var cardData = {};
    var cardFiled = new creditCardModel();
    cardData.cardNumber = cardFiled.cardNumber.val(); # 信用卡号
    cardData.month = cardFiled.expMonth.val(); # 卡到期时间
    cardData.year = cardFiled.expYear.val(); # 卡到期时间
    cardData.cardCode = cardFiled.cardCode.val(); # CVV
    cardData.fullName = cardFiled.fullName.val(); # 持卡人姓名

    var secureData = {};
    secureData.authData = authData;
    secureData.cardData = cardData;

    # 请求 api
    Accept.dispatchData(secureData, responseHandler);

    # 处理响应结果
    function responseHandler(response) {
        # validate error message
        if (response.messages.resultCode === "Error") {
            var i = 0;
            var msg = "";
            while (i < response.messages.message.length) {
                msg += response.messages.message[i].code + ": " + response.messages.message[i].text +"<br/>"
                i = i + 1;
            }
            showMsg(msg);
        } else {
            # 正确响应            
            # 后端发起支付所需一次性令牌数据
            var dataDescriptor = opaqueData.dataDescriptor;
            var dataValue = opaqueData.dataValue;
            ......
            # 生成订单
            payment.placeOrder();
        }
    }
}

后端代码示例

示例使用 PHP 开发语言,Laravel 5.5 框架。示例代码仅供参考,请根据项目实际情况编写代码。

发起支付代码示例
# 安装依赖包
composer require authorizenet/authorizenet

use net\authorize\api\contract\v1 as AnetAPI;
use net\authorize\api\controller as AnetController;

/**
 * Authorize.net payment
 * @param $params['ant_data_descriptor']
 * @param $params['ant_data_value']
 * @param $params['order_data']
 * @return array
 */
public function ant($params = [])
{
    LogCommon::log('payment/authorize', '请求参数:'.json_encode($params)."\n");

    // 设置商家信息
    $merchantAuthentication = new AnetAPI\MerchantAuthenticationType();   
    $merchantAuthentication->setName(Config('payment.ant.login_id'));   
    $merchantAuthentication->setTransactionKey(Config('payment.ant.transaction_key'));   
    $refId = 'ref'.time();

    // 设置信用卡信息
    // $creditCard = new AnetAPI\CreditCardType();
    // $creditCard->setCardNumber($params['card_number']);
    // $creditCard->setExpirationDate($params['card_expiration_date']);
    // $creditCard->setCardCode($params['card_code']);
    // $paymentCreditCard = new AnetAPI\PaymentType();
    // $paymentCreditCard->setCreditCard($creditCard);

    // 设置付款对象
    $opaqueData = new AnetAPI\OpaqueDataType();
    $opaqueData->setDataDescriptor($params['ant_data_descriptor']);
    $opaqueData->setDataValue($params['ant_data_value']);
    $paymentOpaque = new AnetAPI\PaymentType();
    $paymentOpaque->setOpaqueData($opaqueData);

    // 设置订单信息
    $order = new AnetAPI\OrderType();
    $order->setInvoiceNumber($params['order_data']['number']);
    $order->setDescription($params['order_data']['number']);

    // Set the customer's Bill To address
    $billTo = new AnetAPI\CustomerAddressType();
    $billTo->setFirstName($params['order_data']['billing_firstname']);
    $billTo->setLastName($params['order_data']['billing_lastname']);
    $billTo->setAddress($params['order_data']['billing_address_1']);
    $billTo->setCity($params['order_data']['billing_city']);
    $billTo->setState($params['order_data']['billing_zone']);
    $billTo->setCountry($params['order_data']['billing_country']);
    $billTo->setPhoneNumber($params['order_data']['billing_phone']);
    $billTo->setZip($params['order_data']['billing_postcode']);

    // Create the customer shipping address
    $customerShippingAddress = new AnetAPI\CustomerAddressType();
    $customerShippingAddress->setFirstName($params['order_data']['shipping_firstname']);
    $customerShippingAddress->setLastName($params['order_data']['shipping_lastname']);
    $customerShippingAddress->setAddress($params['order_data']['shipping_address_1']);
    $customerShippingAddress->setCity($params['order_data']['shipping_city']);
    $customerShippingAddress->setState($params['order_data']['shipping_zone']);
    $customerShippingAddress->setCountry($params['order_data']['shipping_country']);
    $customerShippingAddress->setZip($params['order_data']['shipping_postcode']);

    // Set the customer's identifying information
    $customerData = new AnetAPI\CustomerDataType();
    $customer_id = $params['order_data']['customer_id'] != 0 ? $params['order_data']['customer_id'] : 'tourist_'.time();
    $customerData->setId($customer_id);
    $customerData->setType("individual");
    $customerData->setEmail($params['order_data']['email']);

    // 设置交易信息
    $transactionRequestType = new AnetAPI\TransactionRequestType();
    $transactionRequestType->setTransactionType("authCaptureTransaction"); 
    $transactionRequestType->setAmount($params['order_data']['total']);
    $transactionRequestType->setOrder($order);
    $transactionRequestType->setPayment($paymentOpaque);
    $transactionRequestType->setBillTo($billTo);
    $transactionRequestType->setShipTo($customerShippingAddress);
    $transactionRequestType->setCustomer($customerData);
    $transactionRequestType->setCustomerIP($params['order_data']['ip']);

    // 发起支付
    $request = new AnetAPI\CreateTransactionRequest();
    $request->setMerchantAuthentication($merchantAuthentication);
    $request->setRefId($refId);
    $request->setTransactionRequest($transactionRequestType);
    // Create the controller and get the response
    $controller = new AnetController\CreateTransactionController($request);
    $response = $controller->executeWithApiResponse(config('payment.ant.api_url'));
    LogCommon::log('payment/authorize', '请求响应1:'.json_encode($response)."\n");
    LogCommon::log('payment/authorize', '请求响应2:'.json_encode($response->getTransactionResponse())."\n");

    if ($response != null) {
        if ($response->getMessages()->getResultCode() == 'Ok') {
            $tresponse = $response->getTransactionResponse();
            if ($tresponse != null && $tresponse->getMessages() != null) {
                $transaction_id = $tresponse->getTransId();
                return arraySuccess(['transaction_id' => $transaction_id, 'credit_card_type' => $tresponse->getAccountType(), 'credit_card_number' => $tresponse->getAccountNumber()]);
            } else {
                return arrayFailed();
            }
        } else {
            return arrayFailed();
        }
    } else {
        return arrayFailed();
    }
}
发起支付响应
# error
请求响应1:{"refId":"ref1587462112","messages":{"resultCode":"Ok","message":[{"code":"I00001","text":"Successful."}]}}
请求响应2:{"responseCode":"3","authCode":"","avsResultCode":"P","cvvResultCode":"","cavvResultCode":"","transId":"60140447815","refTransID":"","transHash":"","testRequest":"0","accountNumber":"XXXX1111","accountType":"Visa","errors":{"error":[{"errorCode":"251","errorText":"This transaction has been declined."}]},"transHashSha2":"4C2071FEA16B5921755231799878B2CE6200F9FFD225E9525E60B96F887A9294EEB2022539A53A8582A2CE7A0B16707689E97AB261E0D5CD01AA3661926FEE90"}

# success
请求响应1:{"refId":"ref1587462112","messages":{"resultCode":"Ok","message":[{"code":"I00001","text":"Successful."}]}}
请求响应2:{"responseCode":"1","authCode":"F8PG0Y","avsResultCode":"Y","cvvResultCode":"P","cavvResultCode":"2","transId":"60140447878","refTransID":"","transHash":"","testRequest":"0","accountNumber":"XXXX1111","accountType":"Visa","messages":{"message":[{"code":"1","description":"This transaction has been approved."}]},"transHashSha2":"EB9E692A74B485D6509C83FFC38B7D858D17AFA9C5305DCE16C233048CB2E9ACE30980F30F236402C7F9F76B9967F8D5FA828D039098FAAF65D99BF8A6773091","networkTransId":"4I2939OE3IG24FZFDIVY3OU"}
接收 Webhook 异步通知代码示例
# 安装依赖包
composer require stymiee/authnetjson

use JohnConde\Authnet\AuthnetApiFactory;
use JohnConde\Authnet\AuthnetWebhook;

public function authorizeWebhook(Request $request)
{
    $log = '';
    $headers = getallheaders();
    $payload = file_get_contents("php://input");
    $webhook = new AuthnetWebhook(config('payment.ant.signature_key'), $payload, $headers);
    if ($webhook->isValid()) {
        $log .= "Webhook响应\nresponse:".json_encode($request->all())."\n";
        $transactionId = $webhook->payload->id;
        $AuthnetApiFactory  = AuthnetApiFactory::getJsonApiHandler(config('payment.ant.login_id'), config('payment.ant.transaction_key'));
        $response = $AuthnetApiFactory->getTransactionDetailsRequest(array('transId' => $transactionId));
        if (!empty($response)) {
            $log .= "transId:".$webhook->payload->id."\n";
            $log .= "eventType:".$webhook->eventType;
            $payment_log = DB::table('payment_log')->where('token', $webhook->payload->id)->first();
            if (!empty($payment_log)) {
                $this->setAuthorizeWebhook($webhook, $payment_log, $request);
            }
        }
        LogCommon::log('payment/authorize', $log);
    }
}


private function setAuthorizeWebhook($webhook, $payment_log, $request)
{
    $payment_log_data = [];
    $payment_log_data['ant_remark'] = json_encode($request->all());
    // net.authorize.payment.authorization.created 通知您已创建授权交易
    if ($webhook->eventType == 'net.authorize.payment.authorization.created') {}
    // net.authorize.payment.authcapture.created 通知您已创建授权和捕获事务
    if ($webhook->eventType == 'net.authorize.payment.authcapture.created') {
        $payment_log_data['is_paid'] = 1;
        DB::table('payment_log')->where('token', $webhook->payload->id)->update($payment_log_data);
        DB::table('order')->where('id', $payment_log->order_id)->update(['status' => 30]);
        $params = ['type' => 'order', 'order_id' => $payment_log->order_id];
        EmailSlug::dispatch($params);
    }
    // net.authorize.payment.capture.created 通知您已创建捕获事务
    if ($webhook->eventType == 'net.authorize.payment.capture.created') {
        $payment_log_data['is_paid'] = 1;
        DB::table('payment_log')->where('token', $webhook->payload->id)->update($payment_log_data);
        DB::table('order')->where('id', $payment_log->order_id)->update(['status' => 30]);
        $params = ['type' => 'order', 'order_id' => $payment_log->order_id];
        EmailSlug::dispatch($params);
    }
    // net.authorize.payment.refund.created 通知您已成功结算的交易已退款
    if ($webhook->eventType == 'net.authorize.payment.refund.created') {
        DB::table('order')->where('id', $payment_log->order_id)->update(['status' => 50]);
    }
    // net.authorize.payment.priorAuthCapture.created 通知您先前的授权已被捕获
    if ($webhook->eventType == 'net.authorize.payment.priorAuthCapture.created') {
        $payment_log_data['is_paid'] = 1;
        DB::table('payment_log')->where('token', $webhook->payload->id)->update($payment_log_data);
        DB::table('order')->where('id', $payment_log->order_id)->update(['status' => 30]);
    }
    // net.authorize.payment.void.created 通知您未结算的交易已作废
    if ($webhook->eventType == 'net.authorize.payment.void.created') {
        DB::table('payment_log')->where('token', $webhook->payload->id)->update(['is_paid' => 2]);
        DB::table('order')->where('id', $payment_log->order_id)->update(['status' => 10]);
    }
    // net.authorize.payment.fraud.held 通知您交易被视为可疑
    if ($webhook->eventType == 'net.authorize.payment.fraud.held') {
        $payment_log_data['is_paid'] = 2;
        DB::table('payment_log')->where('token', $webhook->payload->id)->update($payment_log_data);
        DB::table('order')->where('id', $payment_log->order_id)->update(['status' => 20]);
    }
    // net.authorize.payment.fraud.approved 通知您先前持有的交易已获批准
    if ($webhook->eventType == 'net.authorize.payment.fraud.approved') {}
    // net.authorize.payment.fraud.declined 通知您先前举行的交易被拒绝
    if ($webhook->eventType == 'net.authorize.payment.fraud.declined') {}
}

欺诈检索套件

Fraud Detection Suite 欺诈检索套件是 Authorize.net 提供的打击欺诈、预防欺诈过滤的服务。如:交易IP速度过滤器、IP运送地址不匹配过滤器、增强的CCV处理过滤器等,详细可查看 官方文档 介绍。

在商家中心 TOOLS > Fraud Detection Suite 中设置防欺诈筛选器或工具。设置完成后,当触发这些规则时,Authorize.net 后台会根据我们的设置,进行相对应的处理,并通过 Webhook 通知我们。

比如官方默认开启的 事务IP速度滤波器(Transaction IP Velocity Filter),设置 Filter Actions(过滤操作) 为 Authorize and hold for review(授权并等待审核),允许每小时来自相同IP地址的不超过10次支付事务。当某个客户端 IP 触发了这个过滤器,Authorize.net 后台会将订单设置成手动操作的状态,并通过 Webhook 向我们发出 net.authorize.payment.fraud.held(通知您交易被视为可疑)的通知。

The Transaction IP Velocity Filter allows you to specify the maximum number of transactions allowed from the same Internet protocol (IP) address per hour. If your account receives more transactions from the same IP address in an hour than the threshold you set, all exceeding transactions received that hour will be flagged and processed according to the filter action selected below.

事务IP速度过滤器允许您指定同一Internet协议(IP)地址每小时允许的最大事务数。如果您的帐户在一小时内从相同的IP地址接收到的交易超过您设置的阈值,则所有超过该小时接收的交易将被标记并根据下面选择的筛选操作进行处理。

Filter Actions 解释

当事务触发此筛选器时,请执行以下操作:

  • Process as normal and report filter(s) triggered:正常通过,不做限制;
  • Authorize and hold for review:授权并等待审核。授权银行发起支付,但需要我们在商家中心手动操作拒绝/批准。
  • Do not authorize, but hold for review:不授权,但保留审查。不授权银行发起支付,在商家中心手动操作拒绝/批准,当操作批准后,才向银行授权发起支付。
  • Decline the transaction:直接拒绝交易。

Webhooks

Webhooks 是由 Authorize.Net 帐户中的系统事件生成的自动通知。可在商家中心 Account > Settings > Business Settings > Webhooks 中选择设置。

响应说明

系统事件包括对交易,客户资料和订阅的更改,以及对欺诈过滤器的响应。

本文仅对交易、欺诈过滤器响应解释,具体其它事件请参考官方文档。

net.authorize.payment.authorization.created:通知您已创建授权交易。授权 Authorize.net 向银行发起交易。
net.authorize.payment.authcapture.created:通知您已创建授权和捕获事务。
net.authorize.payment.capture.created:通知您已创建捕获事务。Authorize.net 后台手动处理返回。
net.authorize.payment.refund.created:通知您已成功结算的交易已退款。
net.authorize.payment.priorAuthCapture.created:通知您先前的授权已被捕获。
net.authorize.payment.void.created:通知您未结算的交易已作废。
net.authorize.payment.fraud.held:通知您交易被视为可疑。触发防欺诈过滤器机制。
net.authorize.payment.fraud.approved:通知您先前持有的交易已获批准。后台手动批准可疑订单。
net.authorize.payment.fraud.declined:通知您先前举行的交易被拒绝。后台手动拒绝可疑订单。

监控响应

交易无异常:

请求响应:
response:{"responseCode":"4","authCode":"OAMBI8","avsResultCode":"Y","cvvResultCode":"P","cavvResultCode":"2","transId":"60140439522","refTransID":"","transHash":"","testRequest":"0","accountNumber":"XXXX1111","accountType":"Visa","messages":{"message":[{"code":"253","description":"Your order has been received. Thank you for your business!"}]},"transHashSha2":"F0BEF529CE9E378EB70F27A5CE2355519A4C81A93309008F7C474B10CB41E6A75E3A4B41F85FA5D6BBED603BDA50C4004C16337B1E3AF3B3E63AC08774CA00AD","networkTransId":"5AR29NZ3RO58EKLHOZUXF1L"}

Webhook响应:
response:{"notificationId":"6744903d-fdb2-4942-a1e8-5484ca067e7d","eventType":"net.authorize.payment.authcapture.created","eventDate":"2020-04-01T04:06:35.6547012Z","webhookId":"12edde21-8709-4e09-8c3a-977e540098cb","payload":{"responseCode":1,"authCode":"7O4RZY","avsResponse":"Y","authAmount":51.439999999999998,"fraudList":[{"fraudFilter":"Transaction IP Velocity Filter","fraudAction":"report"},{"fraudFilter":"Shipping Address Verification Filter","fraudAction":"report"}],"merchantReferenceId":"ref1585713994","entityName":"transaction","id":"60140359752"},"_url":"\/api\/payment\/authorizeWebhook"}
transId:60140359752
eventType:net.authorize.payment.authcapture.created

开启防欺诈过滤器,设置 Filter Actions 为 Do not authorize, but hold for review. 不授权,但保留审查。触发该过滤器时:

2020-04-01 12:31:45
请求响应:{json_data}

2020-04-01 12:32:00
Webhook响应:{json_data}
transId:60140360383
eventType:net.authorize.payment.fraud.held

# 在商家后台手动批准后

2020-04-01 13:53:29
Webhook响应:{json_data}
transId:60140360383
eventType:net.authorize.payment.fraud.approved


2020-04-01 13:53:29
Webhook响应:{json_data}
transId:60140360383
eventType:net.authorize.payment.authcapture.created

开启防欺诈过滤器,设置 Filter Actions 为 Authorize and hold for review. 授权并等待审核。触发过滤器时:

2020-04-01 12:15:52
请求响应:{json_data}

2020-04-01 12:16:08
Webhook响应:{json}
transId:60140359980
eventType:net.authorize.payment.fraud.held

2020-04-01 12:16:08
Webhook响应:{json_data}
transId:60140359980
eventType:net.authorize.payment.authorization.created

# 在商家后台手动批准后

2020-04-01 12:25:24
Webhook响应:{json_data}
transId:60140360176
eventType:net.authorize.payment.fraud.approved

2020-04-01 12:25:24
Webhook响应:{json_data}
transId:60140360176
eventType:net.authorize.payment.capture.created

处理订单状态

本文示例中,涉及订单状态如下:

  • 10 = 待付款(Awaiting Payment)
  • 20 = 已付款,未收到(Pending)
  • 30 = 已付款,待发货(Processing)

结合上述,首先在验证通过的情况下,发起支付,设置使用 authCaptureTransaction 事件,生成订单,此时订单状态为 20;当收到 Webhook 通知时,我们需要根据对应的通知事件,来进行订单状态的更新。

建议:在发起支付后,存储响应数据至日志;Webhook 通知,存储通知数据至日志。

net.authorize.payment.authorization.created
通知您已创建授权交易。授权 Authorize.net 向银行发起交易。无操作。

net.authorize.payment.authcapture.created
通知您已创建授权和捕获事务。订单状态更新为 30。

net.authorize.payment.capture.created
通知您已创建捕获事务。Authorize.net 后台手动处理返回。订单状态跟新为 30.

net.authorize.payment.refund.created
通知您已成功结算的交易已退款。订单状态更新为 50。因项目需要,本文示例中未使用退款接口。所以在此处,原则上订单状态是不做处理的。

net.authorize.payment.priorAuthCapture.created
通知您先前的授权已被捕获。无操作。

net.authorize.payment.void.created
通知您未结算的交易已作废。无操作。

net.authorize.payment.fraud.held
通知您交易被视为可疑。触发防欺诈过滤器机制。无操作。

net.authorize.payment.fraud.approved
通知您先前持有的交易已获批准。后台手动批准可疑订单。无操作。

net.authorize.payment.fraud.declined
通知您先前举行的交易被拒绝。后台手动拒绝可疑订单。无操作。

© 2020 Lh1010 - 豫ICP备16115435号-1