PHP通过【支付FM】实现个人免签支付
目录
3.支付成功后对订单进行检查(order_check.php)
前言:
1.支付FM使用
支付FM使用有一定成本,需要先购买额度,额度比为1:200,充值1块钱能够获得200块的收款额度。但相对来说已经算是比较便宜的了。
2.本文内容提醒
小周只是兴趣爱好编程建站,代码可能很乱而且很不规范,如果你也是个人爱好者,也正在想实现支付功能,可以看看,有错误欢迎留言指点,小周感激不尽。本文只是初步实现支付的整个过程,仅供参考。代码写的很丑(自我觉得丑炸天,刚学习PHP没多久,还望见谅)
3.所用到的相关资源
服务器:亿速云(4核4G2M云服务器只要39元/月,还有高速回国宽带,很适合)
MySQL数据库:阿里云数据库
服务器环境:PHP
其他设备:旧手机(用于监听支付结果)
一、配置支付FM
1.支付FM后台配置
首页:支付FM - 聚合支付接口,让支付接口更简单 (zhifux.com)
注册实名不再赘述,这里直接讲怎么配置,前提需要准备以下东西:微信收款码(建议使用赞赏码,不知道怎么获取赞赏码建议找度娘,其次,建议使用闲置的微信账号,这样不影响使用。)
①先充值5块钱【小周穷,只能充5块】
到这里,就可以有1000块的收款额度了。
②配置支付页面
③配置手机
使用旧手机下载指定的APP-->支付FM
注意:手机编号要和后台设置的一致,不然无法获取交易结果信息。
手机一定要保持微信和监听软件的后台,具体怎么设置,不知道的可以找度娘。
到这里第一阶段就完成了,下面开始看文档,撸代码。
二、对接支付FM
1.创建订单(index.php)
官方文档:创建订单
接口地址在你的用户信息里面,用户信息里的东西有很多都会用到,所以注意看一用户信息,如果小周某一个没有说在哪里获取,那么大概率就在用户信息里面了。
①获取创建订单的端口【用户信息当中】
官方给到的传递方法是通过POST传递,小周这里通过PHP发送POST请求
注意:当中有些变量可以不用管
//获取订单必要数据
$order = ""; //该订单在自己平台的单号
$needpay = 1; //需要支付的金额
$attch = "back"; //返回给info_back.php的自定义信息,传入什么会原封不动的返回给对应异步回调路径。
$payname = "测试支付"; //商品名
$descri = "用于测试支付"; //商品描述
$info_back = "https://你的网址/payback.php"; //异步回调路径,用于通知支付结果
$show_page = "https://你的网址/order_check.php"; //支付成功后的跳转路径,就是用户完成支付后,跳到哪一个链接或页面
//配置账号信息
$merchantNum = "你的商户号";
$Accesskey = "你的接入密钥";
$payType = 'wechat'; //支付方式为wechat
$payee = "sj1"; //对应的手机编号
//post用法
function send_post_order($url, $post_data) {
$postdata = http_build_query($post_data);
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' => $postdata,
'timeout' => 15 * 60 // 超时时间(单位:s)
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
return $result;
}
//按照官方给的顺序生成原始sign值
$si = $merchantNum.$oder.$needpay.$info_back.$Accesskey;
//md5加密成32位
$sign = md5($si);
//使用方法
$post_data = array( //是否必须 类型 说明
'merchantNum' => "$merchantNum", //是 string query商户号。在支付FM商户后台【用户中心】处可查看,该值不是用户名。
'orderNo' => "$oder", //是 string query商户订单号。仅允许字母或纯数字,建议不超过32字符,不能有中文
'amount' => $needpay, //是 number query订单金额。请求的支付金额(单位:元),最多小数点后保留2位
'returnType' => 'json', //否 string query接口内容返回类型。默认json,可选page【详见开发文档https://docs.zhifux.com/read/zhifufm/startorder#returnType】
'notifyUrl' => "$info_back", //是 string query支付结果通知网址又称异步回调网址。200字符以内,http(s)开头的网址。商户业务系统用来接收支付结果通知数据的回调地址,通知url必须为外网可直接访问的网址,且不能携带参数,收到通知后请根据规范返回成功标志,请根据通知参数规范使用Postmam等工具功能测试后传入。示例值:https://www.xxxx.com/payfm/payCalback.php
'returnUrl' => "$show_page", //否 string query成功后展示网址。200字符以内,http(s)开头的公网地址。请勿用作支付成功验证,更不要和notifyUrl值传入一样。顾客支付成功后会查看到的网址,系统自动跳转打开,常见为业务系统的支付成功提示、订单中心、会员中心网址等,又称同步跳转地址。
'payType' => "$payType", //是 string query支付方式。请根据所需对接的支付方式正确传值,需要在商户后台配置相应的收款号,参数请查看下文的支付方式payType
'payee' => "$payee", //否 string query指定收款号。该值为在本平台设置的 免签类型的手机编号/签约类型的微信系的账号标识/签约类型的支付宝系的支付宝账号 的值。
'attch' => "$attch", //否 string query附加信息,回调时候原样回传。空内容会被忽略不再回传。
'subject' => "$payname", //否 string query商品标题。100字符以内,签约类型会原样传到支付平台
'body' => "$descri", //否 string query商品描述。200字符以内,签约类型会原样传到支付平台
'payDuration' => 10, //否 integer query订单支付有效期,单位:分钟;默认值5,最大值15。例:payDuration=5表示订单支付有效期5分钟
'sign' => "$sign" //是 string query签名。待签名字符串进行MD5加密得出的32位签名值,小写。待签名字符串=商户号+商户订单号+支付金额+异步通知地址+接入密钥;其中“+”表示字符串拼接,请注意拼接顺序。接入密钥在支付FM商户后台【用户中心】处可查看。
);
//建议②做完后在执行下面的语句
$re = send_post_order('这里修改为你的接口地址【在用户信息中获取】', $post_data);
②将对应的订单信息写入到自己的数据库
在自己的数据库中,小周创建了一个表,用来存放订单信息,它长这样
function insert_order($merchantNum,$orderNo,$amount,$payType,$payee,$attch,$subject,$body,$create_sign,$create_time_ch): int{
$con = mysqli_connect('你的MySQL数据库地址','用户名','密码','对应数据库');
$sql = "INSERT INTO order_info(merchantNum,orderNo,state,amount,createTime,payType,payee,attch,subject,body,create_sign) VALUES ('$merchantNum','$orderNo',0,'$amount','$create_time_ch','$payType','$payee','$attch','$subject','$body','$create_sign')";
$state = mysqli_query($con,$sql);
return $state;
}
//我的数据库中表名叫做order_info
//写入订单数据到数据库
$in = insert_order($merchantNum,$oder,$needpay,$payType,$payee,$attch,$payname,$descri,$sign,$time_ch);
//写入数据库的操作建议在发送请求获取$re之前做
//个人觉得做一下判断看数据有没有写入再发送POST请求最好,避免因为数据库没有数据,跳转回来后检查订单的时候找不到订单
③获取返回的信息
不出意外运行结果通过打印$re可以看见是返回的是json,类似于这样
{"success":true,"msg":"success","code":200,"timestamp":1692426804831,"data":{"id":"xxxxxxxx","payUrl":"xxxxxxxxx"}}
返回的$re中的数据中data当中有个payUrl便是用户支付页面,不过在此之前可以判断一下创建订单是否成功。
$phparr = json_decode($re,ture); //将json转换成PHP数组
//提取对应的信息存入变量
$state = (int)$phparr['success']; //获取是否支付成功
$code = (int)$phparr['code']; //获取返回的code
$timestamp = (int)$phparr['timestamp']; //获取支付时间
$payid = $phparr['data']['id']; //获取订单号-此单号由支付FM生成,不同于自己平台的单号
$payurl = $phparr['data']['payUrl']; //获取支付链接
$msg = $phparr['msg'];
if($state){
//此处建议先完成④
header("refresh:0,url=$payurl"); //跳转到对应支付链接
}else{
echo "<script>;alert('【订单生成失败】支付接口返回错误信息[$msg],建议及时与小周联系。');history.go(-1);</script>";
}
④【建议操作】将返回的支付链接也写进数据库。
有可能用户在没有完成支付的时候就关闭了支付页面,此时订单已经存在于数据库,用户可以查询到订单记录,此时想支付却没有链接,又要重新创建订单,这里做一下将支付链接也写入数据库的操作,方便订单有效期内用户可以在此打开链接支付。
function update_payUrl($url,$order_id): int{
$con = mysqli_connect('你的MySQL数据库地址','用户名','密码','数据库');
$up = "UPDATE order_info SET payUrl = '$url' WHERE orderNo = '$order_id'";
return mysqli_query($con,$up);
}
//小周的数据表名为order_info
$in_url = update_payUrl($payurl,$oder);
//写入成功后在进行跳转到用户的支付链接
⑤跳转目标支付页面
不出意外就能正常打开支付页面了。
用户就可以扫码输入对应金额支付了。
2.获取订单支付通知 (payback.php)
在完成支付后,支付FM会给异步回调地址,也就是上方的$info_back对应的路径发送GET请求,官方文档在这里-->支付通知
官方文档也详细说了会传递哪些值到对应的链接,如图所示
这里就不多说了,就直接给代码
//通过过get获取传递回来的数据
$merchantNum = $_GET['merchantNum']; //商户号。用户中心查看
$orderNo = $_GET['orderNo']; //商户订单号。原样传回
$amount = $_GET['amount']; //订单金额。请求的支付金额(单位:元),最多小数点后保留2位
$platformOrderNo = $_GET['platformOrderNo']; //平台订单号。平台生成的唯一订单号
$actualPayAmount = $_GET['actualPayAmount']; //实际支付金额。最多保留小数点后2位。免签类型因浮动原因,此金额可能会不等于订单金额
$state = (int)$_GET['state']; //付款成功标志。1:付款成功
$payTime = $_GET['payTime']; //付款时间。日期时间格式:yyyy-MM-dd HH:mm:ss
$attch = $_GET['attch']; //附加信息。原样传回
$sign = $_GET['sign']; //签名。通过MD5加密指定的值拼接计算得出的签名值。签名值=md5(付款成功状态state的值+商户号merchantNum的值+商户订单号orderNo的值+订单金额amount的值+接入密钥);其中“+”表示字符串拼接。
//生成正确的sign
$si = $state.$merchantNum.$orderNo.$amount."你的接入密钥";
$need_sign = md5($si);
//里面多次使用update是因为小周的数据库存在一点问题,另外一个数据库可以一次性更新一条数据中的多个值,但是这个数据库只能一个值一个值地更新。
function update_order($orderNo,$platformOrderNo,$actualPayAmount,$payTime,$back_sign): int{
$con = mysqli_connect('你的MySQL数据库地址','用户名','密码','数据库');
$up1 = "UPDATE order_info SET platformOrderNo = '$platformOrderNo' WHERE orderNo = '$orderNo'";
$s1= mysqli_query($con,$up1);
$up2 = "UPDATE order_info SET actualPayAmount = '$actualPayAmount' WHERE orderNo = '$orderNo'";
$s2= mysqli_query($con,$up2);
$up3 = "UPDATE order_info SET payTime = '$payTime' WHERE orderNo = '$orderNo'";
$s3= mysqli_query($con,$up3);
$up4 = "UPDATE order_info SET back_sign = '$back_sign' WHERE orderNo = '$orderNo'";
$s4 = mysqli_query($con,$up4);
$up5 = "UPDATE order_info SET state = 1 WHERE orderNo = '$orderNo'";
$s5 = mysqli_query($con,$up5);
if($s1 && $s2 && $s3 && $s4 && $s5){
return 1;
}else{
return 2;
}
}
//鉴权,初步检查请求是否是由支付服务商请求
if($_GET['merchantNum'] == "你的商户号" && $sign == $need_sign) {
//鉴权通过后的业务代码编写
if($state){
//说明支付成功,更新自己数据库中的对应订单数据
$up = update_order($orderNo,$platformOrderNo,$actualPayAmount,$payTime,$sign);
if($up){
//返回值给支付FM通知它已经收到订单成功的信息了
echo 'success'; exit;
}else{
echo '数据库通信失败'; exit;
}
}else{
echo '支付失败'; exit;
}
}else{
echo '当前商户非小周的website'; exit;
}
3.支付成功后对订单进行检查(order_check.php)
用户完成支付后,会跳转到此链接,也会携带相应数据,这里通过携带的本网站的订单号以及支付FM的订单号识别相对应的订单,并检查订单状态。
由于这里的比较繁琐,html、css、js和PHP夹杂在一起很乱,我就说一下自己的想法,就是通过两个订单号去数据库中找对应的订单,然后再获取订单的状态,我自己写的是生成订单那时的订单状态是0,支付成功后会变成1(文中也是这样),然后输出对应的信息。
下面是基本实现的代码,还存在问题,就是有个按钮,还有问题,还没来得及调整。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" type="image/png" href="https://www.forhwx.cn/picture/webtop/top1.png" />
<script src="https://hwx.icu/get_js.php?v=3.4.1&need=min&webid=A000001&type=ojs"></script>
<title>订单验证页</title>
<style>
.info_success{
width: auto;
text-align: center;
margin: 0 auto;
color: green;
}
.info_error{
width: auto;
text-align: center;
margin: 0 auto;
color: red;
}
.textDiv {
width: 260px;
height: 125px;
border-radius: 10px;
background-color: white;
border: 2px solid gainsboro;
/*margin-top: 10px;*/
margin: 0 auto;
}
.head,
.context,
.price {
width: 250px;
height: 24px;
margin-left: 15px;
font-size: 12px;
}
.stat {
height: 0;
margin: 18px auto;
border: 18px solid transparent;
transform: rotate(45deg) translateY(-160px) translateX(8px);
font-size: 12px;
width: 60px;
text-align: center;
color: white;
}
.blue {
border-bottom-color: deepskyblue;
}
.green {
border-bottom-color: lawngreen;
}
.red {
border-bottom-color: red;
}
.head {
margin-top: 10px;
}
@font-face {
font-family: 'firefly';
src: url(ZCOOLKuaiLe-Regular.ttf);
}
* {
padding: 0;
margin: 0;
}
body {
height: 100vh;
background: url(yh.bmp) no-repeat;
background-size: cover;
}
ul {
list-style: none;
}
button {
outline: none;
border: none;
}
.firefly {
width: 180px;
height: 60px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(to right, #6EB46E 10%, #55B455);
border-radius: 40px;
opacity: .88;
cursor: pointer;
transition: 1s;
}
.firefly:hover {
box-shadow: 0 0 10px #B4FFB4;
}
.firefly p {
line-height: 60px;
font-size: 22px;
color: #F5DD8F;
font-family: firefly;
opacity: .88;
}
.lightning {
width: 95%;
height: 80%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 40px;
transition: .8s;
overflow: hidden;
}
.firefly:hover .lightning {
box-shadow: 0 0 4px #B4FFB4 inset;
}
.lightning ul {
opacity: 0;
transition: .8s;
}
.firefly:hover ul {
opacity: .8;
}
.lightning ul li {
width: 5px;
height: 5px;
background-color: #91FA91;
position: absolute;
bottom: 10%;
border-radius: 50%;
opacity: .6;
animation: fireflymove infinite linear;
}
@keyframes fireflymove {
100% {
bottom: 100%;
}
}
</style>
</head>
<?php
$orderNo = $_GET['orderNo'];
$platformOrderNo = $_GET['platformOrderNo'];
$con = mysqli_connect('你的MySQL数据库地址','用户名','密码','数据库');
$sql = "SELECT * FROM order_info WHERE orderNo = '$orderNo' AND platformOrderNo = '$platformOrderNo'";
//不建议用SELECT * FROM
$num = mysqli_num_rows(mysqli_query($con,$sql));
$sele = mysqli_fetch_array(mysqli_query($con,$sql));
if($num){
$createTime = $sele['createTime'];
$state = $sele['state'];
$needpay = $sele['amount'];
if($state){
$other_info = "回到主页";
$info1 = "支付成功";
$info2 = "已支付";
$payTime = $sele['payTime'];
$turepay = $sele['actualPayAmount'];
$co = "green";
$other = "https://www.forhwx.cn";
$picture_url = "https://source.forhwx.cn/link?userid=10000000&pictureid=1A64e0b6c3226b7&permissionkey=fhrG5ACmGKPRVWmHIcLhtubCEOs9TFm2&userpath=upuser";
}else{
$other_info = "立即支付";
$info1 = "等待支付......";
$info2 = "未支付";
$payTime = "****-**-**";
$turepay = "***";
$co = "blue";
$payurl = $sele['payUrl'];
$picture_url = "https://source.forhwx.cn/link?userid=10000000&pictureid=1A64e0d5b78e282&permissionkey=jDUvxA8os9PT7mecij6UlxAm6KNPTVW0&userpath=upuser";
$other = $payurl;
}
}else{
$other_info = "回到主页";
$info1 = "订单不存在!";
$info2 = "订单不存在";
$co = "blue";
$turepay = "***";
$payTime = "****-**-**";
$createTime = "****-**-**";
$needpay = "***";
$other = "https://www.forhwx.cn";
$picture_url = "https://source.forhwx.cn/link?userid=10000000&pictureid=1A64e0b6adbfd7a&permissionkey=I9iybmEKM9Z13macLf9UlmEKNOPWX13L&userpath=upuser";
}
?>
<body>
<div class="info_success">
<br>
<br>
<br>
<h3><?php echo $info1;?></h3>
<br>
<br>
<img src="<?php echo $picture_url;?>">
<div class="textDiv">
<div class="head">
<b>订单编号:</b>
<?php echo $orderNo?>
</div>
<div class="context">
<b>订单创建时间:</b>
<?php echo $createTime?>
</div>
<div class="context">
<b>订单支付时间:</b>
<?php echo $payTime?>
</div>
<div class="price">
<b>需要支付:</b>
<b style="color: red;"><?php echo $needpay;?>RMB</b>
</div>
<div class="price">
<b>实际支付:</b>
<b style="color: red;"><?php echo $turepay;?>RMB</b>
</div>
<!--<div class="stat <?php echo $co;?>">
<?php echo $info2;?>
</div>-->
</div>
<button class="firefly" onclick="window.location.href='<?php echo $other?>'>"
<p><?php echo $other_info?></p>
<div class="lightning">
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</button>
</div>
<script>
var lgh = $('.lightning li').length;
console.log(lgh)
$('.lightning li').each(function(i) {
$(this).css({
left: i * (100/lgh) + '%',
bottom: randomNum(-20, 10) + '%',
animationDuration: randomNum(1, 5) + 's'
});
});
function randomNum(max, min) {
var num = Math.floor(Math.random() * (max-min+1) + min);
return num;
}
</script>
</body>
</html>
三、代码汇总
代码会在整理好之后发送到-->这里
如果找不到目录就是还没有整理好。
四、结尾
这个仅供参考,很多措施都没有做,只是初步实现了支付的效果,因为还没学习PHP多久,所以文章代码可能很丑,请见谅,有部分资源来源于网络,例如最后的按钮,还没有调整好。