配置文件application\config\config.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* 微信分账支付配置
*/
$config['wxpay_profitsharing'] = [
'mch_id'=>'商户号',
'appid'=>'商户APPID',
'AppSecret'=> '密钥',
'key'=>'key',
'sandbox'=> false, //沙盒环境
'notify_url'=> '回调跳地址',
"usecert" => [
"cert" => "apiclient_cert.pem",
"key" => "apiclient_key.pem"
]
];
模型application\models\Wxpay_model.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Wxpay_model extends CI_Model
{
public $unifiedorderUrl;
public $profitsharingaddreceiverUrl;
public $profitsharingremovereceiverUrl;
public $profitsharingUrl;
public $conf;
public function __construct()
{
parent::__construct();
//自动判断是否用沙盒环境
$this->conf = $this->config->item('wxpay_profitsharing');
if($this->conf['sandbox'] == true){
$baseUrl = "https://api.mch.weixin.qq.com/sandboxnew/pay/";
}else{
$baseUrl = "https://api.mch.weixin.qq.com/pay/";
}
$this->unifiedorderUrl = $baseUrl."unifiedorder"; //统一下单
$this->profitsharingaddreceiverUrl = $baseUrl."profitsharingaddreceiver"; //添加分账接收方
$this->profitsharingremovereceiverUrl = $baseUrl."profitsharingremovereceiver"; //添加分账接收方
$this->profitsharingUrl = "https://api.mch.weixin.qq.com/secapi/pay/profitsharing"; //请求单次分账
}
/**
* 创建支付
* @param $param
* @return false|string
*/
public function createPay($param){
$param['appid'] = $this->conf['appid'];
$param['mch_id'] = $this->conf['mch_id'];
$param['nonce_str'] = $this->noncestr();
$param['spbill_create_ip'] = $this->input->ip_address();
$param['notify_url'] = $this->conf['notify_url'];
$param['trade_type'] = 'JSAPI';
$param['sign'] = $this->sign($param, $this->getSignKey());//签名;
$postXml = $this->arrayToXml($param); //转换成xml
$resp = $this->httpRequest($this->unifiedorderUrl, $postXml);
$xml = simplexml_load_string($resp);
if((string)$xml->return_code != "SUCCESS" || (string)$xml->result_code != "SUCCESS"){
log_message('error',$xml);
return json_encode(['code'=>-1,'msg'=>(string)$xml->return_msg], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
$payParam = array(
'appId' => $param['appid'],
'package' => "prepay_id=".(string)$xml->prepay_id,
'nonceStr' => $param['nonce_str'],
'timeStamp' => time(),
'signType' => 'MD5',
);
$signNew = $this->sign($payParam, $this->getSignKey());//生产js用的签名;
$payParam['paySign'] = $signNew;
return json_encode(['code'=>0,'msg'=>'ok','data'=>$payParam],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
/**
* 获取openid和其他参数
* @param $arr url参数
* @return array|void
*/
public function getOpenidAndState($arr){
$appid = $this->conf['appid'];
$secret = $this->conf['AppSecret'];
$code = $this->input->get('code');
$state2 = urldecode($this->input->get('state'));
if(empty($code) && empty($state2)){ //没获取到code
$redirectUri = urlencode("回调地址");
$state1 = urlencode(json_encode($arr));
$url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$appid}&redirect_uri={$redirectUri}&response_type=code&scope=snsapi_base&state={$state1}#wechat_redirect";
header("Location: {$url}");
return;
}else{ //已经获取code
$accessToken = redis()->get('h5_access_token');
redis()->close();
if(empty($accessToken)) {
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$appid}&secret={$secret}&code={$code}&grant_type=authorization_code";
$resJson = $this->httpGet($url);
$resArr = json_decode($resJson,true);
$refresh_token = $resArr['refresh_token'];
redis()->set('h5_access_token', $resJson, 7199);
redis()->close();
}else{
$accessTokenArr = json_decode($accessToken,true);
$refresh_token = $accessTokenArr['refresh_token'];
}
$url2 = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={$appid}&grant_type=refresh_token&refresh_token=$refresh_token";
$res2 = $this->httpGet($url2);
$openid = json_decode($res2,true)['openid'];
return ['openid'=>$openid, 'state'=>json_decode($state2,true)];
}
}
/**
* 获取验签秘钥
* @return bool|string
*/
public function getSignKey(){
if($this->conf['sandbox'] == true){ //沙盒环境
$arr = [
'mch_id'=>$this->conf['mch_id'],
'nonce_str'=>$this->noncestr()
];
$sign = $this->sign($arr, $this->conf['key']);
$postXml = "<xml>
<mch_id>{$arr['mch_id']}</mch_id>
<nonce_str>{$arr['nonce_str']}</nonce_str>
<sign>{$sign}</sign>
</xml>";
$resp = $this->httpRequest("https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey", $postXml);
$xml = simplexml_load_string($resp);
if((string)$xml->return_code=="SUCCESS"){
return (string)$xml->sandbox_signkey;
}else{
return $resp;
}
}else{ //生产环境
return $this->config->item('wxpay_profitsharing')['key'];
}
}
/**
* 添加分账接收方
* @param $param
* @return false|string
*/
public function addreceiver($param){
$param['appid'] = $this->conf['appid'];
$param['mch_id'] = $this->conf['mch_id'];
$param['nonce_str'] = $this->noncestr();
$param['sign_type'] = 'HMAC-SHA256';
$param['sign'] = $this->signSHA256($param, $this->getSignKey());//签名
$postXml = $this->arrayToXml($param); //转换成xml
$resp = $this->httpRequest($this->profitsharingaddreceiverUrl, $postXml);
$xml = simplexml_load_string($resp);
if((string)$xml->return_code != "SUCCESS" || (string)$xml->result_code != "SUCCESS"){
log_message('error',$xml);
return json_encode(['code'=>-1,'msg'=>(string)$xml->err_code_des], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
$receiver = json_decode($param['receiver'], true);
return json_encode(['code'=>0,'msg'=>'ok', 'data'=>$receiver],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
/**
* 申请单次分账
* @param $param
* @return false|string
*/
public function applyProfitshare($param){
$param['appid'] = $this->conf['appid'];
$param['mch_id'] = $this->conf['mch_id'];
$param['nonce_str'] = $this->noncestr();
$param['sign_type'] = 'HMAC-SHA256';
$param['sign'] = $this->signSHA256($param, $this->getSignKey());//签名
$postXml = $this->arrayToXml($param); //转换成xml
$resp = $this->httpRequest($this->profitsharingUrl, $postXml, true);
$xml = simplexml_load_string($resp);
if((string)$xml->return_code != "SUCCESS" || (string)$xml->result_code != "SUCCESS"){
log_message('error',$xml);
return json_encode(['code'=>-1,'msg'=>(string)$xml->err_code_des], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
$receivers = json_decode($param['receivers'], true);
return json_encode(['code'=>0,'msg'=>'ok', 'data'=>$receivers],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
/**
* 随机32位字符串
* @return string
*/
public function noncestr(){
$result = '';
$str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
for ($i=0;$i<32;$i++){
$result .= $str[rand(0,48)];
}
return $result;
}
/**
* MD5签名方式 $arr要先排好顺序 https://pay.weixin.qq.com/wiki/doc/api/jsapi_sl.php?chapter=4_3
* 但是凡是调用接口的签名, 在StringA按ASCII码拼接完请求参数之后,都需要在结尾加上key值
* @param $arr
* @param $key 签名key
* @return string
*/
public function sign($arr, $key=null){
$stringSign = '';
ksort($arr);
foreach ($arr as $k=>$v){
$stringSign .= $k.'='.$v.'&';
}
$stringSign = trim($stringSign,"&");
if($key){
$stringSign .= "&key=".$key;
}
return strtoupper(md5($stringSign));
}
/**
* HMAC-SHA256签名方式 $arr要先排好顺序 https://pay.weixin.qq.com/wiki/doc/api/jsapi_sl.php?chapter=4_3
* @param $arr
* @param null $key
* @return string
*/
public function signSHA256($arr, $key=null){
$stringSign = '';
ksort($arr);
foreach ($arr as $k=>$v){
$stringSign .= $k.'='.$v.'&';
}
$stringSign = trim($stringSign,"&");
if($key){
$stringSign .= "&key=".$key;
}
return strtoupper(hash_hmac('sha256', $stringSign, $key));
}
/**
* curl post请求
* @param $url
* @param array $data
* @param array $headers
* @param bool $useCert
* @return bool|string
*/
public function httpRequest($url,$data = null,$useCert=false,$headers=array())
{
$curl = curl_init();
if( count($headers) >= 1 ){
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($curl, CURLOPT_URL, $url);
if($useCert == true){
//使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt($curl,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($curl,CURLOPT_SSLCERT, $this->conf['usecert']['cert']);
curl_setopt($curl,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($curl,CURLOPT_SSLKEY, $this->conf['usecert']['key']);
}
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
if (!empty($data)){
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
/**
* curl get请求
* @param $url
* @return bool|string
*/
public function httpGet($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$result = curl_exec($ch);
curl_close ($ch);
return $result;
}
/**
* 将xml转为array
* param string $xml
*/
public function xmlToArray($xml)
{
if(!$xml){
log_message('error',"支付xml数据异常!");
}
//将XML转为array
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $values;
}
/**
* 数组转换xml
* param array $arr
*/
public function arrayToXml($arr){
if(!is_array($arr) || count($arr) <= 0){
log_message('error',"数组数据异常!");
}
$xml = "<xml>";
foreach ($arr as $key=>$val){
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
}
控制器application\controllers\Pay.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
header("Access-Control-Allow-Credentials","true");
header("Access-Control-Allow-Origin: * ");
class Test extends CI_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('Wxpay_model');
}
//html显示页
public function merchantWxpay(){
//获取参数
$merchantId = $this->input->get('mid');
$param = [ 'mid'=>$merchantId ];
//获取用户openid
$resArr = $this->Wxpay_model->getOpenidAndState($param);
$item = [
'title'=>'店铺名',
'desc'=>'店铺描述',
'openid'=>$resArr['openid'],
'mid'=>$resArr['state']['mid'] //商户id
];
$this->load->view("wxpay", $item);
}
//发起输入密码请求
public function merchantReceipt(){
$openid = $this->input->post('openid');
$mid = $this->input->post('mid');
$money = $this->input->post('money');
/**
* todo 下单前动作
*/
$param['openid'] = $openid;
$param['out_trade_no'] = orderSn($mid); //生产订单号
$param['sub_mch_id'] = '商户号'; //特约商户列表里的商户号
$param['total_fee'] = $money * 100;
$param['body'] = 'JSAPI_Test';
$param['profit_sharing'] = "Y"; //开启分账
$res = $this->Wxpay_model->createPay($param);
echo $res;
}
//回调地址
public function notifyProfitsharing(){
$xml = file_get_contents('php://input');
log_message('error',$xml);
$param = $this->Wxpay_model->xmlToArray($xml);
$sign = $param['sign'];
unset($param['sign']);
$signSelf = $this->Wxpay_model->sign($param, $this->Wxpay_model->getSignKey());//生签名
//校验签名
if($sign == $signSelf){
if($param['return_code'] == 'SUCCESS' && $param['result_code'] == 'SUCCESS'){
$out_trade_no = $param['out_trade_no'];
$transaction_id = $param['transaction_id']; //微信返回
/**
* todo 处理回调动作
*/
//https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7
echo "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[{$param['OK']}]]></return_msg></xml>";
}else{
logMessage($param);
echo "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[{$param['err_code_des']}]]></return_msg></xml>";
}
}else{
log_message('error','校验签名失败: '.$sign .'=='. $signSelf);
echo "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[校验签名失败]]></return_msg></xml>";
}
}
//添加分账接收方
public function merchantAddreceiver(){
/**
* todo 添加前的处理
*/
$item = [
'type'=>'MERCHANT_ID',
'account'=>'商户号',
'name'=>'商户全称',
'relation_type'=>'SERVICE_PROVIDER'
];
$receiverJson = json_encode($item,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
$param['receiver'] = $receiverJson;
$param['sub_mch_id'] = '1567643641';
$res = $this->Wxpay_model->addreceiver($param);
var_dump($res);
}
//申请分账
public function applyProfitsharing(){
//服务商最多可控制的分账资金是(订单金额-手续费)*该比例。如需修改,请联系特约商户。 (0.6%)
$items = [
[
'type'=>'MERCHANT_ID',
'account'=>'商户号',
'amount'=>0.03 * 100,
'description'=>'分账描述'
],
[
'type'=>'PERSONAL_WECHATID',
'account'=>'个人微信号',
'amount'=>0.01 * 100,
'description'=>'分账描述'
],
];
$receiverJson = json_encode($items,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
$param['receivers'] = $receiverJson;
$param['sub_mch_id'] = '1567643641';
$param['transaction_id'] = '4200000449201912198210883714';
$param['out_order_no'] = 'ljsy2019121917080610000000148681';
$res = $this->Wxpay_model->applyProfitshare($param);
var_dump($res);
}
}
站帮html页面application\views\wxpay.php
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no">
<title>向商家付款</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="__ROOT__/statics/bbshop/news/css/basic.css">
<link rel="stylesheet" href="__ROOT__/statics/bbshop/news/js/font-awesome/css/font-awesome.min.css">
<style>
.a{height:65px;line-height:65px;text-align:center;border:1px solid #ccc;background-color:white}
.up{border-top:none}
.left{border-left:none}
.b{height:195px;padding:65px 0;background-color:deepskyblue}
.b a{height:195px;display:block;font-size:20px;color:white}
.b a:hover{text-decoration:none}
.container-fluid{overflow:hidden;position:fixed;bottom:0;height:260px;color:#666;width:100%;font-size:30px;background-color:#ffffff}
.form-group{margin:15px auto;width:90%}
.form-group span{padding:5px 10px 0 0;float:right;font-size:15px;color:#999}
.form-group .input-group input{height:60px;text-align:right;color:#333333;font-size:38px}
.footer{overflow:hidden;position:fixed;bottom:280px;margin:0 auto;text-align:center;width:100%;color:#999;font-size:14px}
.pay_logo{margin:0 auto;width:260px;height:100px}
.pay_logo img{float:left;width:80px;height:80px}
.pay_logo h1{font-size:26px;font-weight:normal;padding:15px 0 0 95px}
.pay_logo p{padding:0 0 0 115px}
.header{margin:15px auto;width:100%}
body{background-color:#FFFFFF}
.imgs{z-index:999;position:relative;top:-52px}
*{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
</style>
</head>
<script>
$(document).ready(function(){
$("#input-money").focus(function(){
document.activeElement.blur();
});
});
</script>
<body>
<div class="header">
<div class="pay_logo">
<img src="__ROOT__/statics/bbshop/images/seller_logo.png" alt="">
<h1><?php echo $title;?></h1>
<!--<p>评分:4.9分</p>-->
</div>
</div>
<form class="form-horizontal" action="" method="post" enctype="multipart/form-data" name="pay">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon" style="font-size: 30px;">¥</div>
<input type="tel" name="money" value="1.01" id="input-money" class="form-control" />
<input type="hidden" name="mid" id="mid" value="<?php echo $mid;?>" />
<input type="hidden" name="openid" id="openid" value="<?php echo $openid;?>" />
</div>
<span class="imgs"><img src="__ROOT__/statics/bbshop/images/cursor.gif" alt=""></span>
<span><?php echo $desc;?></span>
</div>
</form>
<!--<div class="footer">由 XXX 提供技术支持</div>-->
<div class="container-fluid">
<div class="row">
<div class="col-xs-3 a" ontouchend="pay_money(this,1)">1</div>
<div class="col-xs-3 a left" ontouchend="pay_money(this,1)">2</div>
<div class="col-xs-3 a left" ontouchend="pay_money(this,1)">3</div>
<div class="col-xs-3 a left" ontouchend="pay_money(this,3)"><i class="fa fa-remove"></i></div>
</div>
<div class="row">
<div class="col-xs-9">
<div class="row">
<div class="col-xs-4 a up" ontouchend="pay_money(this,1)">4</div>
<div class="col-xs-4 a up left" ontouchend="pay_money(this,1)">5</div>
<div class="col-xs-4 a up left" ontouchend="pay_money(this,1)">6</div>
</div>
<div class="row">
<div class="col-xs-4 a up" ontouchend="pay_money(this,1)">7</div>
<div class="col-xs-4 a up left" ontouchend="pay_money(this,1)">8</div>
<div class="col-xs-4 a up left" ontouchend="pay_money(this,1)">9</div>
</div>
<div class="row">
<div class="col-xs-8 a up" ontouchend="pay_money(this,1)">0</div>
<div class="col-xs-4 a up left" ontouchend="pay_money(this,1)">.</div>
</div>
</div>
<div class="col-xs-3 b left" align="center">
<a href="###" class="text" id="pay">确定
支付</a>
</div>
</div>
</div>
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script>
var fm = document.pay;
function pay_money(obj,tip){
if(tip==1){
var con=document.getElementById('input-money').value;
var num = con + obj.innerHTML;
if(num == '.'){
num = '0.';
}
if(num>0){
$(".b").css('background','deepskyblue');
}
if(num>=1){
num = num.replace(/\b(0+)/gi,"");
}
if(num<=99999.99 && num.toString().length<=8){
if(num.indexOf(".")>0){
num = num.replace(/\b(0+)/gi,"0");
if(num.toString().split(".")[1].length<=2){
document.getElementById('input-money').value = num;
}
}else {
document.getElementById('input-money').value = num;
}
}
}else if(tip==2){
document.getElementById('input-money').value="";
}else if(tip==3){
var con = document.getElementById('input-money').value;
var num = con.slice(0,-1);
if(num < 0.01){
$(".b").css('background','lightblue');
}
document.getElementById('input-money').value = num;
}
}
$('#pay').on('click',function () {
var money = fm.money.value;
var openid = fm.openid.value;
var mid = fm.mid.value;
$.post("https://xxxx/pay/merchantreceipt",
{'mid':mid,'openid':openid,'money':money},
function(resdata){
//console.log("resdata",resdata);
var payInfo = JSON.parse(resdata);
if(payInfo.code==0){
//https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7
WeixinJSBridge.invoke(
'getBrandWCPayRequest',{
"appId" : payInfo.data.appId, //公众号名称,由商户传入
"timeStamp": payInfo.data.timeStamp, //戳,自1970 年以来的秒数
"nonceStr" : payInfo.data.nonceStr, //随机串
"package" : payInfo.data.package,
"signType" : payInfo.data.signType, //微信签名方式:
"paySign" : payInfo.data.paySign //微信签名,
},function(res){
//https://mp.weixin.qq.com/s/JEQnD_NmqNvYnCi5hCKW9g
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
console.log("充值成功!");
}
}
);
}
}
);
});
</script>
</body>
</html>
来源:CSDN
作者:guer168
链接:https://blog.csdn.net/guer168/article/details/103983212