php开发第四方支付系统过程记录(2):完成了QQ支付及完善了整个支付流程,代码gon...
本帖最后由 勤勤学长 于 2020-1-31 15:17 编辑前文地址:
php开发第四方支付系统过程记录(1):起因及为什么参数会有sign签名
有前辈提到sign和key的理解不到位.。虽然我认为1提到的方法是也是有效的,但是经受不住考验。
我昨天查了一下关于对称加密和非对称加密的资料。
非对称加密对我这种小白来说还是太过复杂,就想着如何用小白的方式解决算法泄露的问题。
1文提到的key实际还是等同于密码,假设支付系统用户获知他pid和key之后,他可以通过不断提交错误的pid和key值,会一直进行查库操作,那直接抛弃sign,从key入手。
那么,如何在客户端接收的时候,查库之前判断key是否是正确格式的,是否和用户ID匹配。正确且匹配之后再查库呢?
要求:生成key的时候,就要有规律且是对称加密,且生成的key被修改就无法解密。
我建立了数据库
pid=用户ID
key=密钥
money=账户余额
为了保证可以重置key,那么,key是固定值+随机值组成。
我这里用的是 pid=1001&rand=20200130180433 进行加密,rand后边是当前时间,生成/重置key的时间。
用参数的格式是为了方便服务端解密之后转成数组,
解密key失败拦截;
解密key里边的pid和提交支付订单的pid不一致就拦截。
本次开发使用的是thinkphp5.0框架
先把加解密方式写出来
//加密方式
public function encode($data = '')
{
return openssl_encrypt($data, 'DES-ECB', '318692996', 0);
}
//解密方式
public function decode($data = '')
{
return openssl_decrypt($data, 'DES-ECB', '318692996', 0);
}
生成key
$key = $this->encode('pid=1001&rand='.date("YmdHis"));
商户信息到此就完成。
新增订单数据表
CREATE TABLE `xz_order`(
`out_trade_no` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商户订单号',
`trade_no` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '支付宝交易号',
`money` decimal(7, 2) NULL DEFAULT NULL COMMENT '订单金额',
`total_amount` decimal(7, 2) NULL DEFAULT NULL COMMENT '订单金额',
`trade_status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '交易状态',
`gmt_create` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '交易创建时间',
`gmt_payment` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '交易付款时间 ',
`paytype` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '支付方式',
`subject` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '订单描述',
`pid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户id',
`notify_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '异步通知',
`return_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '同步通知'
) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
为了方便后边使用post,写个curl函数
public function http_post($url,$post_data=null)
{
$con=curl_init((string)$url);
curl_setopt($con,CURLOPT_HEADER,False);
curl_setopt($con,CURLOPT_SSL_VERIFYPEER,False);
curl_setopt($con,CURLOPT_RETURNTRANSFER,true);
if ($post_data!=null) {
curl_setopt($con, CURLOPT_POSTFIELDS, $post_data);
}
curl_setopt($con,CURLOPT_TIMEOUT,2);
$result = curl_exec($con);
return $result;
}
演示提交订单信息
public function test()
{
$url = 'http://pay.11ak.cn/index/index/submit';
$out_trade_no =date("YmdHis").mt_rand(1000,9999);
$key =urlencode( 'Cm50Zej3dtS5FHchEDhdyqxE4pp1qw0ck+hYQuc0HRg=');
$str = 'pid=1001&key='.$key.'&out_trade_no='.$out_trade_no."¬ify_url=http://pay.11ak.cn/index/index/testpost&return_url=http://pay.11ak.cn/index/index/testget&subject=订单标题&money=0.01&type=qqpay";
echo $this->http_post($url, $str);
}
生成订单
public function submit()
{
$data = input('post.');
if (empty($data['pid']) || empty($data['key'])|| empty($data['type']) || empty($data['out_trade_no']) || empty($data['notify_url']) || empty($data['return_url']) || empty($data['subject']) || empty($data['money'])) {
return json(['code' => 101, 'msg' => '参数不完整']);
}
if(strlen($data['out_trade_no']) < 16 ||strlen($data['out_trade_no']) >64){
return json(['code' => 102, 'msg' => 'out_trade_no长度不能少于16或大于64位']);
}
if ($data['type'] != 'alipay' && $data['type'] != 'qqpay' && $data['type'] != 'wxpay') {
return json(['code' => 106, 'msg' => '支付方式不存在']);
}
$key = $this->decode($data['key']);
if (!$key) {
//解密失败
return json(['code' => 108, 'msg' => 'key格式不正确']);
}else{
parse_str($key, $key_data );//把url转为数组格式
if ($key_data['pid'] != $data['pid']) {
return json(['code' => 109, 'msg' => 'pid与key不匹配']);
}
}
$userRes = db('user')->where(['pid' => $data['pid'], 'key' => $data['key']])->find();
if (!$userRes) {
return json(['code' => 103, 'msg' => 'pid或key错误']);
}
$orderRes = db('order')->where('out_trade_no',$data['out_trade_no'])->find();
if ($orderRes) {
return json(['code' => 104, 'msg' => 'out_trade_no已存在']);
}
$sql = array();
$sql['out_trade_no'] = $data['out_trade_no'];//本地单号
$sql['money'] = $data['money'];//订单金额
$sql['subject'] = $data['subject'];//订单标题
$sql['pid'] = $data['pid'];//用户ID
$sql['paytype'] = $data['type'];//付款方式
$sql['gmt_create'] =date('Y-m-d H:i:s',time());//时间;
$sql['notify_url'] = $data['notify_url'];
$sql['return_url'] = $data['return_url'];
$addRes = db('order')->insert($sql);
if ($addRes) {
$url = 'http://pay.11ak.cn/index/index/pay?order='.urlencode($this->encode(http_build_query($data)));
return "<script language='javascript' type='text/javascript'> window.location.href='".$url."';</script>";
//header('location:'.$url);
}else{
return json(['code' => 105, 'msg' => '订单数据添加失败']);
}
}
订单校验且生成成功,生成带参数的url,跳转到订单支付页面。
public function pay()
{
$str = input('get.order');
if (empty($str)) {
return $this->err('order为空,请返回来源地重新发起请求!');
}
$str = $this->decode($str);
parse_str($str, $data); //第一个参数为字符串,第二个参数为结果
//halt($data);
if (empty($data['pid']) || empty($data['key']) || empty($data['type'])|| empty($data['out_trade_no']) || empty($data['notify_url']) || empty($data['return_url']) || empty($data['subject']) || empty($data['money'])) {
return $this->err('参数不完整,请返回来源地重新发起请求!');
}
$orderRes = db('order')->where('out_trade_no',$data['out_trade_no'])->find();
if ($orderRes) {
if ($orderRes['trade_status'] == 1) {
//订单已完成支付
$post_data = array();
$post_data['trade_no'] = $orderRes['trade_no'];//交易订单号
$post_data['out_trade_no'] = $orderRes['trade_no'];//本地订单号
$post_data['total_amount'] = $orderRes['out_trade_no'];//实际付款金额
$post_data['gmt_payment'] = $orderRes['gmt_payment'];//付款时间
$post_data['trade_status'] = 'TRADE_SUCCESS';//交易状态
$return_url =urldecode($orderRes['return_url'].'?'.http_build_query($post_data));
return "<script language='javascript' type='text/javascript'> window.location.href='".$return_url."';</script>";
}
}else{
return $this->err('订单不存在!');
}
if ($data['type']=='alipay') {
}elseif ($data['type']=='qqpay') {
$site['title'] = 'QQ钱包';
$site['type'] = '手机QQ';
//下面是QQ钱包的支付代码
$params = array();
$params["out_trade_no"] = $data['out_trade_no'];//商户订单号
$params["mch_id"] = "318692996";//我的id
$params["body"] = $data['subject'];//商品描述
$params["nonce_str"] = \qqpay\Util::createNoncestr();//随机字符串
$params["fee_type"] = "CNY";//货币类型定义
$params["notify_url"] = "http://pay.11ak.cn/index/index/notify_url";//异步通知地址
$params["spbill_create_ip"] = $_SERVER["REMOTE_ADDR"];//访问者IP
$params["total_fee"] = $data['money']*100;//订单金额,单位是分,所以要×100
$params["trade_type"] = "NATIVE";//Native 场景类型
//api调用
$qpayApi = new \qqpay\Api('https://qpay.qq.com/cgi-bin/pay/qpay_unified_order.cgi', null, 10);
$ret = $qpayApi->reqQpay($params);
$data3 = \qqpay\Util::xmlToArray($ret);
$cards = array_merge($data3, $orderRes);
//手机QQ直接支付
if (strstr($_SERVER['HTTP_USER_AGENT'],'QQ/')) {
return "<script language='javascript' type='text/javascript'> window.location.href='".$cards['code_url']."';</script>";
}
$this->assign('data',$cards);
$this->assign('site',$site);
return $this->fetch();
}elseif ($data['type']=='wxpay') {
}else{
return $this->err('支付方式'.$data['type'].'不存在');
}
}
前端页面,监控订单是否完成
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Language" content="zh-cn">
<meta name="renderer" content="webkit">
<title>安全支付 - 勤勤学长支付站点</title>
<link href="/static/assets/css/mqq_pay.css?v=1" rel="stylesheet" media="screen">
<linkrel="stylesheet">
</head>
<body>
<div class="body">
<h1 class="mod-title">
<span class="ico-wechat"></span><span class="text">{$site.type}支付</span>
</h1>
<div class="container">
<div class="row">
<div class="col-md-8 col-sm-6 col-md-offset-2 col-sm-offset-3">
<div class="mod-ct">
<div class="order">
</div>
<div class="amount">{$data.money}元</div>
<div class="qr-image" id="qrcode" title="{$data.code_url}">
</div>
<div class="detail" id="orderDetail">
<div class="detail-ct" style="display: none;">
<p>购买物品:{$data.subject}</p>
<p>商户订单号:{$data.out_trade_no}</p>
<p>创建时间:{$data.gmt_create}</p>
</div>
<a href="javascript:void(0)" class="arrow"><i class="ico-arrow"></i></a>
</div>
<div class="tip"><!--
<span class="dec dec-left"></span>
<span class="dec dec-right"></span>-->
<div class="ico-scan"></div>
<div class="tip-text">
<p>请使用{$site.title}扫一扫</p>
<p>扫描二维码完成支付</p>
</div>
</div>
<div class="tip-text">
</div>
</div>
</div>
</div>
</div>
<div class="foot">
<div class="inner">
<p><atarget="_blank">南宁市勤哥网络科技工作室</a> </p>
</div>
</div>
</div>
<script src="/static/assets/js/qrcode.min.js"></script>
<script src="/static/assets/js/qcloud_util.js"></script>
<script src="/static/assets/js/layer.js"></script>
<script>
var isSafari = navigator.userAgent.indexOf("Safari") > -1;
var code_url = '{$data.code_url}';
var t;
var qrcode = new QRCode("qrcode", {
text: code_url,
width: 230,
height: 230,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H
});
// 订单详情
$('#orderDetail .arrow').click(function (event) {
if ($('#orderDetail').hasClass('detail-open')) {
$('#orderDetail .detail-ct').slideUp(500, function () {
$('#orderDetail').removeClass('detail-open');
});
} else {
$('#orderDetail .detail-ct').slideDown(500, function () {
$('#orderDetail').addClass('detail-open');
});
}
});
// 检查是否支付完成
function loadmsg() {
$.ajax({
type: "GET",
dataType: "json",
url: "{:url('getshop')}",
timeout: 10000, //ajax请求超时时间10s
data: {order: "{php}echo $_GET['order'];{/php}"}, //post数据
success: function (data, textStatus) {
//从服务器得到数据,显示数据并继续查询
if (data.code == 1010) {
clearTimeout(t);
if (confirm("您已支付完成,需要跳转到用户中心吗?")) {
//layer.msg('支付成功,正在跳转中...', {icon: 1, shade: 0.01, time: 15000});
window.location.href = data.return_url;
} else {
// 用户取消
}
} else if (data.code == 1014) {
t = setTimeout("loadmsg()", 4000);
} else {
layer.alert(data.msg, {icon: 0});
//alert();
clearTimeout(t);
}
},
//Ajax请求超时,继续查询
error: function (XMLHttpRequest, textStatus, errorThrown) {
if (textStatus == "timeout") {
setTimeout("loadmsg()", 1000);
} else { //异常
setTimeout("loadmsg()", 4000);
}
}
});
}
window.onload = loadmsg();
</script>
</body>
</html>
客户付款完成之后,我会接收到完成的消息,完成之后修改订单数据,更新商户的余额、生成客户的资金明细。为了方便后期维护,我把所有的异步通知地址写在一个地方
public function getshop()
{
$str = input('get.order');
if (empty($str)) {
return json(['code' => 1011, 'msg' => 'order不存在']);
}
$str = $this->decode($str);
parse_str($str, $data); //第一个参数为字符串,第二个参数为结果
//halt($data);
if (empty($data['pid']) || empty($data['key']) || empty($data['type'])|| empty($data['out_trade_no']) || empty($data['notify_url']) || empty($data['return_url']) || empty($data['subject']) || empty($data['money'])) {
return json(['code' => 1012, 'msg' => '参数不完整']);
}
$res = db('order')->where('out_trade_no',$data['out_trade_no'])->find();
if ($res) {
if ($res['trade_status'] == 1) {
$post_data = array();
$post_data['trade_no'] = $res['trade_no'];//交易订单号
$post_data['out_trade_no'] = $res['trade_no'];//本地订单号
$post_data['total_amount'] = $res['out_trade_no'];//实际付款金额
$post_data['gmt_payment'] = $res['gmt_payment'];//付款时间
$post_data['trade_status'] = 'TRADE_SUCCESS';//交易状态
$return_url =urldecode($res['return_url'].'?'.http_build_query($post_data));
return json(['code' => 1010, 'msg' => '订单已支付','return_url'=>$return_url]);
}else{
$enddate = date('Y-m-d H:i:s',time());
$startdate = $res['gmt_create'];
$minute=floor((strtotime($enddate)-strtotime($startdate))/60);
//var_dump($minute,$enddate,$startdate,(strtotime($enddate)-strtotime($startdate)));
if ($minute>15) {
return json(['code' => 1015, 'msg' => '订单15分钟未支付,付款完成后请手动刷新页面']);
}else{
return json(['code' => 1014, 'msg' => '订单尚未支付']);
}
}
}else{
return json(['code' => 1013, 'msg' => '订单号不存在']);
}
}
2020年1月31日15点15分 发现漏代码了,补充异步通知部分,已增加微信异步通知
public function notify_url()
{
if(!empty(input('post.'))){
//支付宝的异步通知
$trade_no = input('post.trade_no');//支付交易号
$out_trade_no = input('post.out_trade_no');//商户订单号
$trade_status = input('post.trade_status');//交易状态
$gmt_payment = input('post.gmt_payment');//付款时间
$total_amount = input('post.total_amount');//订单金额
}else{
//QQ的异步通知
$xmldata=file_get_contents("php://input");
libxml_disable_entity_loader(true);
$values = json_decode(json_encode(simplexml_load_string($xmldata, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
if(!empty($values['trade_state'])){
if($values['trade_state'] =='SUCCESS'){
$trade_status = 'TRADE_SUCCESS';
$trade_no = $values['transaction_id'];//支付单号
$gmt_payment = date('Y-m-d H:i:s', strtotime($values['time_end']));//date('Y-m-d H:i:s', strtotime($date));
$out_trade_no = $values['out_trade_no'];;//本地订单号
$total_amount = $values['total_fee']/100;//订单金额
}
}
if(!empty($values['return_code'])){
//微信通知
if($values['return_code'] =='SUCCESS'){
$trade_status = 'TRADE_SUCCESS';
$trade_no = $values['transaction_id'];//支付单号
$gmt_payment = date('Y-m-d H:i:s', strtotime($values['time_end']));//date('Y-m-d H:i:s', strtotime($date));
$out_trade_no = $values['out_trade_no'];;//本地订单号
$total_amount = $values['total_fee']/100;//订单金额
}
}
}
if($trade_status == 'TRADE_SUCCESS'){
$sql = array();
$sql['trade_status']=1;
$sql['gmt_payment']=$gmt_payment;//付款时间
$sql['total_amount']=$total_amount;
$sql['trade_no']=$trade_no;
$data = db('order')->where(['out_trade_no'=>$out_trade_no,'trade_status'=>0])->update($sql);
if($data){
$post_data = array();
$post_data['trade_no'] = $trade_no;//交易订单号
$post_data['out_trade_no'] = $out_trade_no;//本地订单号
$post_data['total_amount'] = $total_amount;//实际付款金额
$post_data['gmt_payment'] = $gmt_payment;//付款时间
$post_data['trade_status'] = 'TRADE_SUCCESS';//交易状态
$order_res = db('order')->where('out_trade_no',$out_trade_no)->find();
if ($order_res) {
//更新用户余额
db('user')->where('pid', $order_res['pid'])->setInc('money', $total_amount);
//保存资金明细记录
$logAdd = array();
$logAdd['pid'] = $order_res['pid'];
$logAdd['amount'] = $total_amount;
$logAdd['type'] = 2;//1是提现,2是交易成功
$logAdd['order_id'] = $out_trade_no;
db('money_log')->insert($logAdd);
//把支付成功的信息通知给商户
$this->http_post($order_res['notify_url'], $post_data);
}
}
return true;
}
//return $fail('通信失败,请稍后再通知我');
}
资金明细sql
CREATE TABLE `xz_money_log`(
`pid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`amount` decimal(8, 2) NOT NULL,
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`type` int(1) NOT NULL,
`order_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`msg` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
用户信息sql
CREATE TABLE `xz_user`(
`pid` int(4) NOT NULL AUTO_INCREMENT,
`key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`money` decimal(7, 2) NULL DEFAULT NULL,
PRIMARY KEY (`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
项目的基础功能已完成,把微信和支付宝功能写上去就完事!
有什么建议和意见的,还请大佬能指点一二。
{:1_893:}:lol
雪莱鸟 发表于 2020-1-31 10:26
让我突然想起来个人免签收款,不过看起来你这个还是需要营业执照的 ?
免签需要实时监控,而且只能扫码。要么自己写监控软件,技术要求高。要么用别人提供的,稳定性不能保证。官方接口方便很多且稳定,你可以看我的代码,识别到是QQ环境下,直接发起支付。微信部分也可以实现。营业执照可以申请个体户,不要钱,不用交税。 大佬,如何对接三方接口 大佬,如何对接三方接口
ydydq 发表于 2020-1-30 19:45
大佬,如何对接三方接口
官方申请QQ钱包、微信支付、支付宝支付,按照开发文档和提供的sdk对接就好了呀! 夢不醒的依戀 发表于 2020-1-30 21:31
大佬,如何对接三方接口
官方申请QQ钱包、微信支付、支付宝支付,都有开发文档和sdk的呀! 厉害了学习学习 让我突然想起来个人免签收款,不过看起来你这个还是需要营业执照的 ? {:1_918:}
大佬厉害了
想和大佬学学习,帮你补充点代码 可以可以,类似易支付
页:
[1]
2