Hello! 欢迎来到阿珏酱のBlog!

微信JSAPI支付


avatar
阿珏 2019-03-30 2.22k

前段时间一直在做微信相关的业务,虽说不是什么新技术,但之前一直没有机会接触到,然后踩了些坑,抽空整理记录下。
微信支付一共分为7种,分为是:付款码支付、JSAPI支付、Native支付、APP支付、H5支付、小程序支付、人脸支付。
此次业务中使用到的是微信JSAPI支付:用户通过微信扫码、关注公众号等方式进入商家H5页面,并在微信内调用 JSSDK完成支付

文档:https://pay.weixin.qq.com/wiki/doc/api/index.html

SDK:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1

JSAPI支付需要在微信中的浏览器打开才能唤起微信支付,效果如下图

附上界面代码
代码中使用了模板引擎

html:

<div class="am-modal-bd">
	<img src="{$competition['avatar']}" alt=""><br>
	<span style="font-size: 13px;color: #72c6ef">{$competition['username']}</span><br>
	<span id="tips" style="font-size: 13px"></span>
  <ul class="ul_box">
	<li>
		<div></div>
		<div class="label_box">
			<label>
				<input type="radio" name="price" value="{$prices[0]}" checked="">
				<div class="active"><span class="am-icon-diamond"></span> {$prices[0]}钻</div>
			</label>
			<label>
				<input type="radio" name="price" value="{$prices[1]}">
				<div><span class="am-icon-diamond"></span> {$prices[1]}钻</div>
			</label>
			<label>
				<input type="radio" name="price" value="{$prices[2]}">
				<div><span class="am-icon-diamond"></span> {$prices[2]}钻</div>
			</label>
		</div>
	</li>
	<li>
		<div></div>
		<div class="label_box">
			<label>
				<input type="radio" name="price" value="{$prices[3]}">
				<div><span class="am-icon-diamond"></span> {$prices[3]}钻</div>
			</label>
			<label>
				<input type="radio" name="price" value="{$prices[4]}">
				<div><span class="am-icon-diamond"></span> {$prices[4]}钻</div>
			</label>
			<label>
				<input type="number" name="price" id="price" class="input" placeholder="自定义">
			</label>
		</div>
	</li>
  </ul>
  <span style="font-size: 13px">注:1钻=1元,1钻={$activity['offset']}票</span><br>
  <button type="button" class="am-btn am-btn-primary  am-radius" onclick="callpay()" style="margin-top: 5px;">立即微信支付</button>
</div>

JavaScript:

// 投票
var offset = {$activity['offset']};
$('#tips').html('正在给{$competition['code']}号赠送{$prices[0]}钻='+({$prices[0]}*offset)+'票');
$('#vote').click(function(){
	$.post('/index/index/detailed.html?cid={$cid}&aid={$aid}',{
		formhash :'{FORMHASH}',
		submit:'1',
		type:1,
		openid:'{$_G['member']['openid']}'
	},function(res){
		alert(res.msg);
		if (res.code == 0) {
			$('.box-1 span').text(res.data.all);
			$('.box-2 span').text(res.data.rank);
			$('.box-3 span').text(res.data.up + '票');
		}
	});
})
/* jQuery对象级别插件扩展 */
$.fn.extend({
	/* 单选框 */
	hlRadio:function () {
		var radioEl=$(this);
		radioEl.click(function () {
			var price = 0;
			price = $('input:radio:checked').val();
			$('#price').val('');
			$('#tips').html('正在给{$competition['code']}号赠送'+price+'钻='+(price*offset)+'票');
			radioEl.siblings("div").removeClass("active");
			$(this).siblings("div").addClass("active");
		});
	},
});
$("input[name='price']").hlRadio();
$('#price').bind('input propertychange', function(){
	var price = 0;
	price = $('#price').val();
	$('#tips').html('正在给{$competition['code']}号赠送'+price+'钻='+(price*offset)+'票');
})

CSS部分

.ul_box {
	margin:0 auto;
	padding:0;
	list-style:none;
	width: 344px;
}
.ul_box>li {
	padding:10px 10px 0 10px;
	overflow:hidden;
	border-bottom:#e5e5e5 solid 1px;
}
.ul_box>li:last-child {
	border-bottom:none;
}
.ul_box>li>div {
	float:left;
}
.ul_box>li>div:nth-child(1) {
	width:100px;
}
.ul_box>li>div:nth-child(2) {
	width:480px;
	overflow:hidden;
}
.label_box>label {
	display:block;
	float:left;
	margin:0 10px 10px 0;
	position:relative;
	overflow:hidden;
}
.label_box>label>input {
	position:absolute;
	top:0;
	left:-20px;
}
.label_box>label>div {
	width:100px;
	text-align:center;
	border:#dddddd solid 1px;
	height:40px;
	line-height:40px;
	color:#666666;
	user-select:none;
	overflow:hidden;
	position:relative;
	height: 75px;
}
.label_box>label>div.active{
	border:#d51917 solid 1px;
	background-color: #fff9f8;
	color:#d51917;
}
.label_box>label>div.active:after {
	content:'';
	display:block;
	width:20px;
	height:20px;
	background-color:#d51917;
	transform:skewY(-45deg);
	position:absolute;
	bottom:-10px;
	right:0;
	z-index:1;
}
.label_box>label>div.active:before {
	content:'';
	display:block;
	width:3px;
	height:8px;
	border-right:#ffffff solid 2px;
	border-bottom:#ffffff solid 2px;
	transform:rotate(35deg);
	position:absolute;
	bottom:2px;
	right:4px;
	z-index:2;
}
.input{
	height: 75px!important;
	border: 1px solid #DDD!important;
	position: initial!important;
	width: 100px!important;
	text-align: center!important;
	padding: 5px!important;
	font-size: 19px!important;
}
.am-modal-bd{
	border-bottom: none;
	margin-top: 20px;
}
.am-modal-bd img{
	width: 50px;
	border-radius: 50px;
}
.am-icon-diamond{
	font-size: 20px;
	display: block;
	text-align: center;
	margin-bottom: -7px;
}

唤起微信支付,此处的js代码仅在微信手机浏览器中生效

//调用微信JS api 支付
function jsApiCall(appId,timeStamp,nonceStr,package,signType,paySign)
{
	WeixinJSBridge.invoke(
		'getBrandWCPayRequest',{
			"appId":appId,     //公众号名称,由商户传入
			"timeStamp":timeStamp,         //时间戳,自1970年以来的秒数
			"nonceStr":nonceStr, //随机串
			"package":package,
			"signType":signType,         //微信签名方式:
			"paySign":paySign //微信签名
		},
		function(res){
			if(res.err_msg == "get_brand_wcpay_request:ok" ){
				WeixinJSBridge.log(res.err_msg);
				alert('投票成功');
				// window.history.go(-1);
				var url = '/index/index/detailed.html?cid={$cid}&aid={$aid}';
				window.location.href=url;
				// location.reload();
				return false;
			}
			alert('投票失败');
		}
	);
}
// 支付
function callpay()
{
	var price = '';
	if ($('#price').val()) {
		price = $('#price').val();
	}else if($('input:radio:checked').val()){
		price = $('input:radio:checked').val();
	}else{
		alert('请输入购买数量');
	}
	if (Number(price) <= 0) {
		alert('请输入一个大于0的正数');
		$('#price').val('');
		return false
	}
	$.post('/index/index/weixin.html?cid={$cid}&aid={$aid}',{
		formhash :'{FORMHASH}',
		submit:'1',
		type:1,
		pay:1,
		price: price,
		openid:'{$_G['member']['openid']}'
	},function(res){
		if (res.code ==0) {
			jsApiCall(
				res.data.appId,
				res.data.timeStamp,
				res.data.nonceStr,
				res.data.package,
				res.data.signType,
				res.data.paySign,
			);
		}else{
			alert(res.msg);
		}
	});
	// if (typeof WeixinJSBridge == "undefined"){
	// 	if( document.addEventListener ){
	// 		document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
	// 	}else if (document.attachEvent){
	// 		document.attachEvent('WeixinJSBridgeReady', jsApiCall);
	// 		document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
	// 	}
	// }else{
	// }
}

界面基于妹子UI,效果如下图

PHP后端,创建本地订单

//①、获取用户openid
$tools = new JsApiPay();
$openid = $tools->Getopenid(); //如果已经有了,可以直接赋值
//②、统一下单
$input = new WxPayUnifiedOrder();
$input->SetBody("购买钻石票");
$input->SetAttach(json_encode([  //附加参数
	'order' => $order_id,
	'cid' => $cid,
	'uid' => $_G['uid'],
	'aid' => $aid,
	'md5' => $md5
]));
$input->SetOut_trade_no(WxPayConfig::MCHID.date("YmdHis"));
$input->SetTotal_fee($price);  //价格,单位分,“元记得*100”
$input->SetTime_start(date("YmdHis"));
$input->SetTime_expire(date("YmdHis", time() + 600));
$input->SetGoods_tag("test");
$input->SetNotify_url(WX_NOTIFY_URL);
$input->SetTrade_type("JSAPI");
$input->Setopenid($openid);
$order = WxPayApi::unifiedOrder($input);
$jsApiParameters = $tools->GetJsApiParameters($order);
//保存本地订单
DB::insert('vote_order', [
	'order' => $order_id,
	'time' => time(),
	'price' => intval($_GET['price']),
	'uid' => $_G['uid'],
	'openid' => $openid,
	'ip' => $ip
]);
Json(['code' => 0, 'msg' => '成功','data' => json_decode($jsApiParameters,true)]);
//获取共享收货地址js函数参数
// $editAddress = $tools->GetEditAddressParameters();
// debug($jsApiParameters);
//③、在支持成功回调通知中处理成功之后的事宜,见 notify.php
/**
 * 注意:
 * 1、当你的回调地址不可访问的时候,回调通知会失败,可以通过查询订单来确认支付是否成功
 * 2、jsapi支付时需要填入用户openid,WxPay.JsApiPay.php中有获取openid流程 (文档可以参考微信公众平台“网页授权接口”,
 * 参考http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html)
 */
<?php
/**
 *
 * 回调基础类
 * @author widyhu
 *
 */
class WxPayNotify extends WxPayNotifyReply {
	/**
	 *
	 * 回调入口
	 * @param bool $needSign  是否需要签名输出
	 */
	final public function Handle($needSign = true) {
		$msg = "OK";
		//当返回false的时候,表示notify中调用NotifyCallBack回调失败获取签名校验失败,此时直接回复失败
		$result = WxpayApi::notify(array($this, 'NotifyCallBack'), $msg);
// 这是一个调试技巧 
// file_put_contents('a.txt',print_r($msg,true));
		if ($result == false) {
			$this->SetReturn_code("FAIL");
			$this->SetReturn_msg($msg);
			$this->ReplyNotify(false);
			return;
		} else {
			//该分支在成功回调到NotifyCallBack方法,处理完成之后流程
			$attach = json_decode($msg['attach'], true);
			// file_put_contents('aaaa.txt',print_r($attach,true));
			$cid = $attach['cid'];
			$aid = $attach['aid'];
			$uid = $attach['uid'];
			$order = $attach['order'];
			$md5 = $attach['md5'];
			$vote_order = DB::fetch_first("SELECT * FROM " . DB::table('vote_order') . " WHERE `order`='{$order}' and status = 0");
			if (!$vote_order) {
				$this->SetReturn_code("SUCCESS");
				$this->SetReturn_msg("OK");
			}
			$sign_md5 = md5($vote_order['openid'] . $vote_order['order'] . $vote_order['uid'] );
			if ($sign_md5 == $md5) {
				// 所有活动信息
				$activity = DB::fetch_first("SELECT * FROM " . DB::table('vote_activity') . " WHERE aid={$aid} ");
				$ip = $vote_order['ip'];
				$jewel_vote = $vote_order['price'] * $activity['offset'];
				// 投票
				DB::query("UPDATE `pre_vote_competition` SET `jewel`=jewel+{$vote_order['price']},jewel_vote=jewel_vote+{$jewel_vote} WHERE (`cid`='{$cid}')");
				// 增加投票记录
				DB::insert('vote_record',[
					'time' => TIMESTAMP,
					'openid' => $vote_order['openid'],
					'type' => 1,
					'aid' => $aid,
					'cid' => $cid,
					'ip' => $ip,
					'price' => $vote_order['price'],
					'jewel_vote' => $jewel_vote,
					'uid' => $uid,
					'order' => $vote_order['order']
				]);
				// 更新订单状态
				DB::query("UPDATE `pre_vote_order` SET `status` = 1 WHERE `order`={$order}");
				// 更新活动总收益
				DB::query("UPDATE `pre_vote_activity` SET `profit`=profit+{$vote_order['price']} WHERE `aid`={$aid}");
			}
			$this->SetReturn_code("SUCCESS");
			$this->SetReturn_msg("OK");
		}
		$this->ReplyNotify($needSign);
	}
	/**
	 *
	 * 回调方法入口,子类可重写该方法
	 * 注意:
	 * 1、微信回调超时时间为2s,建议用户使用异步处理流程,确认成功之后立刻回复微信服务器
	 * 2、微信服务器在调用失败或者接到回包为非确认包的时候,会发起重试,需确保你的回调是可以重入
	 * @param array $data 回调解释出的参数
	 * @param string $msg 如果回调处理失败,可以将错误信息输出到该方法
	 * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调
	 */
	public function NotifyProcess($data, &$msg) {
		//TODO 用户基础该类之后需要重写该方法,成功的时候返回true,失败返回false
		return true;
	}
	/**
	 *
	 * notify回调方法,该方法中需要赋值需要输出的参数,不可重写
	 * @param array $data
	 * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调
	 */
	final public function NotifyCallBack($data) {
		$msg = "OK";
		$result = $this->NotifyProcess($data, $msg);
		if ($result == true) {
			$this->SetReturn_code("SUCCESS");
			$this->SetReturn_msg("OK");
		} else {
			$this->SetReturn_code("FAIL");
			$this->SetReturn_msg($msg);
		}
		return $result;
	}
	/**
	 *
	 * 回复通知
	 * @param bool $needSign 是否需要签名输出
	 */
	final private function ReplyNotify($needSign = true) {
		//如果需要签名
		if ($needSign == true &&
			$this->GetReturn_code($return_code) == "SUCCESS") {
			$this->SetSign();
		}
		WxpayApi::replyNotify($this->ToXml());
	}
}

回调类调用

<?php
if(!defined('IN_DISCUZ')) {
	exit('Access Denied');
}
require_once "./source/plugin/vote/class/WxpayAPI_php_v3/lib/WxPay.Api.php";
require_once "./source/plugin/vote/class/WxpayAPI_php_v3/lib/WxPay.Notify.php";
require_once "./source/plugin/vote/class/WxpayAPI_php_v3/example/WxPay.JsApiPay.php";
require_once './source/plugin/vote/class/WxpayAPI_php_v3/example/log.php';
//初始化日志
// $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log');
// $log = Log::Init($logHandler, 15);
// Log::DEBUG("begin notify");
$notify = new WxPayNotify();
$notify->Handle(false);

注意:微信支付需要在后台配置一个微信支付授权目录,其中的APPID等参数在微信支付后台可获取到。
微信公众号信息配置
APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
MCHID:商户号(必须配置,开户邮件中可查看)
KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置)
设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置),
获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN

  • avatar
    游客

    这要怎么复制代码呀 我想实战操作下 求博主给个代码的百度云链接可否

    • avatar
      博主

      @ 苏宝宝 @苏宝宝:微信官方给的SDK中有调用示例,文章中只是基本的html代码,复制去你也是没办法直接使用的,仅供参考

  • avatar
    游客

    表情包怎么整合的,能不能出个教程大佬

    • avatar
      博主

      @ 译丶浅夏 @译丶浅夏:这个有现成的插件,可以去搜一下,但是由于每个博客的模板都不一样,得花点时间自己整上去

  • avatar
    游客

    前提是要有个可以开通支付的微信公众号

  • avatar
    游客

    朕已阅

发表评论
OωO表情

相关阅读