支付宝沙箱支付接入流程
1、首先进入支付宝开放平台申请个人开发者账号
2、创建应用,添加电脑网站支付能力
这里我申请的是支付宝网站支付功能。
3、进入支付宝沙箱环境设置接口加签方式我这里设置的是自定义密钥(RSA2)
文档:https://opendocs.alipay.com/common/02kipl
这里我们需要获取我们需要的秘钥:应用公钥、支付宝公钥,还需要记住生成应用公钥的时候我们自己保存的应用私钥,这三个参数,其中应用公钥、支付宝公钥都可以在沙箱账号中查看,应用私钥是自己有的。
其他信息:沙箱应用的appid,以及沙箱账户的商户账号密码,和用户账号密码.方便付款操作。
4、下载sdk
https://opendocs.alipay.com/open/270/106291
修改sdk中的config.php文件
<?php
$config = array (
//应用ID,您的APPID,这里是沙箱账户中的appid。
'app_id' => "",
//商户私钥,自己生成的私钥
'merchant_private_key' => "",
//异步通知地址
'notify_url' => "http://cakephp.com/alipay/aliPayNotify",
//同步跳转
'return_url' => "http://cakephp.com/api/alipayCallback",
//编码格式
'charset' => "UTF-8",
//签名方式
'sign_type'=>"RSA2",
//支付宝网关(这里记得沙箱网址为https://openapi.alipaydev.com)
//'gatewayUrl' => "https://openapi.alipay.com/gateway.do",
'gatewayUrl' => "https://openapi.alipaydev.com/gateway.do",
//支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
'alipay_public_key' => "",
//日志路径
'log_path' => "",
);
到这里我们就可以参考sdk代码编写支付代码。
代码
//去支付逻辑,点击支付按钮调用该接口
public function pay()
{
$request = $_SERVER['REQUEST_METHOD'] == "POST" ? $_POST : $_GET;
//过滤参数
$data = $this->publicFunction->filterParams($request);
//模拟订单
$orderId = "PAY".time().rand(1000000,10000000); //模拟订单编号
$subject = "金币"; //模拟商品名称
$amount = rand(0.1,100); //模拟金额
$body = "买了不会发货"; //模拟描述
//支付方式,默认alipay
switch ($data['pay_type']) {
case "wechet":
break;
case "alipay":
default :
//这里使用的是自动提交表单,这里将表单隐藏,避免显示
echo "<form id='alipayment' action='/alipay/pagepay/pagepay.php' method='post' style='display: none;'>
<input id='WIDout_trade_no' name='WIDout_trade_no' value=$orderId/>
<input id='WIDsubject' name='WIDsubject' value=$subject/>
<input id='WIDtotal_amount' name='WIDtotal_amount' value=$amount />
<input id='WIDbody' name='WIDbody' value=$body/>
</form><script>document.getElementById('alipayment').submit();</script>";
break;
}
}
//支付宝同步回调
public function alipayCallback()
{
$request = $_SERVER['REQUEST_METHOD'] == "POST" ? $_POST : $_GET;
$data = $this->publicFunction->filterParams($request);
$config = "";//无用
require_once("./alipay/config.php");
require_once './alipay/pagepay/service/AlipayTradeService.php';
$alipaySevice = new \AlipayTradeService($config);
//检验参数
$result = $alipaySevice->check($data);
if ($result) {
//这里可以做支付成功的页面跳转
echo "支付成功"; //test
} else {
//这里可以做支付失败的页面跳转
echo "支付失败"; //test
}
die;
}
//支付宝异步回调
public function aliPayNotify()
{
$request = $_SERVER['REQUEST_METHOD'] == "POST" ? $_POST : $_GET;
$data = $this->publicFunction->filterParams($request);
$config = "";//无用
//记录请求参数,本次请求的详细信息,方便排查问题
$this->log('aliPayNotify-params:'.json_encode($data),LOG_INFO);
require_once("./alipay/config.php");
require_once './alipay/pagepay/service/AlipayTradeService.php';
$alipaySevice = new \AlipayTradeService($config);
$result = $alipaySevice->check($data);
//验签失败
if (!$result){
//验签失败记录日志
$this->log('aliPayNotify-error:验签失败'.json_encode($data),LOG_ERR);
echo "fail";die;
}
//商户订单号
$out_trade_no = $data['out_trade_no'];
//交易总金额
$total_amount = $data['total_amount'];
//交易状态
$trade_status = $data['trade_status'];
if ($trade_status == "TRADE_FINISHED"){
//成功记录日志
$this->log('aliPayNotify-success',LOG_INFO);
echo "success";die;
}
//对订单做判断
//订单号是否存在数据库中查看
$order_id = "PAY16443728188675342";//test
//总交易金额,数据库中查看
$amount = 100;//test
if ($total_amount != $amount) {
//记录失败:订单金额与支付金额不符
$this->log('aliPayNotify-error:订单金额与支付金额不符'.json_encode($data),LOG_ERR);
echo "fail";die;
}
if (!$order_id){
//记录失败:订单不存在
$this->log('aliPayNotify-error:订单不存在'.json_encode($data),LOG_ERR);
echo "fail";die;
}
//这里我只做了一个简单的模拟支付demo,未对数据库、数据进行操作,后续有需要时会详细补充支付成功后的逻辑代码。
//支付成功逻辑处理代码,对订单以及其他逻辑进行修改
//记录支付宝交易信息到日志记录表中,需要记录支付宝的支付编号,以及此次支付宝传递过来的参数,方便后续排查。
//记录成功日志
$this->log('aliPayNotify-success',LOG_INFO);
echo "success";die;
}
模拟支付宝验证签名流程
控制器代码:
//验签流程
//获取签名
public function getSign($params)
{
if (!empty($params)) {
//验证签名时传递的参数会多两个将哪两个参数去掉,不然验证会失败
if (isset($params['sign']) || isset($params['sign_type'])) {
unset($params['sign']);
unset($params['sign_type']);
}
//现将数组进行排序,使用ksort改变数组的顺序,保证同样的数组不会出现其他排列顺序
ksort($params);
$signStr = "";
foreach ($params as $k => $v){
//$v为空不做加密处理
if (empty($v)){
continue;
}
//避免最后多一个&符号
if ($k == 0){
$signStr .= $k.'='.$v;
}else{
$signStr .= '&'.$k.'='.$v;
}
}
//加密签名操作
$sign = $this->publicFunction->encrypt($signStr); //encrypt为自己封装的加密方法
$res = [
'sign' => $sign,
'sign_type' => "encrypt" //这里返回加密的类型,自定义的名称
];
return $res;
}
return false;
}
//验证签名
public function checkSign($params)
{
$newSign = $this->getSign($params);
if ($newSign['sign'] == $params['sign']) {
return true;
}
return false;
}
//发送请求
public function send()
{
$data = [
'out_trade_no' => '213123123123123123',
'total_amount' => '100.00',
'time' => time(),
];
$sign = $this->getSign($data);
$data['sign'] = $sign['sign'];
$data['sign_type'] = $sign['sign_type'];
$url = "http://cakephp.com/api/notify";
//请求对签名进行验证
$res = $this->publicFunction->curlRequest($url, true, $data);
var_dump($res);die;//未修改签名参数则验证成功,修改过则验证失败。
}
public function notify()
{
//参数获取
$request = $_SERVER['REQUEST_METHOD'] == "POST" ? $_POST : $_GET;
$data = $this->publicFunction->filterParams($request);//封装的过滤接收参数的方法
//参数验证
$res = $this->checkSign($data);
if ($res){
echo "验证成功";die;
}else{
echo "验证失败";die;
}
}
助手函数
/**
*
* 过滤输入非安全字符
*
* @param $subject array or string ex: $_GET 需要过滤的数组或字符,可以直接传递$_GET/$_POST
* @param $substr array(key=>value) ex: array('username'=>12, 'desc'=>120) 数组字段允许的最大长度
* @param $allowhtml bool ex:true 是否允许html标签
*
* @return $subject
*
*/
function filterParams($subject, $substr = array(), $allowhtml = true)
{
//空字符串
if (empty($subject) || is_bool($subject) || is_numeric($subject))
return $subject;
if (is_array($subject)) {
foreach ($subject as $key => $val) {
//数组中存在限制长度的字段
if (array_key_exists($key, $substr) && is_string($val)) {
//截取指定长度
$val = mb_substr($val, 0, $substr[$key], 'utf-8');
}
//递归执行filterParams方法,将数组全部过滤。
$subject[$key] = $this->filterParams($val, $substr, $allowhtml);
}
return $subject;
} else {
//防止xss
$subject = str_ireplace(";", "", $subject);
$subject = str_ireplace("&", "&", $subject);
$subject = str_ireplace("<", "<", $subject);
$subject = str_ireplace(">", ">", $subject);
$subject = str_ireplace("'", "", $subject);
$subject = str_ireplace("--", "", $subject);
$subject = str_ireplace("%", "", $subject);
$subject = str_ireplace("$", "", $subject);
//防止sql注入
$subject = str_ireplace("select", "", $subject);
$subject = str_ireplace("join", "", $subject);
$subject = str_ireplace("union", "", $subject);
$subject = str_ireplace("where", "", $subject);
$subject = str_ireplace("insert", "", $subject);
$subject = str_ireplace("delete", "", $subject);
$subject = str_ireplace("update", "", $subject);
$subject = str_ireplace("like", "", $subject);
$subject = str_ireplace("drop", "", $subject);
$subject = str_ireplace("create", "", $subject);
$subject = str_ireplace("modify", "", $subject);
$subject = str_ireplace("rename", "", $subject);
$subject = str_ireplace("alter", "", $subject);
$subject = str_ireplace("cast", "", $subject);
//是否允许html标签,不允许去除html标签
return $allowhtml ? trim($subject) : trim(strip_tags($subject));
}
}
/**
* 发送curl请求
* @param $url 请求url
* @param bool $post 请求类型是否为post
* @param array $params 请求参数
* @param bool $https 是否为https请求
* @return bool|string
*/
function curlRequest($url, $post = true, $params = array(), $https = true)
{
//初始化请求会话
$ch = curl_init($url);
if ($post) {
//设置请求方式为post
curl_setopt($ch, CURLOPT_POST, true);
//设置请求参数
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
}
if ($https) {
//如果是https协议,禁止服务器验证本地证书
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
}
//发送请求,获取返回参数
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$res = curl_exec($ch);
//关闭请求
curl_close($ch);
return $res;
}
/**
* @param $data 加密字段
* @return string
*/
function encrypt($data)
{
$salt = "123123asdasdasd";
$psw = md5($salt . md5($data));
return $psw;
}