微信自动对账功能--基于微擎系统
- 自动对账功能
- 项目说明
- 项目大概逻辑
- 代码
自动对账功能
对于有业务往来的公司之间, 公司与支付宝微信等支付平台之间, 几乎都需要对账。
项目说明
- 这是PHP代码(PHP是世界上最后的语讠,卒!);
- 公司用的是微擎系统, 所以这份代码也是基于微擎, 但是用到微擎的地方也只有数据库操作;
- 用于对账的微信账号是服务商模式;
- 对比的是一天前的订单
项目大概逻辑
- 从微信上获取订单, 有支付订单和退款订单
- 从本地数据库获取这两类订单
- 比对两种订单的数量和金额
- 将对比的差异存在数据库中
代码
<?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;
}
}