微信自动对账功能--基于微擎系统

  • 自动对账功能
  • 项目说明
  • 项目大概逻辑
  • 代码


自动对账功能

对于有业务往来的公司之间, 公司与支付宝微信等支付平台之间, 几乎都需要对账。

项目说明

  1. 这是PHP代码(PHP是世界上最后的语讠,卒!);
  2. 公司用的是微擎系统, 所以这份代码也是基于微擎, 但是用到微擎的地方也只有数据库操作;
  3. 用于对账的微信账号是服务商模式;
  4. 对比的是一天前的订单

项目大概逻辑

  1. 从微信上获取订单, 有支付订单和退款订单
  2. 从本地数据库获取这两类订单
  3. 比对两种订单的数量和金额
  4. 将对比的差异存在数据库中

代码

<?php
/**
 * 本地订单与微信账单比对
 */

define('IN_SYS', true);
require '../framework/bootstrap.inc.php';//引入微擎的框架文件, 主要用到其中的数据库操作

//todo----------------------
$app_id = '##############';
$mch_id = '#################';
$sub_mch_id = '########';
$md5_key = "########";
$now = time();
$bill_date = date('Ymd',strtotime('-1 day'));
$bill_date = '20181226';
$acid = 6;
//todo----------------------

$parData = array(
    "appid" => $app_id,
    "mch_id" => $mch_id,
    "sub_mch_id" => $sub_mch_id,
    "nonce_str" => $now,
    "bill_date" => $bill_date,
    "bill_type" => "ALL"
);

$downLoadWechatBills = new dealWxLocalBills($acid,$bill_date,$app_id,$mch_id,$sub_mch_id,$parData,$md5_key);
$ret = $downLoadWechatBills->deal_summaries();
echo json_encode($ret);



class dealWxLocalBills
{
    /**
     * 对应本地系统的商户公众号id
     * @var null
     */
    private $_acid = null;

    /**
     * 账单日期
     * @var null
     */
    private $_bill_date = null;

    /**
     * appid
     * @var null
     */
    private $_app_id = null;

    /**
     * 商户号
     * @var null
     */
    private $_mch_id = null;

    /**
     * 特约商户号
     * @var null
     */
    private $_sub_mchid = null;

    /**
     * 账单日期的开始时间戳
     * @var false|int|null
     */
    private $_start_time = null;

    /**
     * 账单日期的结束时间戳
     * @var false|int|null
     */
    private $_end_time = null;

    /**
     * 请求参数
     * @var null
     */
    private $_parData = null;

    /**
     * 加密参数
     * @var null
     */
    private $_md5_key = null;


    /**
     * dealWxLocalBills constructor.
     * @param $acid
     * @param $bill_date
     * @param $app_id
     * @param $mch_id
     * @param $sub_mch_id
     * @param $parData
     * @param $md5_key
     */
    public function __construct($acid,$bill_date,$app_id,$mch_id,$sub_mch_id,$parData,$md5_key)
    {
        $this->_acid = $acid;
        $this->_bill_date = $bill_date;
        $this->_app_id = $app_id;
        $this->_mch_id = $mch_id;
        $this->_sub_mchid = $sub_mch_id;
        $this->_start_time = strtotime($bill_date.'000000');
        $this->_end_time = strtotime($bill_date.'235959');
        $this->_parData = $parData;
        $this->_md5_key = $md5_key;
    }


    /**
     * 签名
     * @param $param
     * @param string $signType
     * @param $md5Key
     * @return string
     */
    protected function get_sign($param, $signType = 'MD5', $md5Key)
    {
        $values = $this->para_filter($param);
        $values = $this->array_sort($values);
        $signStr = $this->create_linkstring($values);
        $signStr .= '&key=' . $md5Key;
        switch ($signType)
        {
            case 'MD5':$sign = md5($signStr);break;
            case 'HMAC-SHA256':$sign = hash_hmac('sha256', $signStr, $md5Key);break;
            default:$sign = '';
        }
        return strtoupper($sign);
    }


    /**
     * 过滤数据
     * @param $para
     * @return array
     */
    protected function para_filter($para)
    {
        $paraFilter = [];
        while (list($key, $val) = each($para))
        {
            if ($val == "") {
                continue;
            } else {
                if (! is_array($para[$key])) {
                    $para[$key] = is_bool($para[$key]) ? $para[$key] : trim($para[$key]);
                }
                $paraFilter[$key] = $para[$key];
            }
        }
        return $paraFilter;
    }


    /**
     * 字典排序
     * @param array $param
     * @return array
     */
    protected function array_sort(array $param)
    {
        ksort($param);
        reset($param);
        return $param;
    }


    /**
     * url的&模式
     * @param $para
     * @return bool|string
     */
    protected function create_linkstring($para)
    {
        reset($para);
        $arg  = "";
        while (list($key, $val) = each($para)) {
            if (is_array($val)) continue;
            $arg.=$key."=".urldecode($val)."&";
        }

        //去掉最后一个&字符
        $arg = substr($arg, 0, count($arg) - 2);

        //如果存在转义字符,那么去掉转义
        if (get_magic_quotes_gpc()) {
            $arg = stripslashes($arg);
        }
        return $arg;
    }


    /**
     * 数组转成xml
     * @param $array
     * @return string
     */
    protected function array_to_xml($array)
    {
        $xml = '<xml>';
        foreach ($array as $k => $v) {
            $xml .= "<{$k}>{$v}</$k>";
        }
        $xml .= '</xml>';
        return $xml;
    }


    /**
     * 发送请求
     * @param $xml
     * @param $url
     * @return mixed
     */
    protected function post_xml($xml, $url)
    {
        $curl = curl_init();//初始一个curl会话

        curl_setopt($curl, CURLOPT_URL,$url);//设置url
        curl_setopt($curl, CURLOPT_POST, true);//设置发送方式:post
        curl_setopt($curl, CURLOPT_POSTFIELDS, $xml);//设置发送数据
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);//不进行ssl认证
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);//TRUE 将curl_exec()获取的信息以字符串返回,而不是直接输出

        //执行cURL会话 ( 返回的数据为xml )
        $result = curl_exec($curl);
        return $result;
    }


    /**
     * 处理微信返回账单数据
     * @param $parData
     * @param $md5_key
     * @return bool
     */
    private function _wx_summary()
    {

        $wx_refund_count = 0;//退款笔数
        $wx_refund_amount = 0;//退款金额
        $wx_success_count = 0;//支付笔数
        $wx_success_amount = 0;//支付金额

        //1. 调用微信接口, 获取微信账单信息
        $sign = $this->get_sign($this->_parData, "MD5", $this->_md5_key);
        $this->_parData['sign'] = $sign;
        $xml = $this->array_to_xml($this->_parData);
        $url = "https://api.mch.weixin.qq.com/pay/downloadbill";
        $response = $this->post_xml($xml, $url);

        $response = str_replace(","," ",$response);
        $response = explode(PHP_EOL, $response);
        foreach ($response as $key=>$val)
        {
            if(strpos($val, '`') !== false)
            {
                $data = explode('`', $val);
                array_shift($data); // 删除第一个元素并下标从0开始

                if(count($data) == 27)// 处理账单数据
                {$res[] = $data;
                    //交易日期, 支付笔数, 支付金额, 退款笔数, 退款金额, 对账状态, 差异数据(订单号)
                    if(trim($data[4])=='TICKET-WXAPP' and trim($data[8])=='JSAPI')
                    {
                        if(trim($data[9]) == 'REFUND' and trim($data[15]) !='')
                        {
                            $ret['wx_refund_nos'][] = trim($data[15]);
                            $wx_refund_count++;//退款笔数
                            $wx_refund_amount = bcadd($wx_refund_amount ,trim($data[16]),2);//退款金额

                        }elseif(trim($data[9]) == 'SUCCESS')
                        {
                            $ret['wx_success_nos'][] = trim($data[6]);
                            $wx_success_count++;//支付笔数
                            $wx_success_amount = bcadd($wx_success_amount ,trim($data[12]),2);//支付金额
                        }
                    }

                }
            }
        }

        $ret['wx_refund_count'] = $wx_refund_count;
        $ret['wx_refund_amount'] = $wx_refund_amount;
        $ret['wx_success_count'] = $wx_success_count;
        $ret['wx_success_amount'] = $wx_success_amount;

        return $ret;
    }


    /**
     * 获取本地退款订单
     * @param $start_time
     * @param $end_time
     * @param $acid
     * @return array
     */
    private function _local_refund_orders($start_time,$end_time)
    {
        $orders = pdo_fetchall("SELECT
                                        refund_no,refund_total
                                    FROM
                                        zc2_ticket_refund
                                    WHERE
                                        refund_time >= {$start_time}
                                    AND refund_time <= {$end_time}
                                    AND acid = {$this->_acid}");
        return $orders;
    }


    /**
     * 获取本地支付订单
     * @param $start_time
     * @param $end_time
     * @param $acid
     * @return array
     */
    private function _local_success_orders($start_time,$end_time)
    {
        $orders = pdo_fetchall("SELECT
                                        order_no,total_fee
                                    FROM
                                        zc2_ticket_order
                                    WHERE
                                        order_time >= {$start_time}
                                    AND order_time <= {$end_time}
                                    AND acid = {$this->_acid}");
        return $orders;
    }


    /**
     * 整理本地订单
     * @return mixed
     */
    private function _local_summary()
    {
        //1. 获取本地退款订单信息
        $local_refund_orders  = $this->_local_refund_orders($this->_start_time,$this->_end_time,6);
        $local_refund_total_fees    = array_column($local_refund_orders,'refund_total');
        $ret['local_refund_amount'] = array_sum($local_refund_total_fees);
        $ret['local_refund_count']  = count($local_refund_orders);
        $ret['local_refund_order_nos']     = array_column($local_refund_orders,'refund_no');

        //2. 获取本地支付订单信息
        $local_success_orders = $this->_local_success_orders($this->_start_time,$this->_end_time,6);
        $local_success_total_fees    = array_column($local_success_orders,'total_fee');
        $ret['local_success_amount'] = array_sum($local_success_total_fees);
        $ret['local_success_count']  = count($local_success_orders);
        $ret['local_success_order_nos']     = array_column($local_success_orders,'order_no');

        return $ret;
    }


    /**
     * 对比本地订单和微信订单信息
     * @return mixed
     */
    public function deal_summaries()
    {
        $local_summary = $this->_local_summary();
        $wx_summary = $this->_wx_summary();

        $ret['pay_date'] = $this->_bill_date;//交易日期
        $ret['acid'] = $this->_acid;
        $ret['status'] = 2;
        $ret['_status'] = '对账不工整';

        $ret['local_refund_count'] = $local_summary['local_refund_count'];//本地退款笔数
        $ret['local_refund_amount'] = $local_summary['local_refund_amount'];//本地退款金额
        $ret['local_success_count'] = $local_summary['local_success_count'];//本地支付笔数
        $ret['local_success_amount'] = $local_summary['local_success_amount'];//本地支付金额

        $ret['wx_refund_count'] = $wx_summary['wx_refund_count'];//微信退款笔数
        $ret['wx_refund_amount'] = $wx_summary['wx_refund_amount'];//微信退款金额
        $ret['wx_success_count'] = $wx_summary['wx_success_count'];//微信支付笔数
        $ret['wx_success_amount'] = $wx_summary['wx_success_amount'];//微信支付金额

        if($local_summary['local_refund_count'] == $wx_summary['wx_refund_count']
            and $local_summary['local_refund_amount'] == $wx_summary['wx_refund_amount']
            and $local_summary['local_success_count'] == $wx_summary['wx_success_count']
            and $local_summary['local_success_amount'] == $wx_summary['wx_success_amount']
        )
        {
            $ret['status'] = 1;
            $ret['_status'] = '对账工整';
            $ret['local_wx_refund_nos'] = '';
            $ret['wx_local_refund_nos'] = '';
            $ret['local_wx_success_nos'] = '';
            $ret['wx_local_success_nos'] = '';

        }else{

            $local_refund_order_nos = array_values($local_summary['local_refund_order_nos']);
            $wx_refund_nos          = array_values($wx_summary['wx_refund_nos']);
            $local_wx_refund_nos = array_diff($local_refund_order_nos,$wx_refund_nos);
            $wx_local_refund_nos = array_diff($wx_refund_nos,$local_refund_order_nos);
            $ret['local_wx_refund_nos'] = json_encode(array_values($local_wx_refund_nos));//本地有但微信没有的退款订单号
            $ret['wx_local_refund_nos'] = json_encode(array_values($wx_local_refund_nos));//微信有但本地没有的退款订单号

            $local_success_order_nos = array_values($local_summary['local_success_order_nos']);
            $wx_success_nos          = array_values($wx_summary['wx_success_nos']);
            $local_wx_success_nos = array_diff($local_success_order_nos,$wx_success_nos);
            $wx_local_success_nos = array_diff($wx_success_nos,$local_success_order_nos);
            $ret['local_wx_success_nos'] = json_encode(array_values($local_wx_success_nos));//本地有但微信没有的支付订单号
            $ret['wx_local_success_nos'] = json_encode(array_values($wx_local_success_nos));//微信有但本地没有的支付订单号

        }

        pdo_insert('ticket_bills_compare',$ret);
        return $ret;

    }

}