edit-api-licheb-opt

This commit is contained in:
lccsw
2021-08-10 09:51:58 +08:00
parent 4c7e18b2e9
commit 45788dda75
63 changed files with 3314 additions and 321 deletions
+1 -1
View File
@@ -301,7 +301,7 @@ class Orders extends HD_Controller{
$update['if_ins'] = $params['if_ins'] ? 1:0;
if($params['bx_imgs']){
$imgs = explode(',',$params['bx_imgs']);
$update['bx_imgs'] = json_encode($imgs,JSON_UNESCAPED_UNICODE);
$update['ins_img'] = json_encode($imgs,JSON_UNESCAPED_UNICODE);
}
if($agent){
$result = $this->order_agents_model->update($update,['id'=>$agent['id']]);
+19
View File
@@ -0,0 +1,19 @@
<?php
/**
* Created by Vim
* User: lcc
* Desc: 微信支付配置
* Date: 2021/8/12
* Time: 14:12
*/
defined('BASEPATH') OR exit('No direct script access allowed');
$config['default'] = [ //服务商信息配置
'appid' => 'wxe66f905683582780', //服应用ID
'mchid' => '1612096731', //商户号
'merchantSerialNumber' => '761590F1FF6DFC2466894F96E2DE1169CE644A74', //商户号API证书序列号
'merchantPrivateKey' => '/home/dev28/liche/api/third_party/WXconfig/apiclient_key.pem', //商户私钥路径
'wechatpayCertificate' => '/home/dev28/liche/api/third_party/WXconfig/apiclient_wechatpay.pem', //微信支付平台证书路径
'sub_appid' => 'wx98e64c11aac45966' //子商户应用ID
];
+39 -44
View File
@@ -28,17 +28,52 @@ class Payment extends Wxapp{
if($row['status']>1){
throw new Exception('订单已支付', API_CODE_FAIL);
}
if($row['status']=1 && !$row['status_detail']!=11){
if($row['status']=1 && $row['status_detail']!=11){
throw new Exception('订单已过期', API_CODE_FAIL);
}
if($row['total_price']>0){
$url = http_host_com('api');
$notify_url = $url."/wxapp/{$this->app_key}/wxnotify";
$notify_url = $url."/wxapp/{$this->app_key}/wxnotify_v3";
if($this->uid<=10){
$row['total_price'] = 0.01;
}
$result = $this->pay($sid, $row['total_price'], $this->session['openid'], $row['item_title'], $notify_url);
$this->config->load('wxpay');
$wx_config = $this->config->item('default');
$this->load->library('WechatPayV3');
$params = [
'merchantId' => $wx_config['mchid'],
'merchantSerialNumber' => $wx_config['merchantSerialNumber'],
'merchantPrivateKey' => $wx_config['merchantPrivateKey'],
'wechatpayCertificate' => $wx_config['wechatpayCertificate'],
];
$WechatPayV3 = new WechatPayV3($params);
$n_time = time();
$json = [
'sp_appid' => $wx_config['appid'],
'sp_mchid' => $wx_config['mchid'],
'sub_appid' => $wx_config['sub_appid'],
'sub_mchid' => $row['mch_id'],
'description' => $row['item_title'],
'out_trade_no' => $row['sid'],
'notify_url' => $notify_url,
'settle_info' => [
'profit_sharing' => true
],
'amount' => [
'total' => $row['total_price']*100,
],
'payer' => [
'sub_openid' => $this->session['openid']
],
];
$noncestr = getNonceStr(20);
$resq = $WechatPayV3->unifiedOrder($json,$json['sub_appid'],$noncestr);
if(!$resq['code']){
debug_log("[下单失败]:" . $resq['msg'], 'underorder.log','wxpay');
throw new Exception('微信下单失败', API_CODE_FAIL);
}
$result = $resq['data'];
}else{
$this->load->service('apporder/payment_service', array('app_id' => $this->app_id));
$result = $this->payment_service->after_pay($sid);
@@ -46,44 +81,4 @@ class Payment extends Wxapp{
return $result;
}
/**
* 支付方法
* @param $trade_no
* @param $price
* @param $openid
* @param $body
* @param $notify_url
* @param $attach
* @param $detail
* @return bool|json数据,可直接填入js函数作为参数|mixed
* @throws WxPayException
*/
private function pay($trade_no,$price,$openid,$body,$notify_url,$attach = '',$detail = ''){
if(!$body){return false;}
require_once APPPATH."third_party/WXconfig/liche_WxPay.Config.php";
require_once APPPATH."third_party/WXpay/WxPay.Api.php";
$config = new WxPayConfig();
$wxpay = new WxPayUnifiedOrder();
$wxpay->SetVersion('1.0');
$wxpay->SetBody($body); //简单描述
$attach && $wxpay->SetAttach($attach); //附加信息
$wxpay->SetNotify_url($notify_url);
$wxpay->SetOut_trade_no($trade_no); //订单号
$wxpay->SetTotal_fee($price * 100); //支付价格
$wxpay->SetTime_start(date("YmdHis")); //交易起始时间
$wxpay->SetTime_expire($out_time); //交易结束时间
$wxpay->SetTrade_type("JSAPI"); //设置交易类型
$wxpay->SetOpenid($openid); //openid
$detail && $wxpay->SetDetail($detail);
$return = WxPayApi::unifiedOrder($config, $wxpay); //统一支付
if($return['result_code'] == 'SUCCESS') {
$wxpay_api = new WxPayJsApiPay();
$jsApiParameters = WxPayApi::GetJsApiParameters($return, $config, $wxpay_api);
$jsApiParameters = json_decode($jsApiParameters, true);
return $jsApiParameters;
}else{
throw new Exception($return['return_msg']?$return['return_msg'].$return['err_code_des']:$return['return_msg'], API_CODE_FAIL);
}
}
}
+22 -5
View File
@@ -60,7 +60,7 @@ class Series extends Wxapp{
return $list;
}
//获取车系属性
public function get_attrs(){
protected function get_attrs(){
$s_id = $this->input_param('id');
$type = $this->input_param('type');
$page = $this->input_param('page');
@@ -85,10 +85,6 @@ class Series extends Wxapp{
'id' => $val['id'],
'title' => $val['title']
];
if($val['type']==1){
$temp['price'] = $jsodnata['price'];
$temp['deposit'] = $jsodnata['deposit'];
}
$lists[] = $temp;
}
}
@@ -98,4 +94,25 @@ class Series extends Wxapp{
];
return $data;
}
//获取车型信息信息
protected function get_info(){
$s_id = $this->input_param('car_id');
$v_id = $this->input_param('v_id');
$color_id = $this->input_param('color_id');
$incolor_id = $this->input_param('incolor_id');
$this->load->model('auto/auto_cars_model');
if(!$s_id || !$v_id || !$color_id || !$incolor_id){
throw new Exception('参数错误', ERR_PARAMS_ERROR);
}
$attrs = "{$color_id}_{$v_id}_{$incolor_id}";
$car = $this->auto_cars_model->get(['attrs'=>$attrs,'s_id'=>$s_id]);
$data = [
'price' => $car['price_car'] ? $car['price_car'] : 0,
'deposit' => $car['price_book'] ? $car['price_book'] : 0
];
return $data;
}
}
+24 -12
View File
@@ -23,6 +23,9 @@ class Contract extends Wxapp{
$this->load->model('receiver/order/receiver_order_ckcars_model','ckcars_model');
$this->load->model('receiver/order/receiver_order_bills_model','bills_model');
$this->load->model('receiver/order/receiver_order_deliverys_model','deliverys_model');
$this->load->model("biz/biz_model");
$this->load->model("sys/sys_company_model");
}
protected function get(){
@@ -43,9 +46,7 @@ class Contract extends Wxapp{
list($h5_url,$title) = $this->orders_entity->get_contract_h5($id,$type,1);
list($pdf2img_url,$title) = $this->orders_entity->get_contract_h5($id,$type);
if(!$contract){
$cid = create_order_no(350200,$this->app_key,2);
$add_data = [
'cid' => $cid,
'o_id' => $id,
'type' => $type,
'c_time' => time()
@@ -54,12 +55,15 @@ class Contract extends Wxapp{
if(!$con_id){
throw new Exception('创建合同失败', API_CODE_INVILD_PARAM);
}
$cid = create_contract_no(350200,$con_id,$type);
$this->contracts_model->update(['cid'=>$cid],['id'=>$con_id]);
//html转pdf
$save_path = 'data/contracts/'.date('Ymd');
$filename = $cid.'.pdf';
$c_res = $this->pdf->html2pdf($pdf2img_url,FCPATH.$save_path,$filename);
$update = [
'file' => $save_path.'/'.$filename
'file' => $save_path.'/'.$filename,
'cid' => $cid
];
$this->contracts_model->update($update,['id'=>$con_id]);
}else{
@@ -91,6 +95,7 @@ class Contract extends Wxapp{
$img = $this->input_param('img');
$row = $this->contracts_model->get(['type'=>$type,'o_id'=>$id]);
$order = $this->orders_model->get(['id'=>$id],'biz_id');
if(!$row['file'] || !$img){
throw new Exception('参数错误', API_CODE_INVILD_PARAM);
@@ -98,20 +103,28 @@ class Contract extends Wxapp{
if($row['status']==1){
throw new Exception('已签名', API_CODE_INVILD_PARAM);
}
//获取公司印章
$biz = $this->biz_model->get(['id'=>$order['biz_id']],'company_id');
$company = $this->sys_company_model->get(['id'=>$biz['company_id']],'img_seal,id');
$img_seal = $company['img_seal'] ? build_qiniu_image_url($company['img_seal']) : '';
if(!$img_seal){
throw new Exception('公司公章未上传', API_CODE_FAIL);
}
//pdf转图片
$pdf_url = http_host_com('api').'/'.$row['file'];
$this->load->library('pdf');
$pdf = new Pdf();
$pdf = new Pdf(); //正式
$imgs = $pdf->pdf2img($pdf_url);
if(!$imgs){
throw new Exception('签名失败,图片转换失败', API_CODE_INVILD_PARAM);
}
$this->load->library('receiver/sign_entity');
$sign_entity = new Sign_entity(['comp_img'=>$img_seal]);
switch($type){
case 0: //整车
$sign_img = array_pop($imgs);
$res = $this->sign_entity->merge($sign_img,$img,1050,230,300);
$res = $sign_entity->merge($sign_img,$img,1070,500,230);
if(!$res){
throw new Exception('签名失败', API_CODE_INVILD_PARAM);
}
@@ -131,7 +144,7 @@ class Contract extends Wxapp{
break;
case 1: //协议
$sign_img = array_pop($imgs);
$res = $this->sign_entity->merge($sign_img,$img,1050,1600,300);
$res = $sign_entity->merge($sign_img,$img,1050,1650,300);
if(!$res){
throw new Exception('签名失败', API_CODE_INVILD_PARAM);
}
@@ -153,7 +166,7 @@ class Contract extends Wxapp{
break;
case 2: //确认信息
$sign_img = array_pop($imgs);
$res = $this->sign_entity->merge($sign_img,$img,1050,430,300);
$res = $sign_entity->merge($sign_img,$img,1050,430,450);
if(!$res){
throw new Exception('签名失败', API_CODE_INVILD_PARAM);
}
@@ -167,10 +180,9 @@ class Contract extends Wxapp{
$result = $this->contracts_model->update($update,['id'=>$row['id']]);
if($result){
$this->ckcars_model->update(['status'=>2],['o_id'=>$id]);
$this->orders_model->update(['status'=>3],['id'=>$id]);
if(!$this->bills_model->count(['o_id'=>$id])){
$this->bills_model->add(['o_id'=>$id,'c_time'=>time()]);
}
//生成支付订单
$this->load->library('receiver/orders_entity');
$this->orders_entity->check_finish($id,$this->app_id,$this->session);
throw new Exception('签名成功', API_CODE_SUCCESS);
}else{
throw new Exception('签名失败', API_CODE_INVILD_PARAM);
@@ -178,7 +190,7 @@ class Contract extends Wxapp{
break;
case 3: //车辆交接
$sign_img = array_pop($imgs);
$res = $this->sign_entity->merge($sign_img,$img,400,1400,'','',false);
$res = $sign_entity->merge($sign_img,$img,1050,1500,350);
if(!$res){
throw new Exception('签名失败', API_CODE_INVILD_PARAM);
}
+18 -2
View File
@@ -9,13 +9,19 @@ defined('WXAPP_APP') OR exit('No direct script access allowed');
*/
require_once APPPATH . 'controllers/wxapp/Wxapp.php';
class Order extends Wxapp{
private $type_array = [
3 => '定金',
4 => '定金',
5 => '委托服务费',
6 => '首付',
];
public function __construct($inputs, $app_key){
parent::__construct($inputs, $app_key);
$this->login_white = array();//登录白名单
$this->uid = $this->session['uid'];
$this->load->model('apporder/order_purchase_model');
$this->load->model('receiver/order/receiver_orders_model');
}
/**
@@ -34,7 +40,7 @@ class Order extends Wxapp{
if($type){
$where['status'] = $type;
}
$fileds = 'id,sid,item_title,total_price,jsondata,status,status_detail';
$fileds = 'id,sid,item_title,total_price,jsondata,status,status_detail,type';
$rows = $this->order_purchase_model->select($where,'id desc',$page,$size,$fileds);
$total = $this->order_purchase_model->count($where);
$list = [];
@@ -63,10 +69,20 @@ class Order extends Wxapp{
];
}
}
$type_name = '金额';
if($this->type_array[$val['type']]){
if($val['type']==6){
$r_order = $this->receiver_orders_model->get(['id'=>$cf_id],'payway');
$type_name = $r_order['payway'] ? '尾款' : '首付';
}else{
$type_name = $this->type_array[$val['type']];
}
}
$list[] = [
'id' => $val['id'],
'sid' => $val['sid'],
'cover' => $jsondata['cover'] ? build_qiniu_image_url($jsondata['cover']) : '',
'type_name' => $type_name,
'title' => $val['item_title'],
'price' => $val['total_price'],
'color' => isset($car['color']['jsondata']['title']) ? $car['color']['jsondata']['title'] : "",
+6 -1
View File
@@ -307,7 +307,12 @@ class User extends Wxapp{
if($ckcar_row['status']){
if($val['id']==5){
$state = 2;
$progressOpt = ['title'=> '确认车辆','url'=>'/pages/mine/signContract/queRen?id='.$row['id']];
if($ckcar_row['status']==2){ //未支付
$title = $row['payway'] ? '去支付尾款' : '去支付首付';
$progressOpt = ['title'=> $title,'url'=>'/pages/order/index'];
}else{
$progressOpt = ['title'=> '确认车辆','url'=>'/pages/mine/signContract/queRen?id='.$row['id']];
}
}else{
$state = 1;
}
+11 -3
View File
@@ -7,7 +7,6 @@
*/
//微信支付回调
require_once APPPATH."third_party/WXpay/WxPay.Api.php";
require_once APPPATH."third_party/WXconfig/liche_WxPay.Config.php";
class Wxnotify extends CI_Controller{
private $log_file = 'liche_pay.log';
@@ -18,12 +17,21 @@ class Wxnotify extends CI_Controller{
parent::__construct();
$this->load->model('app/app_wxpaylog_model', 'wxpaylog_model');
$this->load->model('apporder/order_purchase_model','purchase_model');
$input = file_get_contents("php://input");
debug_log("[info] ". __FUNCTION__ . "# input:" . $input, $this->log_file);
//xml 转数组
$obj = simplexml_load_string($input,"SimpleXMLElement", LIBXML_NOCDATA);
$obj_array = json_decode(json_encode($obj),true);
$mch_id = $obj_array['mch_id'];
$config_file = APPPATH."third_party/WXconfig/liche_WxPay.Config.php";
if(!file_exists($config_file)){
debug_log("[error] ". __FUNCTION__ . ":商户配置文件不存在", $this->log_file);
exit();
}
try{
//如果返回成功则验证签名
$config = new WxPayConfig();
$wxpay = new WxPayNotifyResults();
$input = file_get_contents("php://input");
debug_log("[info] ". __FUNCTION__ . "# input:" . $input, $this->log_file);
$result = WxPayNotifyResults::Init($config, $input);
$this->notify = $result->GetValues();
}catch (WxPayException $e){
@@ -0,0 +1,77 @@
<?php
/**
* Created by PhpStorm.
* User: lcc
* Date: 2021/08/11
* Time: 20:17
*/
//微信支付回调V3
use GuzzleHttp\Exception\RequestException;
class Wxnotify_v3 extends CI_Controller{
private $app_id = 1;
private $log_file = 'liche_pay.log';
private $log_dir = "wxpay";
private $notify_data;
private $desc_url = 'http://p7.liche.cn'; //微信服务解码地址
public function __construct(){
parent::__construct();
$this->load->model('app/app_wxpaylog_model', 'wxpaylog_model');
$this->load->model('apporder/order_purchase_model','purchase_model');
$input = file_get_contents('php://input');
debug_log("[info] ". __FUNCTION__ . "# input:" . $input, $this->log_file,$this->log_dir);
$this->notify_data = json_decode($input,true);
}
public function index(){
$client = new GuzzleHttp\Client();
try {
$resp = $client->request('POST', $this->desc_url, ['form_params' => $this->notify_data]);
$result = json_decode($resp->getBody(),true);
if(!$result['code']){ //解密失败
debug_log("[error] ". __FUNCTION__ . "# 解密失败:" . $resp->getBody(), $this->log_file,$this->log_dir);
}else{
debug_log("[info] ". __FUNCTION__ . "# 解密成功:" . $resp->getBody(), $this->log_file,$this->log_dir);
$sid = $result['data']['out_trade_no'];
if($sid){
debug_log("[start] ". __FUNCTION__ . ": sid:".$sid, $this->log_file,$this->log_dir);
$order = $this->purchase_model->get(array('sid'=>$sid,'app_id'=>$this->app_id));
if(!$order){
debug_log("[error] ". __FUNCTION__ . ":{$sid}_订单不存在", $this->log_file,$this->log_dir);
}
//执行失败
$add = array(
'app_id' => $this->app_id,
'sid' => $sid?$sid:0,
'trade_no' => $result['data']['transaction_id'],
'notify_param' => json_encode($result['data'],JSON_UNESCAPED_UNICODE)
);
$ret = $this->wxpaylog_model->add($add);
if(!$ret){
debug_log("[error] ". __FUNCTION__ . ": sql:".$this->wxpaylog_model->db->last_query(), $this->log_file,$this->log_dir);
}
if($result['data']['trade_state'] != 'SUCCESS'){ //支付失败
debug_log("[error] ". __FUNCTION__ . ":支付失败,sid={$sid},app_id=", $this->log_file,$this->log_dir);
}else{ //支付成功
$this->load->service('apporder/payment_service', array('app_id' => $this->app_id));
$result = $this->payment_service->after_pay($sid,$result['data']['amount']['payer_total']/100);
if($result['code']){
debug_log("[success] ". __FUNCTION__ . ":操作成功", $this->log_file,$this->log_dir);
}else{
debug_log("[error] ". __FUNCTION__ . ":".$result['msg'], $this->log_file,$this->log_dir);
}
}
debug_log("[finish] ". __FUNCTION__ . ": sid:".$sid, $this->log_file,$this->log_dir);
}else{
debug_log("[finish] ". __FUNCTION__ . ": 参数错误:".json_encode($result,JSON_UNESCAPED_UNICODE), $this->log_file,$this->log_dir);
}
}
} catch (RequestException $e) {
debug_log("[error] ". __FUNCTION__ . "# 请求失败:" . $e->getResponse(), $this->log_file,$this->log_dir);
}
echo json_encode(['code'=>'SUCCESS','message'=>'成功'],JSON_UNESCAPED_UNICODE);
}
}
+46 -6
View File
@@ -23,6 +23,10 @@ class Cusorder extends Wxapp{
$this->load->model('receiver/order/receiver_order_signs_model','order_signs_model');
$this->load->model('receiver/order/receiver_order_bills_model','order_bills_model');
$this->load->model('receiver/order/receiver_order_deliverys_model','order_deliverys_model');
$this->load->model('receiver/receiver_services_model','services_model');
$this->load->model('receiver/receiver_service_package_model','package_model');
$this->load->model('auto/auto_series_model');
$this->load->model('auto/auto_brand_model');
$this->load->model('auto/auto_attr_model');
@@ -47,6 +51,11 @@ class Cusorder extends Wxapp{
$price = $this->input_param('price');
$deposit = $this->input_param('deposit');
$payway = $this->input_param('payway');
$pack_id = $this->input_param('pack_id');
$main_type = $this->input_param('main_type');
$ifentrust = $this->input_param('ifentrust');
$entrust_name = $this->input_param('entrust_name');
$entrust_idcard = $this->input_param('entrust_idcard');
$row = $this->customers_model->get(['id'=>$cus_id]);
$series_row = $this->auto_series_model->get(['id'=>$car_id]);
@@ -100,6 +109,16 @@ class Cusorder extends Wxapp{
'c_time' => time()
];
$payway && $data['payway'] = 1;
$pack_id && $data['pack_id'] = $pack_id;
$main_type && $data['main_type'] = 1;
if($ifentrust){
$data['ifentrust'] = 1;
$info_json = [
'entrust_name' => $entrust_name,
'entrust_idcard' => $entrust_idcard
];
$data['info_json'] = json_encode($info_json,JSON_UNESCAPED_UNICODE);
}
$o_id = $this->orders_model->add($data);
if($o_id){
$sign_data = [
@@ -117,6 +136,7 @@ class Cusorder extends Wxapp{
protected function put(){
$id = $this->input_param('id');
$payway = $this->input_param('payway');
$pack_id = $this->input_param('pack_id');
$row = $this->orders_model->get(['id'=>$id]);
if(!$row){
throw new Exception('订单不存在', ERR_PARAMS_ERROR);
@@ -125,13 +145,13 @@ class Cusorder extends Wxapp{
$result = false;
$up_data = [];
if(strlen($payway)){
if(strlen($payway)|| $pack_id){
if($row['status']>0){
throw new Exception('修改失败,已签订合同', ERR_PARAMS_ERROR);
}
$up_data['payway'] = $payway;
strlen($payway) && $up_data['payway'] = $payway;
$pack_id && $up_data['pack_id'] = $pack_id;
}
if($up_data){
$result = $this->orders_model->update($up_data,['id'=>$id]);
}
@@ -168,6 +188,11 @@ class Cusorder extends Wxapp{
$keyword = $this->input_param('keyword');
$status = $this->input_param('status');
$ismy = $this->input_param('ismy'); //是否只显示自己
$page = $this->input_param('page');
$size = $this->input_param('size');
!$page && $page = 1;
!$size && $size = 10;
$where = [
'biz_id' => $biz_id
@@ -255,14 +280,28 @@ class Cusorder extends Wxapp{
}
$loan_status = $loan['status']>0 ? 2 : 1;
}
$pack_title = '';
if($row['pack_id']){
$pack_row = $this->package_model->get(['id'=>$row['pack_id']]);
$srv_rows = '';
$pack_row['srv_ids'] && $srv_rows = $this->services_model->select(["id in ({$pack_row['srv_ids']})"=>null],'','','','title');
$srv_rows && $pack_title = implode('+',array_column($srv_rows,'title'));
}
$car_data = [
'车辆名称' => $brand['name'].$series['name'],
'车辆级别' => $version,
'颜色' => $color,
'车辆合同售价' => $row['price'],
'定金' => $row['deposit']
'定金' => $row['deposit'],
'代办包' => $pack_title,
'购车主体' => $row['main_type'] ? '公司' : '个人',
'是否委托' => $row['ifentrust'] ? '是' : '否',
];
if($row['ifentrust']){
$info_json = json_decode($row['info_json'],true);
$car_data['委托人姓名'] = $info_json['entrust_name'] ? $info_json['entrust_name'] : '';
$car_data['委托人身份证'] = $info_json['entrust_idcard'] ? $info_json['entrust_idcard'] : '';
}
//开票信息
$bill_status = 0;
$bill_data = [];
@@ -297,7 +336,8 @@ class Cusorder extends Wxapp{
'loan_data' => $loan_data,
'bill_status' => $bill_status,
'bill_data' => $bill_data,
'ckcar_status' => $ckcar_status
'ckcar_status' => $ckcar_status,
'pack_id' => $row['pack_id']
];
return $data;
}
@@ -37,11 +37,18 @@ class Customerlogs extends Wxapp{
$count = $this->customer_oplogs_model->count($where);
$lists = [];
if($count){
$rows = $this->customer_oplogs_model->select($where,'id desc',$page,$size,'log,uname,type,c_time');
$rows = $this->customer_oplogs_model->select($where,'id desc',$page,$size,'log,uname,type,c_time,imgs');
foreach($rows as $key => $val){
$record = '';
$second = 0;
$content = $val['log'];
$img_json = json_decode($val['imgs'],true);
$imgs = [];
if($img_json){
foreach($img_json as $val2){
$imgs[] = build_qiniu_image_url($val2);
}
}
if($val['type']==2){
$rec_row = $this->receiver_xz_model->get(['id'=>$val['log']],'rec_url,duration');
$content = '拨打电话';
@@ -53,6 +60,7 @@ class Customerlogs extends Wxapp{
'content' => "{$val['uname']}".$content,
'record_url' => $record,
'second' => $second,
'imgs' => $imgs,
'c_time' => date('Y.m.d',$val['c_time'])
];
}
+152 -61
View File
@@ -16,29 +16,57 @@ class Protocol extends CI_Controller{
$this->load->model('receiver/order/receiver_order_agents_model','agents_model');
$this->load->model('receiver/order/receiver_order_loans_model','loans_model');
$this->load->model('receiver/order/receiver_order_ckcars_model','ckcars_model');
$this->load->model('receiver/receiver_service_package_model','package_model');
$this->load->model('receiver/receiver_services_model','services_model');
$this->load->model('auto/auto_series_model');
$this->load->model('auto/auto_brand_model');
$this->load->model('auto/auto_cars_model');
$this->load->model("biz/biz_model");
$this->load->model('area_model');
$this->load->model("sys/sys_company_model");
$this->load->model("items/items_model");
$this->load->model('apporder/order_purchase_model','purchase_model');
}
//整车合同
public function car(){
$wxapp = $this->input->get('wxapp');
//$id = $this->input->get('id');
//$row = $this->orders_model->get(['id'=>$id]);
//$contract = $this->contracts_model->get(['o_id'=>$id,'type'=>0]);
//if($row){
// $brand = $this->auto_brand_model->get(['id'=>$row['brand_id']],'name');
// $series = $this->auto_series_model->get(['id'=>$row['s_id']],'name');
// $car_json = json_decode($row['car_json'],true);
// $color = isset($car_json['color']) ? $car_json['color']['title'] : '';
// $version = isset($car_json['version']) ? $car_json['version']['title'] : '';
// $row['brand_name'] = $brand['name'].$series['name'].' '.$version;
// $row['color'] = $color;
//}
//$row['day'] = $contract ? date('Y年m月d日',$contract['c_time']):date('Y年m月d日');
//$row['price_rmb'] = num_to_rmb($row['price']);
//$row['cid'] = $contract['cid'];
$id = $this->input->get('id');
$row = $this->orders_model->get(['id'=>$id]);
$contract = $this->contracts_model->get(['o_id'=>$id,'type'=>0]);
if($row){
$brand = $this->auto_brand_model->get(['id'=>$row['brand_id']],'name');
$series = $this->auto_series_model->get(['id'=>$row['s_id']],'name');
$car_json = json_decode($row['car_json'],true);
$row['color'] = isset($car_json['color']) ? $car_json['color']['title'] : '';
$row['incolor'] = isset($car_json['incolor']) ? $car_json['incolor']['title'] : '';
$row['version'] = isset($car_json['version']) ? $car_json['version']['title'] : '';
$row['brand_name'] = $brand['name'];
$row['series_name'] = $series['name'];
//获取门店信息
$biz = $this->biz_model->get(['id'=>$row['biz_id']]);
$city = $this->area_model->get(['county_id'=>$biz['county_id']],'city_name,county_name');
$row['address'] = $city['city_name'].$city['county_name'].'&nbsp;&nbsp;'.$biz['address'];
$company = $this->sys_company_model->get(['id'=>$biz['company_id']]);
$row['company'] = $company;
//获取选择代办包
$pack_row = $this->package_model->get(['id'=>$row['pack_id']],'srv_ids');
$srv_total = count(explode(',',$pack_row['srv_ids']));
if($srv_total==4){
$row['give_time'] = 30;
}elseif($srv_total==3){
$row['give_time'] = 45;
}else{
$row['give_time'] = 60;
}
}
$row['info_json'] = json_decode($row['info_json'],true);
$row['price_rmb'] = num_to_rmb($row['price']);
$row['cid'] = $contract['cid'];
$row['day'] = $contract ? date('Y年m月d日',$contract['c_time']):date('Y年m月d日');
$row['dep_price'] = number_format(5000,2); //定金
$folder = $wxapp ? 'protocol' : 'html2pdf';
$this->load->view("wxapp/licheb/{$folder}/car",$row);
}
@@ -46,31 +74,64 @@ class Protocol extends CI_Controller{
//代理合同
public function agent(){
$wxapp = $this->input->get('wxapp');
//$id = $this->input->get('id');
//$row = $this->orders_model->get(['id'=>$id]);
//$contract = $this->contracts_model->get(['o_id'=>$id,'type'=>1]);
//$row['day'] = $contract ? date('Y年m月d日',$contract['c_time']):date('Y年m月d日');
//$row['cid'] = $contract['cid'];
$id = $this->input->get('id');
$row = $this->orders_model->get(['id'=>$id]);
$contract = $this->contracts_model->get(['o_id'=>$id,'type'=>1]);
$total_price = 0;
if($row){
//获取门店信息
$biz = $this->biz_model->get(['id'=>$row['biz_id']]);
$company = $this->sys_company_model->get(['id'=>$biz['company_id']]);
$row['company'] = $company;
//获取服务包
$packs = $this->package_model->get(['id'=>$row['pack_id']],'srv_ids');
if($packs['srv_ids']){
$row['services'] = $services = $this->services_model->select(["id in ({$packs['srv_ids']})"],'','','','title,field_name');
}
$attrs = "{$row['cor_id']}_{$row['v_id']}_{$row['incor_id']}";
$car = $this->auto_cars_model->get(['attrs'=>$attrs,'s_id'=>$row['s_id']]);
//获取挂牌价
$this->load->model('sys/sys_city_model');
$city = $this->sys_city_model->get(['city_id'=>$biz['city_id']],'fee_carno');
$car['fee_carno'] = $city['fee_carno'];
if($services){
foreach($services as $key=>$val){
$field_arr = explode('.',$val['field_name']);
if($car[$field_arr[1]]){
$total_price += $car[$field_arr[1]];
}
}
}
}
$row['total_price'] = $total_price;
$row['info_json'] = json_decode($row['info_json'],true);
$row['day'] = $contract ? date('Y年m月d日',$contract['c_time']):date('Y年m月d日');
$row['cid'] = $contract['cid'];
$folder = $wxapp ? 'protocol' : 'html2pdf';
$this->load->view("wxapp/licheb/{$folder}/agent",$row);
}
//车辆信息确认单
public function car_ck(){
$wxapp = $this->input->get('wxapp');
//$id = $this->input->get('id');
//$row = $this->orders_model->get(['id'=>$id]);
//if($row){
// $brand = $this->auto_brand_model->get(['id'=>$row['brand_id']],'name');
// $series = $this->auto_series_model->get(['id'=>$row['s_id']],'name');
// $car_json = json_decode($row['car_json'],true);
// $version = isset($car_json['version']) ? $car_json['version']['title'] : '';
// $row['brand_name'] = $brand['name'].$series['name'].' '.$version;
//}
//$contract = $this->contracts_model->get(['o_id'=>$id,'type'=>2]);
//$items = $this->items_model->get(['id'=>$row['item_id']],'vin');
//$row['day'] = $contract ? date('Y年m月d日',$contract['c_time']):date('Y年m月d日');
//$row['cid'] = $contract['cid'];
//$row['vin'] = $items['vin'];
$id = $this->input->get('id');
$row = $this->orders_model->get(['id'=>$id]);
if($row){
$brand = $this->auto_brand_model->get(['id'=>$row['brand_id']],'name');
$series = $this->auto_series_model->get(['id'=>$row['s_id']],'name');
$car_json = json_decode($row['car_json'],true);
$version = isset($car_json['version']) ? $car_json['version']['title'] : '';
$row['brand_name'] = $brand['name'].$series['name'].' '.$version;
//获取门店信息
$biz = $this->biz_model->get(['id'=>$row['biz_id']]);
$company = $this->sys_company_model->get(['id'=>$biz['company_id']]);
$row['company'] = $company;
}
$contract = $this->contracts_model->get(['o_id'=>$id,'type'=>2]);
$items = $this->items_model->get(['id'=>$row['item_id']],'vin');
$row['info_json'] = json_decode($row['info_json'],true);
$row['day'] = $contract ? date('Y年m月d日',$contract['c_time']):date('Y年m月d日');
$row['cid'] = $contract['cid'];
$row['vin'] = $items['vin'];
$folder = $wxapp ? 'protocol' : 'html2pdf';
$this->load->view("wxapp/licheb/{$folder}/car_ck",$row);
}
@@ -78,34 +139,64 @@ class Protocol extends CI_Controller{
public function car_fh(){
$this->load->model('app/licheb/app_licheb_users_model');
$wxapp = $this->input->get('wxapp');
//$id = $this->input->get('id');
$id = $this->input->get('id');
//$row = $this->orders_model->get(['id'=>$id]);
//$agent = $this->agents_model->get(['o_id'=>$id]);
//$contract = $this->contracts_model->get(['o_id'=>$id,'type'=>3]);
//$items = $this->items_model->get(['id'=>$row['item_id']],'vin');
//if($row){
// //贷款
// if(!$row['payway']){
// $row['loan'] = $this->loans_model->get(['o_id'=>$id]);
// }
// $brand = $this->auto_brand_model->get(['id'=>$row['brand_id']],'name');
// $series = $this->auto_series_model->get(['id'=>$row['s_id']],'name');
// $car_json = json_decode($row['car_json'],true);
// $info_json = json_decode($row['info_json'],true);
// $color = isset($car_json['color']) ? $car_json['color']['title'] : '';
// $version = isset($car_json['version']) ? $car_json['version']['title'] : '';
// $row['brand_name'] = $brand['name'].$series['name'].' '.$version;
// $row['color'] = $color;
// $row['cardid'] = $info_json['cardid'];
// //获取销售
// $admin = $this->app_licheb_users_model->get(['id'=>$row['admin_id']],'uname');
// $row['uname'] = $admin['uname'];
//}
//$row['agent'] = $agent;
//$row['day'] = $contract ? date('Y年m月d日',$contract['c_time']):date('Y年m月d日');
//$row['cid'] = $contract['cid'];
//$row['vin'] = $items['vin'];
$row = $this->orders_model->get(['id'=>$id]);
$agent = $this->agents_model->get(['o_id'=>$id]);
$contract = $this->contracts_model->get(['o_id'=>$id,'type'=>3]);
$items = $this->items_model->get(['id'=>$row['item_id']],'vin');
$total_price = 0;
if($row){
//贷款
if(!$row['payway']){
$row['loan'] = $this->loans_model->get(['o_id'=>$id]);
}
$brand = $this->auto_brand_model->get(['id'=>$row['brand_id']],'name');
$series = $this->auto_series_model->get(['id'=>$row['s_id']],'name');
$car_json = json_decode($row['car_json'],true);
$row['info_json'] = $info_json = json_decode($row['info_json'],true);
$row['color'] = isset($car_json['color']) ? $car_json['color']['title'] : '';
$row['incolor'] = isset($car_json['incolor']) ? $car_json['incolor']['title'] : '';
$row['version'] = isset($car_json['version']) ? $car_json['version']['title'] : '';
$row['brand_name'] = $brand['name'];
$row['series_name'] = $series['name'];
$row['cardid'] = $info_json['cardid'];
//获取门店信息
$biz = $this->biz_model->get(['id'=>$row['biz_id']]);
$company = $this->sys_company_model->get(['id'=>$biz['company_id']]);
$row['company'] = $company;
//获取服务包
$packs = $this->package_model->get(['id'=>$row['pack_id']],'srv_ids');
if($packs['srv_ids']){
$row['services'] = $services = $this->services_model->select(["id in ({$packs['srv_ids']})"],'','','','title,field_name');
}
$attrs = "{$row['cor_id']}_{$row['v_id']}_{$row['incor_id']}";
$car = $this->auto_cars_model->get(['attrs'=>$attrs,'s_id'=>$row['s_id']]);
//获取挂牌价
$this->load->model('sys/sys_city_model');
$city = $this->sys_city_model->get(['city_id'=>$biz['city_id']],'fee_carno');
$car['fee_carno'] = $city['fee_carno'];
if($services){
foreach($services as $key=>$val){
$field_arr = explode('.',$val['field_name']);
if($car[$field_arr[1]]){
$total_price += $car[$field_arr[1]];
}
}
}
$where = [
'item_id' => $row['id'],
'app_id' => 1,
'status>' => 1
];
$pay = $this->purchase_model->sum('total_price',$where);
$row['pay_price'] = $pay['total_price'];
}
$row['total_price'] = $total_price;
$row['agent'] = $agent;
$row['day'] = $contract ? date('Y年m月d日',$contract['c_time']):date('Y年m月d日');
$row['cid'] = $contract['cid'];
$row['vin'] = $items['vin'];
$folder = $wxapp ? 'protocol' : 'html2pdf';
$this->load->view("wxapp/licheb/{$folder}/car_fh",$row);
}
+54
View File
@@ -0,0 +1,54 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Created by Vim
* User: lcc
* Date: 2021/08/09
* Time: 11:08
*/
require_once APPPATH.'controllers/wxapp/Wxapp.php';
class Services extends Wxapp{
public function __construct(){
parent::__construct();
$this->login_white = array('get_package');//登录白名单
$this->load->model('receiver/receiver_services_model','services_model');
$this->load->model('receiver/receiver_service_package_model','package_model');
}
//服务包
protected function get_package(){
$page = $this->input_param('page');
$size = $this->input_param('size');
!$page && $page = 1;
!$size && $size = 10;
$where = [
'status' => 1
];
$count = $this->package_model->count($where);
$list = [];
if($count){
$rows = $this->package_model->select($where,'id asc',$page,$size);
foreach($rows as $key=>$val){
$title = '';
if($val['srv_ids']){
$s_where = [
"id in ({$val['srv_ids']})" => null
];
$s_rows = $this->services_model->select($s_where,'','','','id,title');
$s_rows && $title = implode('+',array_column($s_rows,'title'));
}
$list[] = [
'id' => $val['id'],
'title' => $title
];
}
}
$data = [
'list' => $list,
'total' => $count
];
return $data;
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ class WxPayConfig
const APPID = 'wx98e64c11aac45966';
const APPSECRET = 'f8eec7be1c87a1c8e40213e144821ec3';
const MCHID = '1611216095';
const KEY = '53e08ba6735ab9777c0369e0c8ea0f7b';
const KEY = 'd1ddc03f6178767795dc283e68a80e81';
const SIGN_TYPE = 'MD5';
const NOTIFY_URL = '';
+13 -15
View File
@@ -31,23 +31,21 @@
<div style="font-size:14px;">
<div style="text-align:center;font-weight:bold;font-size:16px;">委托服务协议</div>
<div style="text-align:right;">协议编号:(右对齐,6位地区编码+合同类型id+yymmdd+6位随机数)</div>
<div><b>甲方(出卖人):</b>(根据门店所属公司)</div>
<div><b>统一社会信用代码:</b>(根据门店所属公司)</div>
<div><b>乙方(买受人):</b>(用户真实姓名)</div>
<div><b>(证件类型)</b>证件号码</div>
<div><b>联系电话:</b>(用户手机号码)</div>
<div>#假如有委托人#</div>
<div><b>委托代理人:</b>(委托人真实姓名)</div>
<div><b>(证件类型)</b>委托人证件号码</div>
<div><b>联系电话:</b>(委托人手机号码)</div>
<div style="text-align:right;">协议编号:<?=$cid?></div>
<div><b>甲方(出卖人):</b><?=$company['title']?></div>
<div><b>统一社会信用代码:</b><?=$company['credit_code']?></div>
<div><b>乙方(买受人):</b><?=$name?></div>
<div><b>联系电话:</b><?=$mobile?></div>
<?if($ifentrust){?>
<div><b>委托代理人:</b><?=$info_json['entrust_name']?></div>
<div><b>委托人身份证</b><?=$info_json['entrust_idcard']?></div>
<?}?>
<div><b>乙方为节省时间和精力,就甲乙双方《车辆买卖合同》标的的车辆,自愿委托甲方办理下列委托事项,并达成如下协议:</b></div>
<div><b>一、委托代办服务事项(乙方授权甲方代办下列事项)</b></div>
<div>代办保险服务 每个车一个价格</div>
<div>代办上牌服务 每个地级市一个价格</div>
<div>代办金融服务 (公式)</div>
<div>购买精品包</div>
<div>代办服务总价:(计算出合同总价,含保险)元, 壹千贰百叁十个肆玩万元整,保险费用由乙方直接支付给保险公司。</div>
<?foreach($services as $val){?>
<div><?=$val['title']?></div>
<?}?>
<div>代办服务总价:<?=$total_price?>元, <?=num_to_rmb($total_price)?>,保险费用由乙方直接支付给保险公司。</div>
<div><b>二、双方特别约定</b></div>
<div>1、车牌选号若为代选号牌时,均由甲方电话通知乙方,因乙方未能及时选号等其它原因,甲方有权代理决定并不对最终选号结果负责;若为自选号牌,甲方可配合乙方上牌,但不对最终选号结果负责。</div>
<div>2、乙方委托甲方代办车辆挂牌服务时,乙方应事先办妥机动车车辆保险,投保险种包括但不限于车辆损失险和第三者责任险。甲方在代办服务过程中造成车辆毁损、灭失的,乙方应当先向保险公司索赔,赔付不足部分由甲方予以修复或赔偿。</div>
+19 -16
View File
@@ -9,36 +9,37 @@
<div style="font-size:14px;">
<div style="text-align:center;font-weight:bold;font-size:16px;">车辆买卖合同</div>
<div style="text-align:right;">合同编号:<?=$cid?></div>
<div><b>甲方(出卖人):</b>(根据门店所属公司)</div>
<div><b>统一社会信用代码:</b>(根据门店所属公司)</div>
<div><b>乙方(买受人):</b>(用户真实姓名)</div>
<div><b>联系电话:</b>(用户手机号码)</div>
<div>#假如有委托人#</div>
<div><b>委托代理人:</b>(委托人真实姓名)</div>
<div><b>联系电话</b>(委托人手机号码)</div>
<div><b>甲方(出卖人):</b><?=$company['title']?></div>
<div><b>统一社会信用代码:</b><?=$company['credit_code']?></div>
<div><b>乙方(买受人):</b><?=$name?></div>
<div><b>联系电话:</b><?=$mobile?></div>
<?if($ifentrust){?>
<div><b>委托代理人:</b><?=$info_json['entrust_name']?></div>
<div><b>委托人身份证</b><?=$info_json['entrust_idcard']?></div>
<?}?>
<div><b>甲、乙双方依据《中华人民共和国合同法》及其他有关法律、法规的规定,在平等、自愿、协商一致的基础上,就买卖汽车事宜,订立本合同。</b></div>
<div><b>第一条 车辆基本情况</b></div>
<div>
<table style="width:100%;">
<tr>
<td style="width:50%;">
品牌:
品牌:<?=$brand_name?>
</td>
<td style="width:50%;">
车系:
车系:<?=$series_name?>
</td>
</tr>
<tr>
<td style="width:50%;">
车型:
车型:<?=$version?>
</td>
<td style="width:50%;">
车身颜色:
车身颜色:<?=$color?>
</td>
</tr>
<tr>
<td style="width:50%;">
内饰颜色:
内饰颜色:<?=$incolor?>
</td>
<td style="width:50%;">
@@ -68,14 +69,16 @@
</div>
<div>车辆单价为车身单价,续航里程为300KM以上的车辆已按扣减国家补贴后的价格结算。不包含车辆购置税、车辆保险费、挂牌杂费等其他费用。乙方如需委托甲方代理上牌、委托运输等服务的,双方应另外签订《委托服务协议》。</div>
<div><b>第三条付款方式</b></div>
<div>乙方在签订合同时即付定金人民币: 元,定金在结算时冲抵车款。</div>
<div>乙方选择一次性付款方式 / 分期付款方式</div>
<div>乙方在签订合同时即付定金人民币:<?=$dep_price?>元,定金在结算时冲抵车款。</div>
<div>乙方选择<?=$row['payway']?'一次性付款方式':'分期付款方式'?></div>
<div><b>第四条 车辆交付</b></div>
<div>1、交车时间:合同签订之日起 XX天内。</div>
<div>1、交车时间:合同签订之日起 <?=$give_time?>天内。</div>
<!--
<div>EX1 4个服务30天,3个服务 45 2个服务60天</div>
<div>雷丁 4个服务30天,3个服务 45 2个服务60天。)</div>
-->
<div>2、提车方式:乙方自提</div>
<div>3、交车地点 XX市XX区/ XXXX店  先按当前门店  。</div>
<div>3、交车地点 <?=$address?>。</div>
<div>4、交车时里程表记录小于100公里</div>
<div>5、备注:如乙方购买续航里程为300KM以上车辆,上牌性质是公户或营运车辆需确保购买车辆自行驶证注册日期起1年之内行驶2万公里,且至车辆注册之日起2年内不得过户,如因违反此条约使甲方在申领国补过程中造成的损失,一切责任及后果由乙方承担。</div>
<div><b>第五条 车辆确认</b></div>
+8 -9
View File
@@ -8,15 +8,14 @@
<div style="font-size:14px;">
<div style="text-align:center;font-weight:bold;font-size:16px;">车辆信息确认单</div>
<div><b>甲方(出卖人):</b>(根据门店所属公司)</div>
<div><b>统一社会信用代码:</b>(根据门店所属公司)</div>
<div><b>乙方(买受人):</b>(用户真实姓名)</div>
<div><b>(证件类型)</b>证件号码</div>
<div><b>联系电话:</b>(用户手机号码)</div>
<div>#假如有委托人#</div>
<div><b>委托代理人:</b>(委托人真实姓名)</div>
<div><b>(证件类型)</b>委托人证件号码</div>
<div><b>联系电话:</b>(委托人手机号码)</div>
<div><b>甲方(出卖人):</b><?=$company['title']?></div>
<div><b>统一社会信用代码:</b><?=$company['credit_code']?></div>
<div><b>乙方(买受人):</b><?=$name?></div>
<div><b>联系电话:</b><?=$mobile?></div>
<?if($ifentrust){?>
<div><b>委托代理人:</b><?=$info_json['entrust_name']?></div>
<div><b>委托人身份证</b><?=$info_json['entrust_idcard']?></div>
<?}?>
<div>乙方于<?=date('Y年m月d日',$c_time)?>在甲方购买 <?=$brand_name?> 车辆 1 台,并确认车架号为:<?=$vin?>。乙方已对上述车辆的厂牌型号、配置等进行认真检查并验收合格无异议。</div>
<div style="margin-top:50px;">
<table style="width:100%;">
+16 -16
View File
@@ -8,15 +8,15 @@
<div style="font-size:14px;">
<div style="text-align:center;font-weight:bold;font-size:16px;">车辆交付表</div>
<div><b>甲方(出卖人):</b>(根据门店所属公司)</div>
<div><b>统一社会信用代码:</b>(根据门店所属公司)</div>
<div><b>乙方(买受人):</b>(用户真实姓名)</div>
<div><b>(证件类型)</b>证件号码</div>
<div><b>联系电话:</b>(用户手机号码)</div>
<div>#假如有委托人#</div>
<div><b>委托代理人:</b>(委托人真实姓名)</div>
<div><b>(证件类型)</b>委托人证件号码</div>
<div><b>联系电话:</b>(委托人手机号码)</div>
<div><b>甲方(出卖人):</b><?=$company['title']?></div>
<div><b>统一社会信用代码:</b><?=$company['credit_code']?></div>
<div><b>乙方(买受人):</b><?=$name?></div>
<div><b>身份证号码:</b><?=$cardid?></div>
<div><b>联系电话:</b><?=$mobile?></div>
<?if($ifentrust){?>
<div><b>委托代理人:</b><?=$info_json['entrust_name']?></div>
<div><b>委托人身份证:</b><?=$info_json['entrust_idcard']?></div>
<?}?>
<div><b>一、车辆信息</b></div>
<div>
<table style="width:100%;">
@@ -25,12 +25,12 @@
品牌:<?=$brand_name?>
</td>
<td style="width:50%;">
车系:
车系:<?=$series_name?>
</td>
</tr>
<tr>
<td style="width:50%;">
车型:
车型:<?=$version?>
</td>
<td style="width:50%;">
车身颜色:<?=$color?>
@@ -38,15 +38,15 @@
</tr>
<tr>
<td style="width:50%;">
内饰颜色:
内饰颜色:<?=$incolor?>
</td>
<td style="width:50%;">
车架号:XW111111111111
车架号:<?=$vin?>
</td>
</tr>
<tr>
<td style="width:50%;">
车牌号:闽D232333
车牌号:<?=$agent['car_num']?>
</td>
<td style="width:50%;">
</td>
@@ -54,8 +54,8 @@
</table>
</div>
<div><b>二、结算信息</b></div>
<div>应付款合计 50100 ,其中包含:车辆价款 50000,委托代办款项 3000</div>
<div>已付款合计 50000</div>
<div>应付款合计 <?=number_format($price+$total_price,2)?> ,其中包含:车辆价款 <?=number_format($price,2)?>,委托代办款项 <?=number_format($total_price,2)?></div>
<div>已付款合计 <?=number_format($pay_price,2)?></div>
<div><b>三、证件资料交接信息</b></div>
<div>
<div><b>证件信息由后台配置,销售交付时钩选才显示:</b></div>
+13 -15
View File
@@ -12,23 +12,21 @@
<div class="inner40 line-height-17 font-28 color-666 text-break">
<div class="pt10 pb20 text-center text-bold font-40">委托代理服务协议</div>
<div class="text-right">协议编号:(右对齐,6位地区编码+合同类型id+yymmdd+6位随机数)</div>
<div><b>甲方(出卖人):</b>(根据门店所属公司)</div>
<div><b>统一社会信用代码:</b>(根据门店所属公司)</div>
<div><b>乙方(买受人):</b>(用户真实姓名)</div>
<div><b>(证件类型)</b>证件号码</div>
<div><b>联系电话:</b>(用户手机号码)</div>
<div>#假如有委托人#</div>
<div><b>委托代理人:</b>(委托人真实姓名)</div>
<div><b>(证件类型)</b>委托人证件号码</div>
<div><b>联系电话:</b>(委托人手机号码)</div>
<div class="text-right">协议编号:<?=$cid?></div>
<div><b>甲方(出卖人):</b><?=$company['title']?></div>
<div><b>统一社会信用代码:</b><?=$company['credit_code']?></div>
<div><b>乙方(买受人):</b><?=$name?></div>
<div><b>联系电话:</b><?=$mobile?></div>
<?if($ifentrust){?>
<div><b>委托代理人:</b><?=$info_json['entrust_name']?></div>
<div><b>委托人身份证</b><?=$info_json['entrust_idcard']?></div>
<?}?>
<div><b>乙方为节省时间和精力,就甲乙双方《车辆买卖合同》标的的车辆,自愿委托甲方办理下列委托事项,并达成如下协议:</b></div>
<div><b>一、委托代办服务事项(乙方授权甲方代办下列事项)</b></div>
<div>代办保险服务 每个车一个价格</div>
<div>代办上牌服务 每个地级市一个价格</div>
<div>代办金融服务 (公式)</div>
<div>购买精品包</div>
<div>代办服务总价:(计算出合同总价,含保险)元, 壹千贰百叁十个肆玩万元整,保险费用由乙方直接支付给保险公司。</div>
<?foreach($services as $val){?>
<div><?=$val['title']?></div>
<?}?>
<div>代办服务总价:<?=$total_price?>元,<?=num_to_rmb($total_price)?>,保险费用由乙方直接支付给保险公司。</div>
<div><b>二、双方特别约定</b></div>
<div>1、车牌选号若为代选号牌时,均由甲方电话通知乙方,因乙方未能及时选号等其它原因,甲方有权代理决定并不对最终选号结果负责;若为自选号牌,甲方可配合乙方上牌,但不对最终选号结果负责。</div>
<div>2、乙方委托甲方代办车辆挂牌服务时,乙方应事先办妥机动车车辆保险,投保险种包括但不限于车辆损失险和第三者责任险。甲方在代办服务过程中造成车辆毁损、灭失的,乙方应当先向保险公司索赔,赔付不足部分由甲方予以修复或赔偿。</div>
+19 -16
View File
@@ -13,36 +13,37 @@
<div class="inner40 line-height-17 font-28 color-666 text-break">
<div class="pt10 pb20 text-center text-bold font-40">车辆买卖合同</div>
<div class="text-right">合同编号:<?=$cid?></div>
<div><b>甲方(出卖人):</b>(根据门店所属公司)</div>
<div><b>统一社会信用代码:</b>(根据门店所属公司)</div>
<div><b>乙方(买受人):</b>(用户真实姓名)</div>
<div><b>联系电话:</b>(用户手机号码)</div>
<div>#假如有委托人#</div>
<div><b>委托代理人:</b>(委托人真实姓名)</div>
<div><b>联系电话</b>(委托人手机号码)</div>
<div><b>甲方(出卖人):</b><?=$company['title']?></div>
<div><b>统一社会信用代码:</b><?=$company['credit_code']?></div>
<div><b>乙方(买受人):</b><?=$name?></div>
<div><b>联系电话:</b><?=$mobile?></div>
<?if($ifentrust){?>
<div><b>委托代理人:</b><?=$info_json['entrust_name']?></div>
<div><b>委托人身份证</b><?=$info_json['entrust_idcard']?></div>
<?}?>
<div><b>甲、乙双方依据《中华人民共和国合同法》及其他有关法律、法规的规定,在平等、自愿、协商一致的基础上,就买卖汽车事宜,订立本合同。</b></div>
<div><b>第一条 车辆基本情况</b></div>
<div>
<table style="width:100%;">
<tr>
<td style="width:50%;">
品牌:
品牌:<?=$brand_name?>
</td>
<td style="width:50%;">
车系:
车系:<?=$series_name?>
</td>
</tr>
<tr>
<td style="width:50%;">
车型:
车型:<?=$version?>
</td>
<td style="width:50%;">
车身颜色:
车身颜色:<?=$color?>
</td>
</tr>
<tr>
<td style="width:50%;">
内饰颜色:
内饰颜色:<?=$incolor?>
</td>
<td style="width:50%;">
@@ -72,14 +73,16 @@
</div>
<div>车辆单价为车身单价,续航里程为300KM以上的车辆已按扣减国家补贴后的价格结算。不包含车辆购置税、车辆保险费、挂牌杂费等其他费用。乙方如需委托甲方代理上牌、委托运输等服务的,双方应另外签订《委托服务协议》。</div>
<div><b>第三条付款方式</b></div>
<div>乙方在签订合同时即付定金人民币: 元,定金在结算时冲抵车款。</div>
<div>乙方选择一次性付款方式 / 分期付款方式</div>
<div>乙方在签订合同时即付定金人民币:<?=$dep_price?>元,定金在结算时冲抵车款。</div>
<div>乙方选择<?=$row['payway']?'一次性付款方式':'分期付款方式'?></div>
<div><b>第四条 车辆交付</b></div>
<div>1、交车时间:合同签订之日起 XX天内。</div>
<div>1、交车时间:合同签订之日起 <?=$give_time?>天内。</div>
<!--
<div>EX1 4个服务30天,3个服务 45 2个服务60天</div>
<div>雷丁 4个服务30天,3个服务 45 2个服务60天。)</div>
-->
<div>2、提车方式:乙方自提</div>
<div>3、交车地点 XX市XX区/ XXXX店  先按当前门店  。</div>
<div>3、交车地点 <?=$address?>。</div>
<div>4、交车时里程表记录小于100公里</div>
<div>5、备注:如乙方购买续航里程为300KM以上车辆,上牌性质是公户或营运车辆需确保购买车辆自行驶证注册日期起1年之内行驶2万公里,且至车辆注册之日起2年内不得过户,如因违反此条约使甲方在申领国补过程中造成的损失,一切责任及后果由乙方承担。</div>
<div><b>第五条 车辆确认</b></div>
+8 -9
View File
@@ -12,15 +12,14 @@
<div class="inner40 line-height-17 font-28 color-666 text-break">
<div class="pt10 pb20 text-center text-bold font-40">车辆信息确认单</div>
<div><b>甲方(出卖人):</b>(根据门店所属公司)</div>
<div><b>统一社会信用代码:</b>(根据门店所属公司)</div>
<div><b>乙方(买受人):</b>(用户真实姓名)</div>
<div><b>(证件类型)</b>证件号码</div>
<div><b>联系电话:</b>(用户手机号码)</div>
<div>#假如有委托人#</div>
<div><b>委托代理人:</b>(委托人真实姓名)</div>
<div><b>(证件类型)</b>委托人证件号码</div>
<div><b>联系电话:</b>(委托人手机号码)</div>
<div><b>甲方(出卖人):</b><?=$company['title']?></div>
<div><b>统一社会信用代码:</b><?=$company['credit_code']?></div>
<div><b>乙方(买受人):</b><?=$name?></div>
<div><b>联系电话:</b><?=$mobile?></div>
<?if($ifentrust){?>
<div><b>委托代理人:</b><?=$info_json['entrust_name']?></div>
<div><b>委托人身份证</b><?=$info_json['entrust_idcard']?></div>
<?}?>
<div>乙方于<?=date('Y年m月d日',$c_time)?>在甲方购买 <?=$brand_name?> 车辆 1 台,并确认车架号为:<?=$vin?>。乙方已对上述车辆的厂牌型号、配置等进行认真检查并验收合格无异议。</div>
<div class="mt50">
<table style="width:100%;">
+16 -16
View File
@@ -12,15 +12,15 @@
<div class="inner40 line-height-17 font-28 color-666 text-break">
<div class="pt10 pb20 text-center text-bold font-40">车辆交付表</div>
<div><b>甲方(出卖人):</b>(根据门店所属公司)</div>
<div><b>统一社会信用代码:</b>(根据门店所属公司)</div>
<div><b>乙方(买受人):</b>(用户真实姓名)</div>
<div><b>(证件类型)</b>证件号码</div>
<div><b>联系电话:</b>(用户手机号码)</div>
<div>#假如有委托人#</div>
<div><b>委托代理人:</b>(委托人真实姓名)</div>
<div><b>(证件类型)</b>委托人证件号码</div>
<div><b>联系电话:</b>(委托人手机号码)</div>
<div><b>甲方(出卖人):</b><?=$company['title']?></div>
<div><b>统一社会信用代码:</b><?=$company['credit_code']?></div>
<div><b>乙方(买受人):</b><?=$name?></div>
<div><b>身份证号码:</b><?=$cardid?></div>
<div><b>联系电话:</b><?=$mobile?></div>
<?if($ifentrust){?>
<div><b>委托代理人:</b><?=$info_json['entrust_name']?></div>
<div><b>委托人身份证:</b><?=$info_json['entrust_idcard']?></div>
<?}?>
<div><b>一、车辆信息</b></div>
<div>
<table style="width:100%;">
@@ -29,12 +29,12 @@
品牌:<?=$brand_name?>
</td>
<td style="width:50%;">
车系:
车系:<?=$series_name?>
</td>
</tr>
<tr>
<td style="width:50%;">
车型:
车型:<?=$version?>
</td>
<td style="width:50%;">
车身颜色:<?=$color?>
@@ -42,15 +42,15 @@
</tr>
<tr>
<td style="width:50%;">
内饰颜色:
内饰颜色:<?=$incolor?>
</td>
<td style="width:50%;">
车架号:XW111111111111
车架号:<?=$vin?>
</td>
</tr>
<tr>
<td style="width:50%;">
车牌号:闽D232333
车牌号:<?=$agent['car_num']?>
</td>
<td style="width:50%;">
</td>
@@ -58,8 +58,8 @@
</table>
</div>
<div><b>二、结算信息</b></div>
<div>应付款合计 50100 ,其中包含:车辆价款 50000,委托代办款项 3000</div>
<div>已付款合计 50000</div>
<div>应付款合计 <?=number_format($price+$total_price,2)?> ,其中包含:车辆价款 <?=number_format($price,2)?>,委托代办款项 <?=number_format($total_price,2)?></div>
<div>已付款合计 <?=number_format($pay_price,2)?></div>
<div><b>三、证件资料交接信息</b></div>
<div>
<div><b>证件信息由后台配置,销售交付时钩选才显示:</b></div>
+2 -1
View File
@@ -1,6 +1,7 @@
{
"require": {
"gregwar/image": "^2.1",
"tencentcloud/ocr": "^3.0"
"tencentcloud/ocr": "^3.0",
"wechatpay/wechatpay-guzzle-middleware": "^0.2.2"
}
}
+52 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6f7afccbfb645077ee3dcb371ebf6039",
"content-hash": "4f7bbe710bfa187154b75afba695ea66",
"packages": [
{
"name": "gregwar/cache",
@@ -908,6 +908,57 @@
"description": "TencentCloudApi php sdk ocr",
"homepage": "https://github.com/tencentcloud-sdk-php/ocr",
"time": "2021-07-16T01:13:52+00:00"
},
{
"name": "wechatpay/wechatpay-guzzle-middleware",
"version": "0.2.2",
"source": {
"type": "git",
"url": "https://github.com/wechatpay-apiv3/wechatpay-guzzle-middleware.git",
"reference": "6782ac33ed8cf97628609a71cdc5e84a6a40677a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wechatpay-apiv3/wechatpay-guzzle-middleware/zipball/6782ac33ed8cf97628609a71cdc5e84a6a40677a",
"reference": "6782ac33ed8cf97628609a71cdc5e84a6a40677a",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-openssl": "*",
"php": ">=5.5"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.3"
},
"suggest": {
"ext-bcmath": "Require bcmath in php 5.* version.",
"guzzlehttp/guzzle": "For using wechatpay guzzle middleware."
},
"bin": [
"tool/CertificateDownloader.php"
],
"type": "library",
"autoload": {
"psr-4": {
"WechatPay\\GuzzleMiddleware\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"description": "WechatPay API V3 Guzzle Middleware",
"homepage": "https://wechatpay-api.gitbook.io/wechatpay-api-v3/",
"keywords": [
"wechatpay"
],
"time": "2021-03-05T03:09:29+00:00"
}
],
"packages-dev": [],
+13
View File
@@ -1039,3 +1039,16 @@ if(!function_exists('convert_url_query')){
return $params;
}
}
if(!function_exists('create_contract_no')){
/**
* 生成合同编号
* int $city_id
* int $id 合同id
* int $type 合同类型 (0整车合同 1代理协议 2确定信息 3交接信息)
*/
function create_contract_no($city_id = 350200, $id, $type = 0)
{
return $city_id . $type . $id . date('Ymd') . sprintf("%06d", rand(1,999999));
}
}
+1
View File
@@ -45,6 +45,7 @@ class Pdf {
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
$pdf->setCellHeightRatio(1.1); //设置行高
$pdf->SetMargins(PDF_MARGIN_LEFT, 5,PDF_MARGIN_RIGHT);
$pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
+90
View File
@@ -0,0 +1,90 @@
<?php
/**
* Created by Vim
* User: lcc
* Date: 2021-08-11
* Time: 18:54
* Desc: 微信支付V3
*/
use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
class WechatPayV3{
private $merchantId;
private $merchantSerialNumber;
private $merchantPrivateKey;
public function __construct($config){
$this->merchantId = $config['merchantId']; //商户号
$this->merchantSerialNumber = $config['merchantSerialNumber']; //商户API证书序列号
$this->merchantPrivateKey = $config['merchantPrivateKey']; //商户证书路径
$this->wechatpayCertificate = $config['wechatpayCertificate']; //微信支付平台证书
}
public function unifiedOrder($json,$appid,$noncestr){
$url = 'https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi';
// 商户相关配置
//$merchantId = '1612096731'; // 商户号
//$merchantSerialNumber = '761590F1FF6DFC2466894F96E2DE1169CE644A74'; // 商户API证书序列号
$merchantPrivateKey = PemUtil::loadPrivateKey($this->merchantPrivateKey); // 商户私钥
// 微信支付平台配置
$wechatpayCertificate = PemUtil::loadCertificate($this->wechatpayCertificate); // 微信支付平台证书
// 构造一个WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
->withMerchant($this->merchantId, $this->merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array
->build();
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = GuzzleHttp\HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
// 创建Guzzle HTTP Client时,将HandlerStack传入
$client = new GuzzleHttp\Client(['handler' => $stack]);
// 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
try {
$resp = $client->request('POST', $url, [
'json' => $json,
'headers' => [ 'Accept' => 'application/json' ]
]);
$body = json_decode($resp->getBody(),true);
if(!$body['prepay_id']){
return ['code'=>0,'msg'=>'下单失败'];
}
//微信支付(小程序)签名
$timeStamp = time();
$str = $this->getWechartSign($appid,$timeStamp,$noncestr,'prepay_id='.$body['prepay_id']);
$data = array('appid'=>$appid,'timeStamp'=>"$timeStamp",'package'=>'prepay_id='.$body['prepay_id'],'paySign'=>$str,'nonceStr'=>$noncestr,'signType'=>'RSA');
return ['code'=>1,'data'=>$data];
} catch (RequestException $e) {
// 进行错误处理
//echo $e->getMessage()."\n";
$error_msg = $e->getMessage();
if ($e->hasResponse()) {
//echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
//echo $e->getResponse()->getBody();
}
return ['code'=>0,'msg'=>$error_msg];
}
}
//调起支付的签名
private function getWechartSign($appid,$timeStamp,$noncestr,$prepay_id){
$str = $appid."\n".$timeStamp."\n".$noncestr."\n".$prepay_id."\n";
$key = file_get_contents($this->merchantPrivateKey);
$str = $this->getSha256WithRSA($str,$key);
return $str;
}
private function getSha256WithRSA($content, $privateKey){
$binary_signature = "";
$algo = "SHA256";
openssl_sign($content, $binary_signature, $privateKey, $algo);
$sign = base64_encode($binary_signature);
return $sign;
}
}
+127 -8
View File
@@ -6,6 +6,7 @@
class Orders_entity{
private $ci;
const SRV_MCH_ID = '1612636924'; //收取服务费商户号 厦门狸车服务
public function __construct(){
$this->ci = & get_instance();
@@ -22,11 +23,11 @@ class Orders_entity{
switch($type){
case 0;
$path = '/wxapp/licheb/protocol/car';
$title = '车辆整车合同';
$title = '车辆买卖合同';
break;
case 1:
$path = '/wxapp/licheb/protocol/agent';
$title = '委托代理服务协议';
$title = '委托服务协议';
break;
case 2:
$path = '/wxapp/licheb/protocol/car_ck';
@@ -34,7 +35,7 @@ class Orders_entity{
break;
case 3:
$path = '/wxapp/licheb/protocol/car_fh';
$title = '车辆交接信息';
$title = '车辆交付表';
break;
default:
}
@@ -46,9 +47,9 @@ class Orders_entity{
/**
* 签完成协议后操作
* @param $oid int 订单id
* @param $oid int 订单id
* @param $app_id int 小程序id
* @param $userinfo array() 小程序用户信息
* @param $userinfo array 小程序用户信息
*/
public function sign_after($oid,$app_id,$userinfo){
$this->ci->load->model('receiver/order/receiver_orders_model','orders_model');
@@ -79,16 +80,19 @@ class Orders_entity{
}
}else{
$this->ci->signs_model->update(['status'=>1],['o_id'=>$row['id']]);
$res = $this->c_order($row,$app_id,$userinfo);
$res = $this->c_order(self::SRV_MCH_ID,$row,$app_id,$userinfo);
}
return $res;
}
/**
* 创建定金消费订单
* @param $oder array 订单数据
* @param $mch_id string 微信商户号
* @param $oder array 订单数据
* @param $app_id int 应用id
* @param $userinfo array 用户信息
* return boolean
*/
public function c_order($order,$app_id,$userinfo){
public function c_order($mch_id,$order,$app_id,$userinfo){
$this->ci->load->model('apporder/order_purchase_model');
$this->ci->load->model('auto/auto_series_model');
$this->ci->load->model('auto/auto_brand_model');
@@ -107,6 +111,7 @@ class Orders_entity{
'app_id' => $app_id,
'app_uid' => $userinfo['uid'],
'sid' => $sid,
'mch_id' => $mch_id,
'item_id' => $order['id'],
'item_title' => $brand['name'].$series['name'],
'item_num' => 1,
@@ -244,6 +249,120 @@ class Orders_entity{
}
return $res;
}
//确认车辆完成创建两个支付订单
public function check_finish($oid,$app_id,$userinfo){
$this->ci->load->model('receiver/order/receiver_orders_model','orders_model');
$this->ci->load->model('apporder/order_purchase_model');
$this->ci->load->model('auto/auto_series_model');
$this->ci->load->model('auto/auto_brand_model');
$this->ci->load->model('auto/auto_cars_model');
$this->ci->load->model("biz/biz_model");
$this->ci->load->model('receiver/receiver_service_package_model','package_model');
$this->ci->load->model('receiver/receiver_services_model','services_model');
$this->ci->load->model("sys/sys_company_model");
$this->ci->load->model('sys/sys_city_model');
$row = $this->ci->orders_model->get(['id'=>$oid]);
if(!$row){
return false;
}
$brand = $this->ci->auto_brand_model->get(['id'=>$row['brand_id']],'name');
$series = $this->ci->auto_series_model->get(['id'=>$row['s_id']],'name');
$car_json = json_decode($row['car_json'],true);
$color = isset($car_json['color']) ? $car_json['color'] : '';
$jsondata['car'] = $car_json;
if($color['jsondata']['img']){
$jsondata['cover'] = $color['jsondata']['img'];
}
//获取门店信息
$biz = $this->ci->biz_model->get(['id'=>$row['biz_id']],'company_id,city_id');
$company = $this->ci->sys_company_model->get(['id'=>$biz['company_id']]);
//获取服务包
$packs = $this->ci->package_model->get(['id'=>$row['pack_id']],'srv_ids');
$services = [];
if($packs['srv_ids']){
$services = $this->ci->services_model->select(["id in ({$packs['srv_ids']})"],'','','','title,field_name');
}
$attrs = "{$row['cor_id']}_{$row['v_id']}_{$row['incor_id']}";
$car = $this->ci->auto_cars_model->get(['attrs'=>$attrs,'s_id'=>$row['s_id']]);
//获取挂牌价
$city = $this->ci->sys_city_model->get(['city_id'=>$biz['city_id']],'fee_carno');
$car['fee_carno'] = $city['fee_carno'];
$srv_price = 0;
if($services){
foreach($services as $key=>$val){
$field_arr = explode('.',$val['field_name']);
if($car[$field_arr[1]]){
$srv_price += $car[$field_arr[1]];
}
}
}
if($row['payway']){ //全款
$to_com_price = $car['price']; //给销售公司金额 裸车价格
$to_srv_price = $car['price'] + $srv_price - $to_com_price - $row['deposit'] - $car['price_insure']; //给服务公司金额 裸车价格+服务费-给销售公司金额-定金-保险
$to_srv_price = $to_srv_price>0 ? $to_srv_price : 0;
}else{ //分期
$to_com_price = $car['first_pay']; //给销售公司金额
$to_srv_price = $car['first_pay'] + $srv_price - $to_com_price - $row['deposit'] - $car['price_insure']; //给服务公司的金额 首付+服务费-给销售公司-定金-保险
$to_srv_price = $to_srv_price>0 ? $to_srv_price : 0;
}
$add_data = [];
if($to_srv_price>0){
$order_type = 5;
$sid = create_order_no(350200,'liche',1,$order_type);
$add_data[] = [
'app_id' => $app_id,
'app_uid' => $userinfo['uid'],
'sid' => $sid,
'mch_id' => self::SRV_MCH_ID,
'item_id' => $row['id'],
'item_title' => $brand['name'].$series['name'],
'item_num' => 1,
'type' => $order_type,
'item_price' => $to_srv_price,
'total_price' => $to_srv_price,
'uname' => $userinfo['nickname'],
'mobile' => $userinfo['mobile'],
'payway' => 1,
'status' => 1,
'status_detail' => 11,
'jsondata' => json_encode($jsondata,JSON_UNESCAPED_UNICODE),
'c_time' => time(),
'cf_id' => $row['id']
];
}
if($to_com_price>0){
$order_type = 6;
$sid = create_order_no(350200,'liche',1,$order_type);
$add_data[] = [
'app_id' => $app_id,
'app_uid' => $userinfo['uid'],
'sid' => $sid,
'mch_id' => $company['wx_mchid'],
'item_id' => $row['id'],
'item_title' => $brand['name'].$series['name'],
'item_num' => 1,
'type' => $order_type,
'item_price' => $to_com_price,
'total_price' => $to_com_price,
'uname' => $userinfo['nickname'],
'mobile' => $userinfo['mobile'],
'payway' => 1,
'status' => 1,
'status_detail' => 11,
'jsondata' => json_encode($jsondata,JSON_UNESCAPED_UNICODE),
'c_time' => time(),
'cf_id' => $row['id']
];
}
$result = false;
if($add_data){
$result = $this->ci->order_purchase_model->add_batch($add_data);
}
return $result;
}
}
?>
+2 -57
View File
@@ -9,8 +9,9 @@ class Sign_entity{
private $ci;
private $comp_img = 'https://qimg.haodian.cn/hdi/2021/07/f2308e9066d290eb/79063515aefc302f.png'; //公司印章图片地址
public function __construct(){
public function __construct($params=[]){
$this->ci = & get_instance();
$params['comp_img'] && $this->comp_img = $params['comp_img'];
}
/**
@@ -63,60 +64,4 @@ class Sign_entity{
}
}
/**
* 图片签名
* @param $orgin_url string 签名图片地址
* @param $user_image base64 用户签名文件对象
* @param $width int 用户签名x坐标
* @param $height int 用户签名y坐标
* @param $o_width int 公司签名x坐标
* @param $o_height int 公司签名y坐标
* @param $need_c boolean 是否需要公司章
* @param $s_path string 图片保存地址
* return string 返回合成后图片地址
*/
public function test($origin_url,$user_file,$width,$height,$o_width='',$o_height='',$need_c=true,$s_path='' ){
$user_file = "iVBORw0KGgoAAAANSUhEUgAAAXcAAAKdCAMAAADvKdm1AAAAAXNSR0IB2cksfwAAAFFQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXKkVQgAAABt0Uk5TADCA/99A78/Q8CAQwKBwv+BQkLBgr59/P2+PuKEyZQAAC7pJREFUeJzt3ely6zYMhuHKuy0v8ZJz2t7/hdaCvCTxJskkPvXgff51ppPSGJUEQVD66y8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+J8oCvUIQhoMh6OxehABTYbD4VQ9iIBmx7jP1YMIqHreB+pBxLM4hn1YqkcRz/gY9qV6EAEd05nhSj2IgFZM7xLLY9xJ392V1bK6UI8inhXZu0JRPe5r9Sji2RzD/qEeRDzbIauqwohNk8KAZEahrEpiO/Uo4qke9zklMW8lFWAJHncJHncNHncJHncNHncJHncNHncJHncNHncNHneJMY+7xI6mGYnqvGOrHkQ8BT2REutj2PfqQQQ0Pcb9oB5EPAuOVSUOx7DP1IMIaE+PmIK1onJ30t2W3jyJFV0zClYCpifSHR3vEnS8a9DxLnGg412hHFESU6DjXcIqYpyruvusFlUOPLxVx9kU3v1xjUxiTSFSwQozdG+0keRVgwealVqyrLtXRiG6bybqMN+aRAj8TB3lOyK8crJczdVhvkFDQjPVcUeq3H07G35EmGcSsMoMsXJXpUQcd7izPROVGXfsmTSqihh1d3f2HjGOmdxVTQRUxNxZzwwFYHcp90xobHHa2scoIvbHpYw8Ya5xtLiWkUek8H4+v1QRN+rBxGG5e9UAXF2O58KBl/LavFF92oAijZPdNXcvP1hbvViL2LkyU1THVhEO6PSm3+ruB6Z4H5a6f7lmsOfYycPiZ4tYPcWTxWe2uTnuKMji8zvcmVUGXxda5GCHqjd1yCVF4czshvDNKVM5p1CT1fjBjGKFg0//8QRhBYK73XQ76gUZDR7P41Wf64Rj7iyKJ1tTqxeQTGZh7yF4tHquz7VhJLZ+XhBYMtNkYan7k/cQLJhpsvh81Q/JTJPD+HVUmWkyeJi6XzHTpPckdb9ipklt0exUiZkmsduq+13MNGltm57lMdOkdL/qfhczTUK75nc7FvR1JFO0Ocdbc+iXyveGmVf2HPqlsf7RMPMCh35p2CzT5r2cVlH4lW08QVifUrv7wTs6yN5WTtufnJYzOsje9dklPaGD7F02ZbS/Hsy29T3WlTfrMGPQQfYOmy86vXODbesb6lSm21O75TpCV11SmatVu90WLjqlMhdcR+ioYypzMaZXtYvOqczFgF7V9or25YEbM5LJtqzjumMqc2HJJJeK29gkmSNsrpqytjaW6n0PNsWzfWrKntMkb7FaUYtvztbURN8GmnHc2lSSNfXyx2Zkkw2lWVPP7AIOHTWvpX6HUsHa2kS6NfXbX2SKfy7lmnq2pzT5StI19fJHP+ioeSHtmnpGafKFXO8lpDT5VPo19Yxr9E/kWFNPOOd+LMuaekYy+VCeNfWMZPKBzO/6JZm8L9+aekIyeY9FJe934UkmbxVvtIY1xjn3T/X3C3I/i5xz//BeR15ztoawtp7VYff4CNaaBoMvfnmF/XTOTVJjLOxe31je02Bwsn67EbKN+pybKzj1Wuf4HVXbt5LG12H3zO2swSB6Gm9hdw6CNRjETuPrsHv/Tx8+jdeEPXwarwp78DReF/bQabwy7IHT+MKlBPlQ1DTeDrGVvztoGp/3ELuJkGl8Hz5YG/DWWfZD7EbC3To75D/EbiTYrTP/WtgjoW6dSRP37yLdOutR2CPdOutV2MO8em877VfYT7XJ3+pR5DbqW9hPr96b/uGdwj2silgX2TGd1Ke1GW1nw3nf3tx7qAM/+f1HR76HSts/HSP/h082/TO2PH44Uo8jnvW8fytPCCWB19j2L8WNwXbSvGHVH3dcRQZRimQ9U/JZRQ2+TyGyDHbi2hdVkWyuHkREh+GcKg0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBmfxfqEYS04+t6CvZlePUgAqq+VL9UDyIe+9QYnwByNzmGfa8eRDzjY9jnfDvV3TGZGa7UgwhodIz7Vj2IeBbVqso0427Nqiqx5Hu1CmU1zfBhbHfVl+Bn6kEEtDrGfaceRDxltVmlCOyummb4Kra/atM0UA8ingObJokNtRmFMcm7xJ4agcKCgyaJFeeqClaaoX/D3eAY9g/1IOKxEgGPu7sD59kSlAgk2DNpUCKQYM+kwZ5JgiRSgz2TBkmkBHsmDZJICfZMGiSREuyZNEgiNdgzSZBEakxpAVYoSCIlVjQrKVglknuT7tYkkRJUIiXGdLxLrKhEKlCa0aA0ozGiNKOwpTQjwfmehK2q3M52t+N8T4EmMY0VLyNQsMI7r1Zyt2HPpHCgRKBQjkhmFAYcqyosJpx3KHxSmVEY8y4CiSmviVQgh5Swwgz1X3dVHXJODunNyu7kkO6sMEMO6c1ySDoi3VGYkRhwqKqwIIeUoDAjseVwT4HTDo0di6rCmJ2qxJQWAoUB5V8FUneNDam7woF6mIIdMpG6u1txyKRA6i5hBQI6N9xRdZewOwa8VMkdR9kSa1J3BSsQTP/meXdWlSErIx55T4Ph2YRH3o/tmPbLIRsnV7ZjmpeL1YwH3tOv82FHOeeB97O97piqeX5CZcyFpZCnuow98J/a8USx+VqXYfvkZf29PaxaWkcsrdlZOWz5458pj+VWTn+eZNMv5mF3M5+XH1WlRjWeIGyj+qNfhn7g7KyB4KbovqdnLLPPu3P5okriJ0zx2awf1MGsfYnA52Ip490GAgv8lCw+C6sPPGhTWhH4XCxzf/iCGQv8xnVAQXw+P+SwQ5BfjuMJYncnc/+qnBH4DGzlnD2bwOvA86KrpCyVeXHBoJgT+MTqVOZVgl6wf0rreSpzZdUbivHJvEhlrg5kkwm9SmV+/qucgiTxOpX5Ysnamki9pjadtO0UhLU1gU2TVObKkhrW1rcN2p4msbamYBumdt9sZm1N4KZ9oAHW1rfdtg80YGsrgX/DvfaBBqjUvMf63Lvc2bO1lcB3delzb43Av2H7RmJC4Dsrv/S5t0fgu7rfpNQYge/mUZNSYwS+i+L9dykR+Pbqq5Jv3hgj8K1tkkSMwLeUqrZF4FuxcCV5UV4deC78NVLcvV7QDT3ajdVraqpQEfim0qypFwS+meTnRXXgOXJ9Lt2aesGthNfsqCP1e/II/CtFow7U1gj8c9aklGOfY3dceWHKA00bfzv8Za6DPFaHPc9nIwj8Y7/yhZ3AP2Zhz/di8ZLujrvWbfqtu6Ct5h7bL+X9Ng2Bv9Xs6tibCPyNb2/Jy2bLOch3P96Slw0HUN8s3L7EROC/2iToHmiIwF95zTKGwJ9lKLm//M8R+FPm7vhWawJfEUSBwItiQOBFEYgeeNnvjx144a+PHHjpb48bePEvjxr4f9S/O2TgFxv9rw4X+O35u4bi3xwt8KM66nP5L64D/696GF7sjRnDZQ++NFYH/jNIC992dnzY+/FNjoOduU578AgEUx92T/hAiLe6kyzQ6toXpbVp08Pnzw4ah5sgq2uPbG2SH3H7zFthqe2EG8beyvoz6f+oxxHPaXVlkvdW7125fuZuXG+hWF29LWwLNWEL5a3cxypQ9seOSV7jwCSvUW+hyOTdnbZQZPLuBkMOQyTqOhnlGndFfRjyWz2OcE6HIVFOvHukPgyhJu/uVK6hauCtZJIXoSYvQk1epODEW+NUk2cL5e1UriGtcUc/mciBfjKNLd3aGqd+MtIab3Rri5wKlATe3YC0RuNA4DXqyjD5pLu6WkOZzF197joln/RW55Mk8u7qMhn3Xf2RyIusmGo0DmQ1Ggc2UBorpngJSyeZ4v3ZzpUp3h9TvAhTvAZTvIhN8SOmeHc2xW/Uowgo+TfY0cyStVWi/GBtlWD7JML2SYTtk0a9fVKPIiCb4lla/VXvaOrHy71jqS5279WDCKhggteoMhr1GCIi7hrEXYO4axB3DeKu8TEcztRjiGg7++C1EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoLf+A6jRZVspl5i5AAAAAElFTkSuQmCC";
$arrContextOptions=array(
"ssl"=>array(
"verify_peer"=>false,
"verify_peer_name"=>false,
),
);
//临时保存用户签名图片
if (!file_exists(FCPATH.'/temp')){
$oldumask = umask(0);
mkdir(FCPATH.'/temp', 0777, true);
umask($oldumask);
}
$file_name = time().rand(1,9999999);
!$s_path && $s_path = FCPATH.'temp/'.md5('sign'.$file_name).'.jpg';
$imgPath = FCPATH.'temp/'.md5($file_name).'.png';
$res = file_put_contents($imgPath,base64_decode($user_file));
if(!$res){
return false;
}
$yh_image = Image::open($imgPath)->cropResize(150,150)->rotate(-90);
//原始签名文件
$data = file_get_contents($origin_url,false,stream_context_create($arrContextOptions));
$imgobj = Image::fromData($data);
if($need_c){
!$o_height && $o_height = $height;
//公司
$gs_data = file_get_contents($this->comp_img,false,stream_context_create($arrContextOptions));
$gs_image = Image::fromData($gs_data)->cropResize(200,200);
$imgobj->merge($gs_image,$o_width,$o_height);
}
$imgobj->merge($yh_image,$width,$height)->save($s_path);
@unlink($imgPath);
if(file_exists($s_path)){
return str_replace(FCPATH,'',$s_path);
}else{
return false;
}
}
}
@@ -0,0 +1,13 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Receiver_service_package_model extends HD_Model
{
private $table_name = 'lc_receiver_service_package';
public function __construct()
{
parent::__construct($this->table_name, 'default');
}
}
@@ -0,0 +1,13 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Receiver_services_model extends HD_Model
{
private $table_name = 'lc_receiver_services';
public function __construct()
{
parent::__construct($this->table_name, 'default');
}
}
@@ -11,7 +11,7 @@ defined('BASEPATH') OR exit('No direct script access allowed');
class Receiver_order_ckcars_model extends HD_Model
{
private $table_name = 'lc_receiver_order_ckcars';
private $status_arr = [ 0 => '车辆确认中',1 => '用户未签名',2=>'已确'];
private $status_arr = [ 0 => '车辆确认中',1 => '用户未签名',2 => '尾款未支付' ,3 => '已确'];
public function __construct()
{
+27 -2
View File
@@ -19,6 +19,8 @@ class Payment_service extends HD_Service{
$this->load->model('receiver/order/receiver_orders_model','orders_model');
$this->load->model('receiver/order/receiver_order_signs_model','order_signs_model');
$this->load->model('receiver/order/receiver_order_bills_model','bills_model');
$this->load->model('receiver/order/receiver_order_ckcars_model','ckcars_model');
}
@@ -30,12 +32,12 @@ class Payment_service extends HD_Service{
public function after_pay($sid,$pay_price = ''){
if($sid){
debug_log("[start] ". __FUNCTION__ . ": sid:".$sid, $this->log_file);
$order = $this->purchase_model->get(array('sid'=>$sid,'app_id'=>$this->app_id,'status'=>1));
$order = $this->purchase_model->get(array('sid'=>$sid,'app_id'=>$this->app_id));
if(!$order){
debug_log("[error] ". __FUNCTION__ . ":{$sid}_订单不存在", $this->log_file);
return array('code'=>0,'msg'=>'订单不存在');
}
if(!$order['status']>1){
if($order['status']>1){
debug_log("[error] ". __FUNCTION__ . ":{$sid}_订单已支付", $this->log_file);
return array('code'=>0,'msg'=>'订单已支付');
}
@@ -92,6 +94,29 @@ class Payment_service extends HD_Service{
return array('code'=>0,'msg'=>'更新失败');
}
break;
case 5: //委托服务费
case 6: //首付或尾款
$upd = array('status'=>2,'status_detail'=>21,'pay_time'=>date('Y-m-d H:i:s'));
$pay_price && $upd['pay_price'] = $pay_price;
$res = $this->purchase_model->update($upd,array('id'=>$order['id']));
if($res){
$nopay = $this->purchase_model->count(['app_id'=>$this->app_id,'cf_id'=>$order['item_id'],'type in (5,6)'=>null,'status'=>1]); //未支付的尾款或者服务费
if(!$nopay){
//更新订单状态
$row = $this->orders_model->get(['id'=>$order['item_id']]);
if($row){
$this->ckcars_model->update(['status'=>3],['o_id'=>$row['id']]);
$this->orders_model->update(['status'=>3],['id'=>$row['id']]);
if(!$this->bills_model->count(['o_id'=>$row['id']])){
$this->bills_model->add(['o_id'=>$row['id'],'c_time'=>time()]);
}
}
}
return array('code'=>1,'msg'=>'操作成功');
}else{
return array('code'=>0,'msg'=>'更新失败');
}
break;
default:
debug_log("[error] ". __FUNCTION__ . ":{$item['type']}_未知商品类型", $this->log_file);
return array('code'=>0,'msg'=>'未知商品类型');
+1
View File
@@ -0,0 +1 @@
../wechatpay/wechatpay-guzzle-middleware/tool/CertificateDownloader.php
+1
View File
@@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'WechatPay\\GuzzleMiddleware\\' => array($vendorDir . '/wechatpay/wechatpay-guzzle-middleware/src'),
'TencentCloud\\' => array($vendorDir . '/tencentcloud/common/src/TencentCloud', $vendorDir . '/tencentcloud/ocr/src/TencentCloud'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'),
+8
View File
@@ -19,6 +19,10 @@ class ComposerStaticInitd0872984a1db7aa104ae1184a3170d3e
);
public static $prefixLengthsPsr4 = array (
'W' =>
array (
'WechatPay\\GuzzleMiddleware\\' => 27,
),
'T' =>
array (
'TencentCloud\\' => 13,
@@ -43,6 +47,10 @@ class ComposerStaticInitd0872984a1db7aa104ae1184a3170d3e
);
public static $prefixDirsPsr4 = array (
'WechatPay\\GuzzleMiddleware\\' =>
array (
0 => __DIR__ . '/..' . '/wechatpay/wechatpay-guzzle-middleware/src',
),
'TencentCloud\\' =>
array (
0 => __DIR__ . '/..' . '/tencentcloud/common/src/TencentCloud',
+53
View File
@@ -929,5 +929,58 @@
],
"description": "TencentCloudApi php sdk ocr",
"homepage": "https://github.com/tencentcloud-sdk-php/ocr"
},
{
"name": "wechatpay/wechatpay-guzzle-middleware",
"version": "0.2.2",
"version_normalized": "0.2.2.0",
"source": {
"type": "git",
"url": "https://github.com/wechatpay-apiv3/wechatpay-guzzle-middleware.git",
"reference": "6782ac33ed8cf97628609a71cdc5e84a6a40677a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wechatpay-apiv3/wechatpay-guzzle-middleware/zipball/6782ac33ed8cf97628609a71cdc5e84a6a40677a",
"reference": "6782ac33ed8cf97628609a71cdc5e84a6a40677a",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-openssl": "*",
"php": ">=5.5"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.3"
},
"suggest": {
"ext-bcmath": "Require bcmath in php 5.* version.",
"guzzlehttp/guzzle": "For using wechatpay guzzle middleware."
},
"time": "2021-03-05T03:09:29+00:00",
"bin": [
"tool/CertificateDownloader.php"
],
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"WechatPay\\GuzzleMiddleware\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"description": "WechatPay API V3 Guzzle Middleware",
"homepage": "https://wechatpay-api.gitbook.io/wechatpay-api-v3/",
"keywords": [
"wechatpay"
]
}
]
@@ -0,0 +1,9 @@
# ref: https://github.com/github/gitignore/blob/master/Composer.gitignore
composer.phar
/vendor/
test/
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
composer.lock
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,262 @@
# wechatpay-guzzle-middleware
## 概览
[微信支付API v3](https://wechatpay-api.gitbook.io/wechatpay-api-v3/)的[Guzzle HttpClient](http://docs.guzzlephp.org/)中间件Middleware,实现了请求签名的生成和应答签名的验证。
如果你是使用Guzzle的商户开发者,可以在构造`GuzzleHttp\Client`时将`WechatPayGuzzleMiddleware`传入,得到的`GuzzleHttp\Client`实例在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。
## 项目状态
当前版本为`0.2.0`测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
## 环境要求
我们开发和测试使用的环境如下:
+ PHP 5.5+ / PHP 7.0+
+ guzzlehttp/guzzle 6.0+
## 安装
可以使用PHP包管理工具composer引入SDK到项目中:
#### Composer
方式一:在项目目录中,通过composer命令行添加:
```shell
composer require wechatpay/wechatpay-guzzle-middleware
```
方式二:在项目的composer.json中加入以下配置:
```json
"require": {
"wechatpay/wechatpay-guzzle-middleware": "^0.2.0"
}
```
添加配置后,执行安装
```shell
composer install
```
## 开始
首先,通过`WechatPayMiddlewareBuilder`构建一个`WechatPayMiddleware`,然后将其加入`GuzzleHttp\Client``HandlerStack`中。我们提供相应的方法,可以方便的传入商户私钥和微信支付平台证书等信息。
```php
use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
// 商户相关配置
$merchantId = '1000100'; // 商户号
$merchantSerialNumber = 'XXXXXXXXXX'; // 商户API证书序列号
$merchantPrivateKey = PemUtil::loadPrivateKey('/path/to/mch/private/key.pem'); // 商户私钥
// 微信支付平台配置
$wechatpayCertificate = PemUtil::loadCertificate('/path/to/wechatpay/cert.pem'); // 微信支付平台证书
// 构造一个WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array
->build();
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = GuzzleHttp\HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
// 创建Guzzle HTTP Client时,将HandlerStack传入
$client = new GuzzleHttp\Client(['handler' => $stack]);
// 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
try {
$resp = $client->request('GET', 'https://api.mch.weixin.qq.com/v3/...', [ // 注意替换为实际URL
'headers' => [ 'Accept' => 'application/json' ]
]);
echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
echo $resp->getBody()."\n";
$resp = $client->request('POST', 'https://api.mch.weixin.qq.com/v3/...', [
'json' => [ // JSON请求体
'field1' => 'value1',
'field2' => 'value2'
],
'headers' => [ 'Accept' => 'application/json' ]
]);
echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
echo $resp->getBody()."\n";
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
echo $e->getResponse()->getBody();
}
return;
}
```
### 上传媒体文件
```php
// 参考上述指引说明,并引入 `MediaUtil` 正常初始化,无额外条件
use WechatPay\GuzzleMiddleware\Util\MediaUtil;
// 实例化一个媒体文件流,注意文件后缀名需符合接口要求
$media = new MediaUtil('/your/file/path/with.extension');
// 正常使用Guzzle发起API请求
try {
$resp = $client->request('POST', 'https://api.mch.weixin.qq.com/v3/[merchant/media/video_upload|marketing/favor/media/image-upload]', [
'body' => $media->getStream(),
'headers' => [
'Accept' => 'application/json',
'content-type' => $media->getContentType(),
]
]);
// POST 语法糖
$resp = $client->post('merchant/media/upload', [
'body' => $media->getStream(),
'headers' => [
'Accept' => 'application/json',
'content-type' => $media->getContentType(),
]
]);
echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
echo $resp->getBody()."\n";
} catch (Exception $e) {
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
echo $e->getResponse()->getBody();
}
return;
}
```
### 敏感信息加/解密
```php
// 参考上上述说明,引入 `SensitiveInfoCrypto`
use WechatPay\GuzzleMiddleware\Util\SensitiveInfoCrypto;
// 上行加密API 多于 下行解密,默认为加密,实例后直接当方法用即可
$encryptor = new SensitiveInfoCrypto(PemUtil::loadCertificate('/path/to/wechatpay/cert.pem'));
// 正常使用Guzzle发起API请求
try {
// POST 语法糖
$resp = $client->post('/v3/applyment4sub/applyment/', [
'json' => [
'business_code' => 'APL_98761234',
'contact_info' => [
'contact_name' => $encryptor('value of `contact_name`'),
'contact_id_number' => $encryptor('value of `contact_id_number'),
'mobile_phone' => $encryptor('value of `mobile_phone`'),
'contact_email' => $encryptor('value of `contact_email`'),
],
//...
],
'headers' => [
// 命令行获取证书序列号
// openssl x509 -in /path/to/wechatpay/cert.pem -noout -serial | awk -F= '{print $2}'
// 或者使用工具类获取证书序列号 `PemUtil::parseCertificateSerialNo($certificate)`
'Wechatpay-Serial' => 'must be the serial number via the downloaded pem file of `/v3/certificates`',
'Accept' => 'application/json',
],
]);
echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
echo $resp->getBody()."\n";
} catch (Exception $e) {
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
echo $e->getResponse()->getBody();
}
return;
}
// 单例加解密示例如下
$crypto = new SensitiveInfoCrypto($wechatpayCertificate, $merchantPrivateKey);
$encrypted = $crypto('Alice');
$decrypted = $crypto->setStage('decrypt')($encrypted);
```
## 定制
当默认的本地签名和验签方式不适合你的系统时,你可以通过实现`Signer`或者`Verifier`来定制签名和验签。比如,你的系统把商户私钥集中存储,业务系统需通过远程调用进行签名,你可以这样做。
```php
use WechatPay\GuzzleMiddleware\Auth\Signer;
use WechatPay\GuzzleMiddleware\Auth\SignatureResult;
use WechatPay\GuzzleMiddleware\Auth\WechatPay2Credentials;
class CustomSigner implements Signer
{
public function sign($message)
{
// 调用签名RPC服务,然后返回包含签名和证书序列号的SignatureResult
return new SignatureResult('xxxx', 'yyyyy');
}
}
$credentials = new WechatPay2Credentials($merchantId, new CustomSigner);
$wechatpayMiddleware = WechatPayMiddleware::builder()
->withCredentials($credentials)
->withWechatPay([ $wechatpayCertificate ])
->build();
```
## 常见问题
### 如何下载平台证书?
使用`WechatPayMiddlewareBuilder`需要调用`withWechatpay`设置[微信支付平台证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu),而平台证书又只能通过调用[获取平台证书接口](https://wechatpay-api.gitbook.io/wechatpay-api-v3/jie-kou-wen-dang/ping-tai-zheng-shu#huo-qu-ping-tai-zheng-shu-lie-biao)下载。为了解开"死循环",你可以在第一次下载平台证书时,按照下述方法临时"跳过”应答签名的验证。
```php
use WechatPay\GuzzleMiddleware\Validator;
class NoopValidator implements Validator
{
public function validate(\Psr\Http\Message\ResponseInterface $response)
{
return true;
}
}
$wechatpayMiddleware = WechatPayMiddleware::builder()
->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey)
->withValidator(new NoopValidator) // NOTE: 设置一个空的应答签名验证器,**不要**用在业务请求
->build();
```
**注意**:业务请求请使用标准的初始化流程,务必验证应答签名。
### 证书和回调解密需要的AesGcm解密在哪里?
请参考[AesUtil.php](src/Util/AesUtil.php)。
### 配合swoole使用时,上传文件接口报错
建议升级至swoole 4.6+swoole在 4.6.0 中增加了native-curl([swoole/swoole-src#3863](https://github.com/swoole/swoole-src/pull/3863))支持,我们测试能正常使用了。
更详细的信息,请参考[#36](https://github.com/wechatpay-apiv3/wechatpay-guzzle-middleware/issues/36)。
## 联系我们
如果你发现了**BUG**或者有任何疑问、建议,请通过issue进行反馈。
也欢迎访问我们的[开发者社区](https://developers.weixin.qq.com/community/pay)。
@@ -0,0 +1,28 @@
{
"name": "wechatpay/wechatpay-guzzle-middleware",
"version": "0.2.2",
"description": "WechatPay API V3 Guzzle Middleware",
"type": "library",
"keywords": [
"wechatpay"
],
"homepage": "https://wechatpay-api.gitbook.io/wechatpay-api-v3/",
"license": "Apache-2.0",
"require": {
"php": ">=5.5",
"ext-openssl": "*"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.3"
},
"autoload": {
"psr-4": { "WechatPay\\GuzzleMiddleware\\" : "src/" }
},
"bin": [
"tool/CertificateDownloader.php"
],
"suggest": {
"ext-bcmath": "Require bcmath in php 5.* version.",
"guzzlehttp/guzzle": "For using wechatpay guzzle middleware."
}
}
@@ -0,0 +1,70 @@
<?php
/**
* CertificateVerifier
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Auth;
use WechatPay\GuzzleMiddleware\Auth\Verifier;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
/**
* CertificateVerifier
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
class CertificateVerifier implements Verifier
{
/**
* WechatPay Certificates Public Keys
*
* @var array (serialNo => publicKey)
*/
protected $publicKeys = [];
/**
* Constructor
*
* @param array of string|resource $certifcates WechatPay Certificates (string - PEM formatted \
* certificate, or resource - X.509 certificate resource returned by openssl_x509_read)
*/
public function __construct(array $certificates)
{
foreach ($certificates as $certificate) {
$serialNo = PemUtil::parseCertificateSerialNo($certificate);
$this->publicKeys[$serialNo] = \openssl_get_publickey($certificate);
}
}
/**
* Verify signature of message
*
* @param string $serialNumber certificate serial number
* @param string $message message to verify
* @param string $signautre signature of message
*
* @return bool
*/
public function verify($serialNumber, $message, $signature)
{
$serialNumber = \strtoupper(\ltrim($serialNumber, '0')); // trim leading 0 and uppercase
if (!isset($this->publicKeys[$serialNumber])) {
return false;
}
if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {
throw new \RuntimeException("当前PHP环境不支持SHA256withRSA");
}
$signature = \base64_decode($signature);
return \openssl_verify($message, $signature, $this->publicKeys[$serialNumber],
'sha256WithRSAEncryption');
}
}
@@ -0,0 +1,72 @@
<?php
/**
* PrivateKeySigner
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Auth;
use WechatPay\GuzzleMiddleware\Auth\Signer;
use WechatPay\GuzzleMiddleware\Auth\SignatureResult;
/**
* PrivateKeySigner
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
class PrivateKeySigner implements Signer
{
/**
* Certificate Serial Number
*
* @var string
*/
protected $certificateSerialNumber;
/**
* Merchant certificate private key
*
* @var string|resource
*/
protected $privateKey;
/**
* Constructor
*
* @param string $serialNo Merchant Certificate Serial Number
* @param string|resource $privateKey Merchant Certificate Private Key \
* (string - PEM formatted key, or resource - key returned by openssl_get_privatekey)
*/
public function __construct($serialNumber, $privateKey)
{
$this->certificateSerialNumber = $serialNumber;
$this->privateKey = $privateKey;
}
/**
* Sign Message
*
* @param string $message Message to sign
*
* @return string
*/
public function sign($message)
{
if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {
throw new \RuntimeException("当前PHP环境不支持SHA256withRSA");
}
if (!\openssl_sign($message, $sign, $this->privateKey, 'sha256WithRSAEncryption')) {
throw new \UnexpectedValueException("签名验证过程发生了错误");
}
return new SignatureResult(\base64_encode($sign), $this->certificateSerialNumber);
}
}
@@ -0,0 +1,64 @@
<?php
/**
* SignatureResult
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Auth;
/**
* SignatureResult
*
* @package WechatPay
* @author WeChat Pay Team
*/
class SignatureResult
{
/**
* Signature
*
* @var string
*/
public $sign;
/**
* Certificate Serial Number
*
* @var string
*/
public $certificateSerialNumber;
/**
* Constructor
*/
public function __construct($sign, $serialNumber)
{
$this->sign = $sign;
$this->certificateSerialNumber = $serialNumber;
}
/**
* Get Signature
*
* @return string
*/
public function getSign()
{
return $this->sign;
}
/**
* Get Certificate Serial Number
*
* @return string
*/
public function getCertificateSerialNumber()
{
return $this->certificateSerialNumber;
}
}
@@ -0,0 +1,33 @@
<?php
/**
* Signer
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Auth;
use WechatPay\GuzzleMiddleware\Auth\SignatureResult;
/**
* Interface abstracting Signer.
*
* @package WechatPay
* @author WeChat Pay Team
*/
interface Signer
{
/**
* Sign Message
*
* @param string $message Message to sign
*
* @return SignatureResult
*/
public function sign($message);
}
@@ -0,0 +1,33 @@
<?php
/**
* Verifier
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Auth;
/**
* Interface abstracting Verifier.
*
* @package WechatPay
* @author WeChat Pay Team
*/
interface Verifier
{
/**
* Verify signature of message
*
* @param string $serialNumber certificate serial number
* @param string $message message to verify
* @param string $signautre signature of message
*
* @return bool
*/
public function verify($serialNumber, $message, $signature);
}
@@ -0,0 +1,147 @@
<?php
/**
* WechatPay2Credentials
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Auth;
use Psr\Http\Message\RequestInterface;
use WechatPay\GuzzleMiddleware\Credentials;
use WechatPay\GuzzleMiddleware\Auth\Signer;
/**
* WechatPay2Credentials
*
* @category Class
* @package WechatPay\GuzzleMiddleware\Auth
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
class WechatPay2Credentials implements Credentials
{
/**
* Merchant Id
*
* @var string
*/
protected $merchantId;
/**
* signer
*
* @var Signer
*/
protected $signer;
/**
* Constructor
*/
public function __construct($merchantId, Signer $signer)
{
$this->merchantId = $merchantId;
$this->signer = $signer;
}
/**
* Get schema of credentials
*
* @return string
*/
public function getSchema()
{
return 'WECHATPAY2-SHA256-RSA2048';
}
/**
* Get token of credentials
*
* @param RequestInterface $request Api request
*
* @return string
*/
public function getToken(RequestInterface $request)
{
$nonce = $this->getNonce();
$timestamp = $this->getTimestamp();
$message = $this->buildMessage($nonce, $timestamp, $request);
$signResult = $this->signer->sign($message);
$sign = $signResult->getSign();
$serialNo = $signResult->getCertificateSerialNumber();
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$this->merchantId, $nonce, $timestamp, $serialNo, $sign
);
return $token;
}
/**
* Get Merchant Id
*
* @return string
*/
public function getMerchantId()
{
return $this->merchantId;
}
/**
* Get sign timestamp
*
* @return integer
*/
protected function getTimestamp()
{
return \time();
}
/**
* Get nonce
*
* @return string
*/
protected function getNonce()
{
static $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < 32; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
/**
* Build message to sign
*
* @param string $nonce Nonce string
* @param integer $timestamp Unix timestamp
* @param RequestInterface $request Api request
*
* @return string
*/
protected function buildMessage($nonce, $timestamp, RequestInterface $request)
{
$body = '';
$bodyStream = $request->getBody();
// non-seekable stream need to be handled by the caller
if ($bodyStream->isSeekable()) {
$body = (string)$bodyStream;
$bodyStream->rewind();
}
return $request->getMethod()."\n".
$request->getRequestTarget()."\n".
$timestamp."\n".
$nonce."\n".
$body."\n";
}
}
@@ -0,0 +1,120 @@
<?php
/**
* WechatPay2Validator
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Auth;
use Psr\Http\Message\ResponseInterface;
use WechatPay\GuzzleMiddleware\Validator;
use WechatPay\GuzzleMiddleware\Auth\Verifier;
/**
* WechatPay2Validator
*
* @category Class
* @package WechatPay\GuzzleMiddleware\Auth
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
class WechatPay2Validator implements Validator
{
/**
* sign verifier
*
* @var Verifier
*/
protected $verifier;
/**
* Constructor
*/
public function __construct(Verifier $verifier)
{
$this->verifier = $verifier;
}
/**
* Validate Response
*
* @param ResponseInterface $response Api response to validate
*
* @return bool
*/
public function validate(ResponseInterface $response)
{
$serialNo = $this->getHeader($response, 'Wechatpay-Serial');
$sign = $this->getHeader($response, 'Wechatpay-Signature');
$timestamp = $this->getHeader($response, 'Wechatpay-TimeStamp');
$nonce = $this->getHeader($response, 'Wechatpay-Nonce');
if (!isset($serialNo, $sign, $timestamp, $nonce)) {
return false;
}
if (!$this->checkTimestamp($timestamp)) {
// log here
return false;
}
$body = $this->getBody($response);
$message = "$timestamp\n$nonce\n$body\n";
return $this->verifier->verify($serialNo, $message, $sign);
}
/**
* Build message to sign
*
* @param string $nonce Nonce string
* @param integer $timestamp Unix timestamp
* @param RequestInterface $request Api request
*
* @return string
*/
protected function getHeader(ResponseInterface $response, $name)
{
$values = $response->getHeader($name);
return empty($values) ? null : $values[count($values) - 1];
}
/**
* Check whether timestamp is valid
*
* @param integer $timestamp Unix timestamp
*
* @return bool
*/
protected function checkTimestamp($timestamp)
{
// reject responses beyond 5 minutes
return \abs((int)$timestamp - \time()) <= 300;
}
/**
* Build message to sign
*
* @param string $nonce Nonce string
* @param integer $timestamp Unix timestamp
* @param RequestInterface $request Api request
*
* @return string
*/
protected function getBody(ResponseInterface $response)
{
$body = '';
$bodyStream = $response->getBody();
// non-seekable stream need to be handled by the caller
if ($bodyStream->isSeekable()) {
$body = (string)$bodyStream;
$bodyStream->rewind();
}
return $body;
}
}
@@ -0,0 +1,39 @@
<?php
/**
* Credentials
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware;
use Psr\Http\Message\RequestInterface;
/**
* Interface abstracting Credentials.
*
* @package WechatPay
* @author WeChat Pay Team
*/
interface Credentials
{
/**
* Get schema of credentials
*
* @return string
*/
public function getSchema();
/**
* Get token of credentials
*
* @param RequestInterface $request Api request
*
* @return string
*/
public function getToken(RequestInterface $request);
}
@@ -0,0 +1,85 @@
<?php
/**
* AesUtil
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChatPay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Util;
/**
* Util for AEAD_AES_256_GCM.
*
* @package WechatPay
* @author WeChatPay Team
*/
class AesUtil
{
/**
* AES key
*
* @var string
*/
private $aesKey;
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* Constructor
*
* @param string $aesKey API V3 Key
*
*/
public function __construct($aesKey)
{
if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
}
$this->aesKey = $aesKey;
}
/**
* Decrypt AEAD_AES_256_GCM ciphertext
*
* @param string $associatedData AES GCM additional authentication data
* @param string $nonceStr AES GCM nonce
* @param string $ciphertext AES GCM cipher text
*
* @return string|bool Decrypted string on success or FALSE on failure
*/
public function decryptToString($associatedData, $nonceStr, $ciphertext)
{
$ciphertext = \base64_decode($ciphertext);
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
return false;
}
// ext-sodium (default installed on >= PHP 7.2)
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
\sodium_crypto_aead_aes256gcm_is_available()) {
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// ext-libsodium (need install libsodium-php 1.x via pecl)
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
\Sodium\crypto_aead_aes256gcm_is_available()) {
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// openssl (PHP >= 7.1 support AEAD)
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
$ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
$authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
}
@@ -0,0 +1,146 @@
<?php
/**
* MediaUtil
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChatPay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Util;
use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\Psr7\MultipartStream;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\CachingStream;
/**
* Util for Media(image or video) uploading.
*
* @package WechatPay
*/
class MediaUtil {
/**
* local file path
*
* @var string
*/
private $filepath;
/**
* file content stream to upload
* @var string
*/
private $fileStream;
/**
* upload meta json
*
* @var string
*/
private $json;
/**
* upload contents stream
*
* @var MultipartStream
*/
private $multipart;
/**
* multipart stream wrapper
*
* @var FnStream
*/
private $stream;
/**
* Constructor
*
* @param string $filepath The media file path or file name,
* should be one of the
* images(jpg|bmp|png)
* or
* video(avi|wmv|mpeg|mp4|mov|mkv|flv|f4v|m4v|rmvb)
* @param StreamInterface $fileStream File content stream, optional
*/
public function __construct($filepath, $fileStream = null)
{
$this->filepath = $filepath;
$this->fileStream = $fileStream;
$this->composeStream();
}
/**
* Compose the GuzzleHttp\Psr7\FnStream
*/
private function composeStream()
{
$basename = \basename($this->filepath);
$stream = isset($this->fileStream) ? $this->fileStream : new LazyOpenStream($this->filepath, 'r');
if (!$stream->isSeekable()) {
$stream = new CachingStream($stream);
}
$json = \GuzzleHttp\json_encode([
'filename' => $basename,
'sha256' => \GuzzleHttp\Psr7\hash($stream, 'sha256'),
]);
$this->meta = $json;
$multipart = new MultipartStream([
[
'name' => 'meta',
'contents' => $json,
'headers' => [
'Content-Type' => 'application/json',
],
],
[
'name' => 'file',
'filename' => $basename,
'contents' => $stream,
],
]);
$this->multipart = $multipart;
$this->stream = FnStream::decorate($multipart, [
// for signature
'__toString' => function () use ($json) {
return $json;
},
// let the `CURL` to use `CURLOPT_UPLOAD` context
'getSize' => function () {
return null;
},
]);
}
/**
* Get the `meta` of the multipart data string
*/
public function getMeta()
{
return $this->meta;
}
/**
* Get the `GuzzleHttp\Psr7\FnStream` context
*/
public function getStream()
{
return $this->stream;
}
/**
* Get the `Content-Type` of the `GuzzleHttp\Psr7\MultipartStream`
*/
public function getContentType()
{
return 'multipart/form-data; boundary=' . $this->multipart->getBoundary();
}
}
@@ -0,0 +1,108 @@
<?php
/**
* AuthUtil
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChatPay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Util;
/**
* Util for read private key and certificate.
*
* @package WechatPay
* @author WeChatPay Team
*/
class PemUtil
{
/**
* Read private key from file
*
* @param string $filepath PEM encoded private key file path
*
* @return resource|bool Private key resource identifier on success, or FALSE on error
*/
public static function loadPrivateKey($filepath)
{
return \openssl_get_privatekey(\file_get_contents($filepath));
}
/**
* Read certificate from file
*
* @param string $filepath PEM encoded X.509 certificate file path
*
* @return resource|bool X.509 certificate resource identifier on success or FALSE on failure
*/
public static function loadCertificate($filepath)
{
return \openssl_x509_read(\file_get_contents($filepath));
}
/**
* Read private key from string
*
* @param string $content PEM encoded private key string content
*
* @return resource|bool Private key resource identifier on success, or FALSE on error
*/
public static function loadPrivateKeyFromString($content)
{
return \openssl_get_privatekey($content);
}
/**
* Read certificate from string
*
* @param string $content PEM encoded X.509 certificate string content
*
* @return resource|bool X.509 certificate resource identifier on success or FALSE on failure
*/
public static function loadCertificateFromString($content)
{
return \openssl_x509_read($content);
}
/**
* Parse Serial Number from Certificate
*
* @param string|resource $certifcates Certificates (string - PEM formatted certificate, \
* or resource - X.509 certificate resource returned by loadCertificate or openssl_x509_read)
*
* @return string
*/
public static function parseCertificateSerialNo($certificate)
{
$info = \openssl_x509_parse($certificate);
if (!isset($info['serialNumber']) && !isset($info['serialNumberHex'])) {
throw new \InvalidArgumentException('证书格式错误');
}
$serialNo = '';
// PHP 7.0+ provides serialNumberHex field
if (isset($info['serialNumberHex'])) {
$serialNo = $info['serialNumberHex'];
} else {
// PHP use i2s_ASN1_INTEGER in openssl to convert serial number to string,
// i2s_ASN1_INTEGER may produce decimal or hexadecimal format,
// depending on the version of openssl and length of data.
if (\strtolower(\substr($info['serialNumber'], 0, 2)) == '0x') { // HEX format
$serialNo = \substr($info['serialNumber'], 2);
} else { // DEC format
$value = $info['serialNumber'];
$hexvalues = ['0','1','2','3','4','5','6','7',
'8','9','A','B','C','D','E','F'];
while ($value != '0') {
$serialNo = $hexvalues[\bcmod($value, '16')].$serialNo;
$value = \bcdiv($value, '16', 0);
}
}
}
return \strtoupper($serialNo);
}
}
@@ -0,0 +1,161 @@
<?php
/**
* SensitiveInfoCrypto
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChatPay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware\Util;
/**
* Encrypt/Decrypt the sensitive information by the certificates pair.
*
* <code>
* // Encrypt usage:
* $encryptor = new SensitiveInfoCrypto(
* PemUtil::loadCertificate('/downloaded/pubcert.pem')
* );
* $json = json_encode(['name' => $encryptor('Alice')]);
* // That's simple!
*
* // Decrypt usage:
* $decryptor = new SensitiveInfoCrypto(
* null,
* PemUtil::loadPrivateKey('/merchant/key.pem')
* );
* $decrypted = $decryptor->setStage('decrypt')(
* 'base64 encoding message was given by the payment plat'
* );
* // That's simple too!
*
* // Working both Encrypt and Decrypt usages:
* $crypto = new SensitiveInfoCrypto(
* PemUtil::loadCertificate('/merchant/cert.pem'),
* PemUtil::loadPrivateKey('/merchant/key.pem')
* );
* $encrypted = $crypto('Carol');
* $decrypted = $crypto->setStage('decrypt')($encrypted);
* // Having fun with this!
* </code>
*
* @package WechatPay
*/
class SensitiveInfoCrypto implements \JsonSerializable {
/**
* @var resource|null $publicCert The public certificate
*/
private $publicCert;
/**
* @var resource|null $privateKey The private key
*/
private $privateKey;
/**
* @var string $message The encryped or decrypted content
*/
private $message = '';
/**
* @var string $stage The crypto working scenario, default is `encrypt`.
* Mention here: while toggle the scenario,
* the next stage is the previous one.
*/
private $stage = 'encrypt';
/**
* @var array $scenarios Methods that allowed.
*/
private static $scenarios = ['encrypt', 'decrypt'];
/**
* Constructor
*
* @param resource|null $publicCert The public certificate resource
* @param resource|null $privateKey The private key resource
*/
public function __construct($publicCert, $privateKey = null) {
$this->publicCert = $publicCert;
$this->privateKey = $privateKey;
}
/**
* Encrypt the string by the public certificate
*
* @param string $str The content shall be encrypted
*
* @return SensitiveInfoCrypto
*/
private function encrypt($str) {
if (!is_resource($this->publicCert)) {
throw new \InvalidArgumentException('The publicCert must be resource.');
}
openssl_public_encrypt($str, $encrypted,
$this->publicCert, \OPENSSL_PKCS1_OAEP_PADDING);
$this->message = \base64_encode($encrypted);
return $this;
}
/**
* Decrypt the string by the private key certificate
*
* @param string $str The content shall be decrypted
*
* @return SensitiveInfoCrypto
*/
private function decrypt($str) {
if (!is_resource($this->privateKey)) {
throw new \InvalidArgumentException('The privateKey must be resource.');
}
openssl_private_decrypt(\base64_decode($str), $decrypted,
$this->privateKey, \OPENSSL_PKCS1_OAEP_PADDING);
$this->message = $decrypted;
return $this;
}
/**
* Specify data which should be
*
* @return string
*/
public function jsonSerialize() {
return $this->message;
}
/**
* Toggle the crypto instance onto `encrypt` or `decrypt` stage
*
* @param string $scenario Should be `encrypt` or `decrypt`
*
* @throws \InvalidArgumentException if the scenario is invalid.
*
* @return SensitiveInfoCrypto
*/
public function setStage($scenario) {
if (!in_array($scenario, self::$scenarios)) {
throw new \InvalidArgumentException(sprintf(
'Cannot setStage `%s`, here is only allowed one of the %s.',
$scenario,
\implode(', ', self::$scenarios)
));
}
$this->stage = $scenario;
return $this;
}
public function __invoke($str) {
$copy = clone $this;
return $copy->{$this->stage}($str);
}
public function __toString() {
return $this->jsonSerialize();
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Validator
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware;
use Psr\Http\Message\ResponseInterface;
/**
* Interface abstracting Validator.
*
* @package WechatPay
* @author WeChat Pay Team
*/
interface Validator
{
/**
* Validate Response
*
* @param ResponseInterface $response Api response to validate
*
* @return bool
*/
public function validate(ResponseInterface $response);
}
@@ -0,0 +1,190 @@
<?php
/**
* WechatPayMiddleware
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use WechatPay\GuzzleMiddleware\Credentials;
use WechatPay\GuzzleMiddleware\Validator;
use WechatPay\GuzzleMiddleware\WechatPayMiddlewareBuilder;
/**
* WechatPayMiddleware
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
class WechatPayMiddleware
{
/**
* WechatPayMiddleware version
*
* @var string
*/
const VERSION = "0.2.0";
/**
* WechatPay API domain
*
* @var string
*/
protected static $API_DOMAINS = [
'api.mch.weixin.qq.com',
'api2.mch.weixin.qq.com',
'apius.mch.weixin.qq.com',
'apihk.mch.weixin.qq.com'
];
/**
* WechatPay API base urls
*
* @var array of string
*/
protected static $BASE_URLS = [
'/v3/',
'/sandbox/v3/',
'/hk/v3/'
];
/**
* Merchant credentials
*
* @var Credentials
*/
protected $credentials;
/**
* Response Validator
*
* @var Validator
*/
protected $validator;
/**
* Constructor
*/
public function __construct(Credentials $credentials, Validator $validator)
{
$this->credentials = $credentials;
$this->validator = $validator;
}
/**
* Use as Guzzle middleware
*
* @param callable $handler
*
* @return callable
*/
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
if (!self::isWechatPayApiUrl($request->getUri())) {
return $handler($request, $options);
}
if (!$request->getBody()->isSeekable() && \class_exists("\\GuzzleHttp\\Psr7\\CachingStream")) {
$request = $request->withBody(new \GuzzleHttp\Psr7\CachingStream($request->getBody()));
}
$schema = $this->credentials->getSchema();
$token = $this->credentials->getToken($request);
$request = $request->withHeader("Authorization", $schema.' '.$token);
if (self::isUserAgentOverwritable($request)) {
$request = $request->withHeader('User-Agent', self::getUserAgent());
}
return $handler($request, $options)->then(
function (ResponseInterface $response) use ($request) {
$code = $response->getStatusCode();
if ($code >= 200 && $code < 300) {
if (!$response->getBody()->isSeekable() && \class_exists("\\GuzzleHttp\\Psr7\\CachingStream")) {
$response = $response->withBody(new \GuzzleHttp\Psr7\CachingStream($response->getBody()));
}
if (!$this->validator->validate($response)) {
if (\class_exists('\\GuzzleHttp\\Exception\\ServerException')) {
throw new \GuzzleHttp\Exception\ServerException(
"应答的微信支付签名验证失败", $request, $response);
} else {
throw new \RuntimeException("应答的微信支付签名验证失败", $code);
}
}
}
return $response;
}
);
};
}
/**
* Create a new builder
*
* @return WechatPayMiddlewareBuilder
*/
public static function builder()
{
return new WechatPayMiddlewareBuilder();
}
/**
* Check whether url is WechatPay API V3 url
*/
protected static function isWechatPayApiUrl(UriInterface $url)
{
if ($url->getScheme() !== 'https' || !\in_array($url->getHost(), self::$API_DOMAINS)) {
return false;
}
foreach (self::$BASE_URLS as $baseUrl) {
if (\substr($url->getPath(), 0, strlen($baseUrl)) === $baseUrl) {
return true;
}
}
return false;
}
/**
* Get User Agent
* @return string
*/
protected static function getUserAgent()
{
static $userAgent = '';
if (!$userAgent) {
$agent = 'WechatPay-Guzzle/'.self::VERSION;
if (\class_exists('\\GuzzleHttp\\Client')) {
$version = defined('\\GuzzleHttp\\Client::VERSION') ? \GuzzleHttp\Client::VERSION
: \GuzzleHttp\Client::MAJOR_VERSION;
$agent .= ' GuzzleHttp/'.$version;
}
if (extension_loaded('curl') && function_exists('curl_version')) {
$agent .= ' curl/'.\curl_version()['version'];
}
$agent .= \sprintf(" (%s/%s) PHP/%s", PHP_OS, \php_uname('r'), PHP_VERSION);
$userAgent = $agent;
}
return $userAgent;
}
private static function isUserAgentOverwritable(RequestInterface $request)
{
if (!$request->hasHeader('User-Agent')) {
return true;
}
$headers = $request->getHeader('User-Agent');
$userAgent = $headers[\count($headers) - 1];
if (\function_exists('\\GuzzleHttp\\default_user_agent')) {
return $userAgent === \GuzzleHttp\default_user_agent();
}
return false;
}
}
@@ -0,0 +1,125 @@
<?php
/**
* WechatPayMiddlewareBuilder
* PHP version 5
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
namespace WechatPay\GuzzleMiddleware;
use WechatPay\GuzzleMiddleware\Credentials;
use WechatPay\GuzzleMiddleware\Validator;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Auth\PrivateKeySigner;
use WechatPay\GuzzleMiddleware\Auth\CertificateVerifier;
use WechatPay\GuzzleMiddleware\Auth\WechatPay2Credentials;
use WechatPay\GuzzleMiddleware\Auth\WechatPay2Validator;
/**
* WechatPayMiddlewareBuilder
*
* @category Class
* @package WechatPay
* @author WeChat Pay Team
* @link https://pay.weixin.qq.com
*/
class WechatPayMiddlewareBuilder
{
/**
* Merchant credentials
*
* @var Credentials
*/
protected $credentials;
/**
* Response Validator
*
* @var Validator
*/
protected $validator;
/**
* Constructor
*/
public function __construct()
{
}
/**
* Set Merchant Infomation
*
* @param string $merchantId Merchant Id
* @param string $serialNo Merchant Certificate Serial Number
* @param string|resource $privateKey Merchant Certificate Private Key (string - PEM formatted key, or resource - key returned by openssl_get_privatekey)
*
* @return $this
*/
public function withMerchant($merchantId, $serialNo, $privateKey)
{
$this->credentials = new WechatPay2Credentials($merchantId,
new PrivateKeySigner($serialNo, $privateKey));
return $this;
}
/**
* Set Merchant Credentials
*
* @param Credentials $credentials Merchant Certificate Credentials
*
* @return $this
*/
public function withCredentials(Credentials $credentials)
{
$this->credentials = $credentials;
return $this;
}
/**
* Set WechatPay Certificates Infomation
*
* @param array of string|resource $certifcates WechatPay Certificates (string - PEM formatted \
* certificate, or resource - X.509 certificate resource returned by openssl_x509_read)
*
* @return $this
*/
public function withWechatPay(array $certificates)
{
$this->validator = new WechatPay2Validator(new CertificateVerifier($certificates));
return $this;
}
/**
* Set WechatPay Validator
*
* @param Validator $Validator WechatPay Validator
*
* @return $this
*/
public function withValidator(Validator $validator)
{
$this->validator = $validator;
return $this;
}
/**
* Build WechatPayMiddleware
*
* @return WechatPayMiddleware
*/
public function build()
{
if (!isset($this->credentials)) {
throw new \InvalidArgumentException('商户认证信息(credentials)未设置');
}
if (!isset($this->validator)) {
throw new \InvalidArgumentException('微信支付平台签名验证(validator)未设置');
}
return new WechatPayMiddleware($this->credentials, $this->validator);
}
}
@@ -0,0 +1,218 @@
#!/usr/bin/env php
<?php
// load autoload.php
$possibleFiles = [__DIR__.'/../vendor/autoload.php', __DIR__.'/../../../autoload.php', __DIR__.'/../../autoload.php'];
$file = null;
foreach ($possibleFiles as $possibleFile) {
if (file_exists($possibleFile)) {
$file = $possibleFile;
break;
}
}
if (null === $file) {
throw new RuntimeException('Unable to locate autoload.php file.');
}
require_once $file;
unset($possibleFiles, $possibleFile, $file);
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Validator;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use WechatPay\GuzzleMiddleware\Util\AesUtil;
use WechatPay\GuzzleMiddleware\Auth\CertificateVerifier;
use WechatPay\GuzzleMiddleware\Auth\WechatPay2Validator;
class CertificateDownloader
{
const VERSION = '0.1.0';
public function run()
{
$opts = $this->parseOpts();
if (!$opts) {
$this->printHelp();
exit(1);
}
if (isset($opts['help'])) {
$this->printHelp();
exit(0);
}
if (isset($opts['version'])) {
echo self::VERSION . "\n";
exit(0);
}
$this->downloadCert($opts);
}
private function downloadCert($opts)
{
try {
// 构造一个WechatPayMiddleware
$builder = WechatPayMiddleware::builder()
->withMerchant($opts['mchid'], $opts['serialno'], PemUtil::loadPrivateKey($opts['privatekey'])); // 传入商户相关配置
if (isset($opts['wechatpay-cert'])) {
$builder->withWechatPay([ PemUtil::loadCertificate($opts['wechatpay-cert']) ]); // 使用平台证书验证
}
else {
$builder->withValidator(new NoopValidator); // 临时"跳过”应答签名的验证
}
$wechatpayMiddleware = $builder->build();
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
// 创建Guzzle HTTP Client时,将HandlerStack传入
$client = new GuzzleHttp\Client(['handler' => $stack]);
// 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
$resp = $client->request('GET', 'https://api.mch.weixin.qq.com/v3/certificates', [
'headers' => [ 'Accept' => 'application/json' ]
]);
if ($resp->getStatusCode() < 200 || $resp->getStatusCode() > 299) {
echo "download failed, code={$resp->getStatusCode()}, body=[{$resp->getBody()}]\n";
return;
}
$list = json_decode($resp->getBody(), true);
$plainCerts = [];
$x509Certs = [];
$decrypter = new AesUtil($opts['key']);
foreach ($list['data'] as $item) {
$encCert = $item['encrypt_certificate'];
$plain = $decrypter->decryptToString($encCert['associated_data'],
$encCert['nonce'], $encCert['ciphertext']);
if (!$plain) {
echo "encrypted certificate decrypt fail!\n";
exit(1);
}
// 通过加载对证书进行简单合法性检验
$cert = \openssl_x509_read($plain); // 从字符串中加载证书
if (!$cert) {
echo "downloaded certificate check fail!\n";
exit(1);
}
$plainCerts[] = $plain;
$x509Certs[] = $cert;
}
// 使用下载的证书再来验证一次应答的签名
$validator = new WechatPay2Validator(new CertificateVerifier($x509Certs));
if (!$validator->validate($resp)) {
echo "validate response fail using downloaded certificates!";
exit(1);
}
// 输出证书信息,并保存到文件
foreach ($list['data'] as $index => $item) {
echo "Certificate {\n";
echo " Serial Number: ".$item['serial_no']."\n";
echo " Not Before: ".(new DateTime($item['effective_time']))->format('Y-m-d H:i:s')."\n";
echo " Not After: ".(new DateTime($item['expire_time']))->format('Y-m-d H:i:s')."\n";
echo " Text: \n ".str_replace("\n", "\n ", $plainCerts[$index])."\n";
echo "}\n";
$outpath = $opts['output'].DIRECTORY_SEPARATOR.'wechatpay_'.$item['serial_no'].'.pem';
file_put_contents($outpath, $plainCerts[$index]);
}
}
catch (RequestException $e) {
echo "download failed, message=[{$e->getMessage()}] ";
if ($e->hasResponse()) {
echo "code={$e->getResponse()->getStatusCode()}, body=[{$e->getResponse()->getBody()}]\n";
}
exit(1);
}
catch (Exception $e) {
echo "download failed, message=[{$e->getMessage()}]\n";
echo $e;
exit(1);
}
}
private function parseOpts()
{
$opts = [
[ 'key', 'k', true ],
[ 'mchid', 'm', true ],
[ 'privatekey', 'f', true ],
[ 'serialno', 's', true ],
[ 'output', 'o', true ],
[ 'wechatpay-cert', 'c', false ],
];
$shortopts = 'hV';
$longopts = [ 'help', 'version' ];
foreach ($opts as $opt) {
$shortopts .= $opt[1].':';
$longopts[] = $opt[0].':';
}
$parsed = getopt($shortopts, $longopts);
if (!$parsed) {
return false;
}
$args = [];
foreach ($opts as $opt) {
if (isset($parsed[$opt[0]])) {
$args[$opt[0]] = $parsed[$opt[0]];
}
else if (isset($parsed[$opt[1]])) {
$args[$opt[0]] = $parsed[$opt[1]];
}
else if ($opt[2]) {
return false;
}
}
if (isset($parsed['h']) || isset($parsed['help'])) {
$args['help'] = true;
}
if (isset($parsed['V']) || isset($parsed['version'])) {
$args['version'] = true;
}
return $args;
}
private function printHelp()
{
echo <<<EOD
Usage: 微信支付平台证书下载工具 [-hV] [-c=<wechatpayCertificatePath>]
-f=<privateKeyFilePath> -k=<apiV3key> -m=<merchantId>
-o=<outputFilePath> -s=<serialNo>
-m, --mchid=<merchantId> 商户号
-s, --serialno=<serialNo> 商户证书的序列号
-f, --privatekey=<privateKeyFilePath>
商户的私钥文件
-k, --key=<apiV3key> ApiV3Key
-c, --wechatpay-cert=<wechatpayCertificatePath>
微信支付平台证书,验证签名
-o, --output=<outputFilePath>
下载成功后保存证书的路径
-V, --version Print version information and exit.
-h, --help Show this help message and exit.
EOD;
}
}
class NoopValidator implements Validator
{
public function validate(\Psr\Http\Message\ResponseInterface $response)
{
return true;
}
}
// main
(new CertificateDownloader())->run();
@@ -0,0 +1,45 @@
# Certificate Downloader
Certificate Downloader 是 PHP版 微信支付 APIv3 平台证书的命令行下载工具。该工具可从 `https://api.mch.weixin.qq.com/v3/certificates` 接口获取商户可用证书,并使用 [APIv3 密钥](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/api-v3-mi-yao) 和 AES_256_GCM 算法进行解密,并把解密后证书下载到指定位置。
## 使用
使用方法与 [Java版Certificate Downloader](https://github.com/wechatpay-apiv3/CertificateDownloader) 一致,参数与常见问题请参考[其文档](https://github.com/wechatpay-apiv3/CertificateDownloader/blob/master/README.md)。
```shell
> php tool/CertificateDownloader.php
Usage: 微信支付平台证书下载工具 [-hV] [-c=<wechatpayCertificatePath>]
-f=<privateKeyFilePath> -k=<apiV3key> -m=<merchantId>
-o=<outputFilePath> -s=<serialNo>
-m, --mchid=<merchantId> 商户号
-s, --serialno=<serialNo> 商户证书的序列号
-f, --privatekey=<privateKeyFilePath>
商户的私钥文件
-k, --key=<apiV3key> ApiV3Key
-c, --wechatpay-cert=<wechatpayCertificatePath>
微信支付平台证书,验证签名
-o, --output=<outputFilePath>
下载成功后保存证书的路径
-V, --version Print version information and exit.
-h, --help Show this help message and exit.
```
完整命令示例:
```shell
php tool/CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath} -c ${wechatpayCertificateFilePath}
```
## 常见问题
### 如何保证证书正确
请参见CertificateDownloader文档中[关于如何保证证书正确的说明](https://github.com/wechatpay-apiv3/CertificateDownloader#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E8%AF%81%E4%B9%A6%E6%AD%A3%E7%A1%AE)。
### 如何使用信任链验证平台证书
请参见CertificateDownloader文档中[关于如何使用信任链验证平台证书的说明](https://github.com/wechatpay-apiv3/CertificateDownloader#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E4%BF%A1%E4%BB%BB%E9%93%BE%E9%AA%8C%E8%AF%81%E5%B9%B3%E5%8F%B0%E8%AF%81%E4%B9%A6)。
### 第一次下载证书
请参见CertificateDownloader文档中[相关说明](https://github.com/wechatpay-apiv3/CertificateDownloader#%E7%AC%AC%E4%B8%80%E6%AC%A1%E4%B8%8B%E8%BD%BD%E8%AF%81%E4%B9%A6)。
+2 -2
View File
@@ -3,7 +3,7 @@
-- Author:lcc
-- Table:lc_order_purchase
-- ---------------------------
drop table if exists hd_order_purchase;
drop table if exists lc_order_purchase;
create table lc_order_purchase (
id int(10) unsigned not null auto_increment,
app_id int(10) unsigned not null comment '小程序id',
@@ -39,7 +39,7 @@ create table lc_order_purchase (
key idx_user_order (app_id,app_uid,status),
key idx_sid (sid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='消费订单表';
alter table lc_order_purchase add mch_id varchar(30) not null default '' comment '支付商户号' after sid;
-- ----------------------------
-- Title:订单总表
+4
View File
@@ -153,6 +153,7 @@ create table lc_receiver_order_signs (
-- Title:订单表
-- Author:lcc
-- Table:lc_receiver_orders
-- info_json entrust_name 代办人姓名 entrust_idcard 代办人身份证
-- ---------------------------
drop table if exists lc_receiver_orders;
create table lc_receiver_orders (
@@ -180,3 +181,6 @@ create table lc_receiver_orders (
u_time timestamp not null default current_timestamp on update current_timestamp,
primary key (id)
) ENGINE=INNODB DEFAULT CHARSET=UTF8MB4 COLLATE=UTF8MB4_0900_AI_CI COMMENT='订单表';
alter table lc_receiver_orders add pack_id int(10) unsigned not null default 0 comment '服务包id' after incor_id;
alter table lc_receiver_orders add main_type tinyint(1) unsigned not null default 0 comment '购车主体(0个人 1公司)' after admin_id;
alter table lc_receiver_orders add ifentrust tinyint(1) unsigned not null default 0 comment '是否委托代办' after main_type;
+28
View File
@@ -0,0 +1,28 @@
-- ----------------------------
-- Title:服务表
-- Author:lcc
-- Table:lc_receiver_services
-- ---------------------------
create table lc_receiver_services (
id int(10) unsigned not null auto_increment comment '自增id',
title varchar(50) not null default '' comment '服务名称',
field_name varchar(50) not null default '' comment '表名.字段名',
c_time int(10) unsigned not null default '0' comment '创建时间',
u_time timestamp not null default current_timestamp on update current_timestamp,
primary key (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='服务表';
-- ----------------------------
-- Title:服务包表
-- Author:lcc
-- Table:lc_receiver_service_package
-- ---------------------------
create table lc_receiver_service_package (
id int(10) unsigned not null auto_increment comment '自增id',
srv_ids varchar(50) not null default '' comment '服务包组合id',
status tinyint(2) not null default '1' comment '状态(-1删除 0禁用 1正常)',
c_time int(10) unsigned not null default '0' comment '创建时间',
u_time timestamp not null default current_timestamp on update current_timestamp,
primary key (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='服务包表'
+68
View File
@@ -0,0 +1,68 @@
<?php
/**
* 微信v3 推送数据解析 php7.1以上 才能使用
* 官方解密文件地址 https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi#jie-mi
*/
class AesUtil
{
/**
* AES key
*
* @var string
*/
private $aesKey;
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* Constructor
*/
public function __construct($aesKey)
{
if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
}
$this->aesKey = $aesKey;
}
/**
* Decrypt AEAD_AES_256_GCM ciphertext
*
* @param string $associatedData AES GCM additional authentication data
* @param string $nonceStr AES GCM nonce
* @param string $ciphertext AES GCM cipher text
*
* @return string|bool Decrypted string on success or FALSE on failure
*/
public function decryptToString($associatedData, $nonceStr, $ciphertext)
{
$ciphertext = \base64_decode($ciphertext);
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
return false;
}
// ext-sodium (default installed on >= PHP 7.2)
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
\sodium_crypto_aead_aes256gcm_is_available()) {
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// ext-libsodium (need install libsodium-php 1.x via pecl)
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
\Sodium\crypto_aead_aes256gcm_is_available()) {
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// openssl (PHP >= 7.1 support AEAD)
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
$ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
$authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
require_once "AesUtil.php";
$data = isset($_POST) ? $_POST : ''; //原样推送微信数据过来
$resource = isset($data['resource']) ? $data['resource'] : '';
$nonceStr = isset($resource['nonce']) ? $resource['nonce'] : '';
$associatedData = isset($resource['associated_data']) ? $resource['associated_data'] : '';
$ciphertext = isset($resource['ciphertext']) ? $resource['ciphertext'] : '';
$aesKey = '056afb568e28468defe86dd0102f8d33';
$req = ['code'=> 0,'msg'=> ''];
if(!$resource || !$nonceStr || !$associatedData || !$ciphertext){
$req['msg'] = '参数错误';
die(json_encode($req,JSON_UNESCAPED_UNICODE));
}
$AesUtil = new AesUtil($aesKey);
try {
$result = $AesUtil->decryptToString($associatedData,$nonceStr,$ciphertext);
if($result){
$req['code'] = 1;
$req['data'] = json_decode($result);
$req['msg'] = '解密成功';
die(json_encode($req,JSON_UNESCAPED_UNICODE));
}else{
$req['msg'] = '解密失败';
die(json_encode($req,JSON_UNESCAPED_UNICODE));
}
} catch (Exception $e) {
$req['msg'] = $e->getMessage();
die(json_encode($req,JSON_UNESCAPED_UNICODE));
}