V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
homeagain
V2EX  ›  分享创造

whmcs 免签约插件开发

  •  
  •   homeagain · 2019-04-20 20:45:53 +08:00 · 3087 次点击
    这是一个创建于 2076 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们引入的是免签约插件——码支付,别怪我这么推荐它,主要是码支付真的太厚道了,别人付款直接到你的账户里,无需提现没有手续费,方便快捷,缺点就是需要一台挂软件的 win 电脑或服务器,每日有几百元的限额。

    码支付这类的支付在 whmcs 里叫做第三方网关,首先定位到该系统根目录下 modules>gateways,在 gateways 目录里存放着很多个支付文件,我们需要在该目录下新建一个文件:codepay_alipay,意为码支付的支付宝支付方式,下面开始正式 whmcs 支付开发之旅。如需帮助可以留言 mebi.me

    防止直接访问

    因为支付文件的重要性,我们不允许用户能够直接访问该文件,所以我们要阻止所有通过不正当的途径访问该文件的方式。下面代码即为不是 whmcs 系统访问的全都显示错误信息并终止程序往下进行。

    if (!defined("WHMCS")) {
        die("This file cannot be accessed directly");
    }

    保存后你在浏览器访问本文件,将直接显示:This file cannot be accessed directly

    配置插件信息

    我们需要注明这个插件的版本号,作者名等信息。函数 gatewaymodule_MetaData 为 whmcs 系统内部定义的函数,我们可以直接拿来使用,gatewaymodule 修改为文件名,例如我的文件为 codepay_alipay.php ,这个函数就叫做 codepay_alipay_MtaData,必须同名,不然 whmcs 无法识别。

    function codepay_alipay_MetaData()
    {
        return array(
            'DisplayName' => 'codepay for whmcs',
            'APIVersion' => '1.0', //版本信息
            'DisableLocalCredtCardInput' => true,
            'TokenisedStorage' => false,
        );
    }

    配置插件设置页

    接下来我们配置激活插件后,插件的设置页面,这里用户可以修改插件的配置信息,主要用来定义支付时显示的文字、码支付的 id 和密钥

    function codepay_alipay_config()
    {
        return array(
            //支付时显示的文字
            'FriendlyName' => array(
                'Type' => 'System',
                'Value' => '码支付—支付宝',
            ),
            // 码支付 id
            'codepayId' => array(
                'FriendlyName' => '码支付 id',
                'Type' => 'text',
                'Size' => '25',
                'Default' => '',
                'Description' =>'输入你的码支付 id',
             ),
            // 码支付 id
            'codepayKey' => array(
                'FriendlyName' => '码支付密钥',
                'Type' => 'text',
                'Size' => '25',
                'Default' => '',
                'Description' =>'输入你的码支付密钥',
             ),
        );
    }

    设置支付网关

    支付插件最为核心的部分,我们需要依靠这个函数来处理用户的付款,这个函数不对是无法付款成功的。

    function codepay_alipay_link($params)
    {
      $parameter = array(
        "id" => $params['codepayId'],
        "type" => 1,                      //1 为支付宝,2 为 qq 钱包
        "pay_id" => $params['invoiceid'],  //账单号
        "price" => $params['amount'],      //这笔订单的付款
        "outTime" => 300,
        "page" => 1,
        "return_url" => "",
        "notify_url" => "",
      );
     //处理付款
     $back = create_link($parameter, $params['codepayKey']); 
     return '<form method="post" action="'.$back.'">
     <p>本页账单已生成,请尽快支付。</p>
     	<input type="submit" value="' . $params['langpaynow'] . '" />
        </form>';
    }

    这里我们把付款放到 create_link 函数里,就是让文件结构更清晰

    //创建支付链接
    function create_link($canshu, $codepay_key, $host="")
    {
        ksort($canshu); //重新排序$data 数组
        reset($canshu); //内部指针指向数组中的第一个元素
        $sign = '';
        $urls = '';
        foreach ($canshu AS $key => $val) {
            if ($val == '') continue;
            if ($key != 'sign') {
                if ($sign != '') {
                    $sign .= "&";
                    $urls .= "&";
                }
                $sign .= "$key=$val"; //拼接为 url 参数形式
                $urls .= "$key=" . urlencode($val); //拼接为 url 参数形式
            }
        }
      
        $key = md5($sign . $codepay_key);//开始加密
        $query = $urls . '&sign=' . $key; //创建订单所需的参数
        $apiHost = ($host ? $host : "http://api2.fateqq.com:52888/creat_order/?"); //网关
        $url = $apiHost . $query; //生成的地址
        return $url;
    }

    官方还提供 gatewaymodule_refund 函数用于处理退款,但是谁希望用户买东西之后退款?所以不写,反正码支付 API 也不支持退款,需要退款可以手动操作,毕竟退款在少数。

    异步通知

    支付完成后,码支付网关会确认用户的付款信息并返回,我们需要获取码支付返回的信息,以此来判断用户是否付款成功并让 whmcs 做出处理。这一步也比较重要,不要让你用户付款后 whmcs 什么也不做,小心被认为骗钱把你举报了。

    引入通知函数

    require_once __DIR__ . '/../../../init.php';
    require_once __DIR__ . '/../../../includes/gatewayfunctions.php';
    require_once __DIR__ . '/../../../includes/invoicefunctions.php';

    获取 codepay_alipay 的配置信息,比如码支付 id 和密钥

    $gatewayParams = getGatewayVariables("codepay_alipay");

    如果插件未激活,直接报错并退出。当然这段代码可以放在开头。

    if (!$gatewayParams['type']) {
        die("Module Not Activated");
    }

    处理码支付返回的数据以确认是否付款成功。码支付使用 post 方式返回数据。

    function handleData($data)
    { 
        $pay_id = $data['pay_id']; //需要充值的 ID 或订单号 或用户名
        $money = (float)$data['money']; //实际付款金额
        $price = (float)$data['price']; //订单的原价
        $type = (int)$data['type']; //支付方式
        $pay_no = $data['pay_no']; //支付流水号
        $param = $data['param']; //自定义参数 原封返回 您创建订单提交的自定义参数
        $pay_time = (int)$data['pay_time']; //付款时间戳
        $pay_tag = $data['tag']; //支付备注 仅支付宝才有 其他支付方式全为 0 或空
        $status = 2; //业务处理状态 这里就全设置为 2  如有必要区分是否业务同时处理了可以处理完再更新该字段为其他值
        $creat_time = time(); //创建数据的时间戳
    
    //判断支付金额、支付 id 和支付的时间,务必判断支付 id 存在,没有即为付款不成功!
        if ($money <= 0 || empty($pay_id) || $pay_time <= 0 || empty($pay_no)) {
          return "don't pay yet";
        }else{
          return "paid";
        }
    }

    上面的是函数只是一个处理数据的模板,下面才是对实际传来的数据做出处理,并将处理后的数据套用这个模板就可以了。

    $codepay_key = $gatewayParams["codepayKey"]; //这是您的密钥
    $isPost = true; //默认为 POST 传入
    if (empty($_POST)) { //如果 GET 访问
        $_POST = $_GET;  //POST 访问 为服务器或软件异步通知  不需要返回 HTML
        $isPost = false; //标记为 GET 访问  需要返回 HTML 给用户
    }
    ksort($_POST); //排序 post 参数
    reset($_POST); //内部指针指向数组中的第一个元素
    $sign = ''; //加密字符串初始化
    foreach ($_POST AS $key => $val) {
        if ($val == '' || $key == 'sign') continue; //跳过这些不签名
        if ($sign) $sign .= '&'; //第一个字符串签名不加& 其他加&连接起来参数
        $sign .= "$key=$val"; //拼接为 url 参数形式
    }
    $invoiceId = $_POST['pay_id']; //需要充值的 ID 或订单号 或用户名
    $amount = (float)$_POST['money']; //实际付款金额
    $price = (float)$_POST['price']; //订单的原价
    $param = $_POST['param']; //自定义参数
    $type = (int)$_POST['type']; //支付方式
    $transid = $_POST['pay_no'];//流水号

    接下来套用模板,并做出判断:支付成功就确认订单生效;支付失败就不做处理。

    if (!$_POST['pay_no'] || md5($sign . $codepay_key) != $_POST['sign']) { //不合法的数据
        echo "invaild data";
    } else { //数据合法
        $result = handleData($_POST); //套用模板判断是否支付成功
        if ($result == 'paid') { //确认支付成功	 
          	$invoiceId = checkCbInvoiceID( $invoiceId, $gatewayParams['name']);
            //使订单、账单生效
            checkCbTransID($transid); 
       addInvoicePayment($invoiceId,$transid,$amount,"0","codepay_alipay");
          	echo "ok";
        } else {
            $error_msg = defined('DEBUG') && DEBUG ? $result : 'no'; //调试模式显示 否则输出 no
            if ($isPost) exit($error_msg);  //服务器访问 返回给服务器
            $result = "don't pay yet";
        }
    }

    添加一个 0.01 元的商品,并自测付款,whmcs 如果自动确认付款,码支付后台订单状态为“支付成功”即可。如有问题可以在码支付里调试,看看哪儿出错了。

    另外 whmcs 新版不自动确认服务,需要手动审核,我测试可以使用钩子函数OrderPaid修改数据表达到自动审核的目的,不知道这个是不是通用做法。

    纯干货没有图片,图文并茂不存在的。

    5 条回复    2019-04-22 14:00:14 +08:00
    qfdk
        1
    qfdk  
       2019-04-20 20:50:39 +08:00
    好神奇,我最近搞的是 PayPal 然后当面付,java 的,php 玩儿不转,但是效果还不知道咋样。https://pay.qfdk.me/ 顺便有空帮我测一下呗。
    homeagain
        2
    homeagain  
    OP
       2019-04-20 21:09:24 +08:00 via iPhone
    @qfdk php 可是全世界最好的语言,放弃 java 吧😄
    qfdk
        3
    qfdk  
       2019-04-20 21:19:12 +08:00 via iPhone
    @homeagain java 还是会战胜人类的 哈哈哈 之前也是用了这门语言 后来就真的学会了最后一课中世界最美丽的语言 merde
    xuebi1109
        4
    xuebi1109  
       2019-04-22 11:40:55 +08:00
    码支付 安全吗
    imxthd
        5
    imxthd  
       2019-04-22 14:00:14 +08:00
    @qfdk 出错啦 :(
    Internal Server Error
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2775 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 00:26 · PVG 08:26 · LAX 16:26 · JFK 19:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.