PHP通过【支付FM】实现个人免签支付

目录

前言:

         1.支付FM使用

         2.本文内容提醒

          3.所用到的相关资源

一、配置支付FM

        1.支付FM后台配置

二、对接支付FM 

        1.创建订单(index.php)

        2.获取订单支付通知 (payback.php)

        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多久,所以文章代码可能很丑,请见谅,有部分资源来源于网络,例如最后的按钮,还没有调整好。