热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

使用Authorize.net的SDK实现符合PCI标准的支付流程

PCI标准是为了最大限度保护持卡人数据的一套标准。要求很多,可以看PCI标准站点了解。对于程序猿来说,要保证的是用户的任何支付信息,都不走

PCI 标准是为了最大限度保护持卡人数据的一套标准。要求很多,可以看 PCI标准 站点了解。对于程序猿来说,要保证的是用户的任何支付信息,都不走自己的服务器,不保存在自己的数据库。

实现符合PCI标准的支付,有两种方式

  • 加载Authorize.net的托管表单

  • 使用AcceptJs

Authorize.net的托管表单,加载方便,安全性高,但是用户定制程度不高,只能稍微改改表单样式,AcceptJs可以使用自己设计的表单,调用AcceptJs做安全性校验和数据发送接收。

一. 前期准备工作

1.1 注册一个沙盒环境账号 (必须)

沙盒环境账号,可以用来在api文档页面直接调试各种接口,也可以在沙盒里面查看各种扣款记录。

如果项目要上线,请注册生产环境账号,这里全部使用沙盒环境。

1.2 下载Authorize.net SDK (非必须)

下载SDK到项目。

cd /your_php_project_path
composer require authorizenet/authorizenet

再在项目中引入即可(如何引入可以看上面地址的介绍,这里不再重复)。

该项目的GITHUB地址:AuthorizeNet/sdk-php 可以在上面搜索、提出你的issues

使用SDK的php案列:AuthorizeNet/sample-code-php

Authorizenet官方实现的一个符合PCI标准的案列AuthorizeNet/accept-sample-app (这个没有使用SDK)

1.3 不使用Authorize.net SDK (非必须)

因为Authorize.net SDK 要求 php: >=5.5 , 所以只能自己封装api请求了,具体如何封装个人自便,但要说明的一点是,Authorize.net 的api,如果选择的是json格式:

header("Content-type:text/json;charset=utf-8");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->authorizeUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_COOKIESESSION, true);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, urldecode($data));
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
// curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain')); //xml request
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/json'));
$result = curl_exec($curl);
$curlErrno = curl_errno($curl);
$curlError = curl_error($curl);
curl_close($curl);

返回的数据也是JSON格式,but。。。。,这个返回的json数据,是无法用

json_decode($result,true)

来解析的,需要

json_decode(substr($result, 3), true);

来解析。究其原因,应该是它返回的数据带了BOM头,详细请移步 json-decode-returns-null

XML格式我没有去写代码测试,各位有兴趣可以自行测试,也可以在沙盒环境直接测试。

有个直接扣款的API,其中的ORDER参数要有顺序,要有顺序,要有顺序,如果遇到一些API,调试一直报错,但又没有特别的原因,请注意看是否是顺序问题。

1.4 各种环境地址

内容测试环境生产环境
api请求地址apitest urlapi url
Accept.jsAccept jstest urlAccept js url
请求支付表单test payment/paymentaccept payment/payment
Manage ProfilesManage ProfilesManage Profiles
Add Payment ProfileAdd Payment ProfileAdd Payment Profile
Add Shipping ProfileAdd Shipping ProfileAdd Shipping Profile
Edit Payment ProfileEdit Payment ProfileEdit Payment Profile
Edit Shipping ProfileEdit Shipping ProfileEdit Shipping Profile

二. iframe 加载托管表单方式发起支付

1. 加载iframe托管表单创建用户的payment Info。

1.1 为用户申请创建CustomerProfileID

需要请求的API : createCustomerProfileRequest
API的详细文档地址:createCustomerProfileRequest
CustomerProfile详细介绍:customer_profiles

该API可以在创建CustomerProfileId 的同时,也创建PaymentProfileId 。但是PaymentProfileId需要的参数都是涉及到用户敏感信息的,按照PCI标准,是不允许商户收集,所以需要使用Authorize.net的托管表单来创建。
所以这一步只简单的传递几个参数即可,使用SDK创建代码:

$customerProfile = new AnetAPI\CustomerProfileType();
$customerProfile->setDescription("Customer 2 Test PHP");
$customerProfile->setMerchantCustomerId('11211');
$customerProfile->setEmail($post['email']);
$request = new AnetAPI\CreateCustomerProfileRequest();
$request->setMerchantAuthentication($this->merchantAuthentication);
$request->setProfile($customerProfile);
$controller = new AnetController\CreateCustomerProfileController($request);
$response = $controller->executeWithApiResponse(\net\authorize\api\constants\ANetEnvironment::SANDBOX);

1.2 为添加PaymentInfo托管表单申请token

需要请求的API : getHostedProfilePageRequest
API的详细文档地址:getHostedProfilePageRequest

用上一步创建的CustomerProfileId $profileId = $response->getCustomerProfileId(); 来获取token

$setting = new AnetAPI\SettingType();
$setting->setSettingName("hostedProfileIFrameCommunicatorUrl");
$url = \Yii::$app->urlManager->createAbsoluteUrl(['authorizenet/special']);
$setting->setSettingValue($url);
$request = new AnetAPI\GetHostedProfilePageRequest();
$request->setMerchantAuthentication($this->merchantAuthentication);
$request->setCustomerProfileId($profileId);
$request->addToHostedProfileSettings($setting);
$controller = new AnetController\GetHostedProfilePageController($request);
$response = $controller->executeWithApiResponse(
\net\authorize\api\constants\ANetEnvironment::SANDBOX);

1.3 视图页面iframe使用token加载托管表单

"/>

此时该iframe里面还没有任何东西,需要提交这个form表单才能加载托管表单,这里给一个函数让他页面加载的时候自动提交以加载托管表单。

var button = document.getElementById('submit');
button.click();

1.4 捕获响应并处理

我们回到 1.2 申请表单这里,这个API支持设置托管表单的很多属性,比较有用的有 :

hostedProfileReturnUrl : 设置托管会话结束(用户点击SAVE)返回给用户的页面 (这里省略)
hostedProfileIFrameCommunicatorUrl : 用来接受、处理Authorize.net响应的页面

上面设置的hostedProfileIFrameCommunicatorUrl的页面为authorizenet/special

function callParentFunction(str) {var referrer = document.referrer;var s = {qstr : str , parent : referrer};if(referrer == 'https://test.authorize.net/customer/addPayment'){switch(str){case 'action=successfulSave' :window.parent.parent.location.href="https://www.basic.com/authorizenet/payment";break;}}
}function receiveMessage(event) {if (event && event.data) {callParentFunction(event.data);}
}if (window.addEventListener) {window.addEventListener("message", receiveMessage, false);
} else if (window.attachEvent) {window.attachEvent("onmessage", receiveMessage);
}if (window.location.hash && window.location.hash.length > 1) {callParentFunction(window.location.hash.substring(1));
}

这里设置成功保存paymentInfo 信息到Authorize.net之后就跳转到 payment 页面支付。
action有不同的状态,可以根据action作相应的处理。
resizeWindow : 托管表单加载
successfulSave : 表单成功保存(CustomerProfile)
cancel : 用户点击取消按钮
transactResponse :支付成功(payment)

2. 加载iframe托管表单发起支付

1.1 通过上面的CustomerProfileId,获取用户填写的PaymentInfo,用来回填支付表单

需要请求的API : getCustomerProfileRequest
API的详细文档地址:getCustomerProfileRequest

$customer = $this->getCustomerProfile($profileId);
$billTo = end($customer->getProfile()->getPaymentProfiles())->getBillTo();

因为一个CustomerProfi对应多个PaymentProfile ,这里获取最后一个PaymentProfile

1.2 为添加Payment托管表单申请token

需要请求的API : getHostedPaymentPageRequest
API的详细文档地址:getHostedPaymentPageRequest
请求该URL,可以指定加载表单的样式等各种参数,具体参考:Accept Hosted feature details page

$transactionRequestType = new AnetAPI\TransactionRequestType();
$transactionRequestType->setTransactionType("authCaptureTransaction");
$transactionRequestType->setAmount("12.23");
$customer = $this->getCustomerProfile(\Yii::$app->session->get('profileId'));
$billTo = end($customer->getProfile()->getPaymentProfiles())->getBillTo();$transactionRequestType->setBillTo($billTo);//回填账单地址
$customer = new AnetAPI\CustomerDataType();
$customer->setEmail(\Yii::$app->session->get('email'));
$customer->setId(\Yii::$app->session->get('user_id'));
$transactionRequestType->setCustomer($customer);$request = new AnetAPI\GetHostedPaymentPageRequest();
$request->setMerchantAuthentication($this->merchantAuthentication);
$request->setTransactionRequest($transactionRequestType);
$setting3 = new AnetAPI\SettingType();
$setting3->setSettingName("hostedPaymentReturnOptions");
$setting3->setSettingValue("{\"url\": \"https://www.basic.com/index.php?r=authorizenet/receipt\", \"cancelUrl\": \"https://www.basic.com/index.php?r=authorizenet/cancel\", \"showReceipt\": false}");
$request->addToHostedPaymentSettings($setting3);//设置托管表单显示email,且必填 (因为form表单没有禁止修改email参数,所以可以设置email但不显示在表单中,以防修改)
$setting4 = new AnetAPI\SettingType();
$setting4->setSettingName('hostedPaymentCustomerOptions');
$setting4->setSettingValue("{\"showEmail\": true, \"requiredEmail\":true}");
$request->addToHostedPaymentSettings($setting4);$setting6 = new AnetAPI\SettingType();
$setting6->setSettingName('hostedPaymentIFrameCommunicatorUrl');
$url = \Yii::$app->urlManager->createAbsoluteUrl(['authorizenet/special']);
$setting6->setSettingValue("{\"url\": \"".$url."\"}");
$request->addToHostedPaymentSettings($setting6);
$controller = new AnetController\GetHostedPaymentPageController($request);
$response = $controller->executeWithApiResponse( \net\authorize\api\constants\ANetEnvironment::SANDBOX);if (($response != null) && ($response->getMessages()->getResultCode() == "Ok") ) {return $response->getToken();
}

1.3 视图页面iframe使用token加载托管表单


" />


1.4 捕获响应并处理。

同 二.1.14 一致,可以设置为同一个页面,通过referrer来判断是完善支付信息表单的响应,还是支付表单的响应
如:

if(referrer == 'https://test.authorize.net/customer/addPayment'){//your code
}else if(referrer == 'https://test.authorize.net/payment/payment'){//your code
}else if(other){//your code
}

3. 最终效果图

注册-完善支付-支付 流程

(支付完成后的处理我没做,无非就是弹个窗之类的告诉用户支付成功,再处理后台逻辑之类的)

可以看到,这里只可以回填账单地址、客户电话和email之类的信息。信用卡、信用卡过期时间、信用卡安全码等都无法回填,需要用户再次输入,用户体验非常不好。
所以支付这一步我们可以不用托管表单,使用通过CustomerProfileID发起支付的API来完成

需要请求的API : createTransactionRequest
API的详细文档地址:createTransactionRequest

$paymentprofileid = $this->getCustomerProfile($profileid);
$profileToCharge = new AnetAPI\CustomerProfilePaymentType();
$profileToCharge->setCustomerProfileId($profileid);
$paymentProfile = new AnetAPI\PaymentProfileType();
$paymentProfile->setPaymentProfileId($paymentprofileid);
$profileToCharge->setPaymentProfile($paymentProfile);$transactionRequestType = new AnetAPI\TransactionRequestType();
$transactionRequestType->setTransactionType( "authCaptureTransaction");
$transactionRequestType->setAmount(5);
$transactionRequestType->setProfile($profileToCharge);$request = new AnetAPI\CreateTransactionRequest();
$request->setMerchantAuthentication($this->merchantAuthentication);
$request->setTransactionRequest( $transactionRequestType);
$controller = new AnetController\CreateTransactionController($request);
$response = $controller->executeWithApiResponse( \net\authorize\api\constants\ANetEnvironment::SANDBOX);

4. 结尾补充

托管表单要求你的程序挂载在HTTPS域名下

还可以通过CustomerProfileId、paymentProfileId发起ARB(Auto Recurring Billing)扣款
需要请求的API : ARBCreateSubscriptionRequest
API的详细文档地址:getHostedPaymentPageRequest
关于APB的详细介绍请看:recurring_billing

关于测试请看:testing_guide
可以填写不同的 Zip Code 和 Card Code 来模拟不同的错误返回

三. AccceptJs方式发起支付

(缺)

1. 加载AccpectJS

(缺)

2. 巴拉巴拉

(缺)

缺失的内容请自行参考官方demo。。。。。



推荐阅读
  • 移动端常用单位——rem的使用方法和注意事项
    本文介绍了移动端常用的单位rem的使用方法和注意事项,包括px、%、em、vw、vh等其他常用单位的比较。同时还介绍了如何通过JS获取视口宽度并动态调整rem的值,以适应不同设备的屏幕大小。此外,还提到了rem目前在移动端的主流地位。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • 从Oracle安全移植到国产达梦数据库的DBA实践与攻略
    随着我国对信息安全和自主可控技术的重视,国产数据库在党政机关、军队和大型央企等行业中得到了快速应用。本文介绍了如何降低从Oracle到国产达梦数据库的技术门槛,保障用户现有业务系统投资。具体包括分析待移植系统、确定移植对象、数据迁移、PL/SQL移植、校验移植结果以及应用系统的测试和优化等步骤。同时提供了移植攻略,包括待移植系统分析和准备移植环境的方法。通过本文的实践与攻略,DBA可以更好地完成Oracle安全移植到国产达梦数据库的工作。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 本文介绍了使用Python解析C语言结构体的方法,包括定义基本类型和结构体类型的字典,并提供了一个示例代码,展示了如何解析C语言结构体。 ... [详细]
author-avatar
宝宝贝贝198812126
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有