公众号支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
1、 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
2、用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
3、将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
公众号支付具体实现步骤见官方文档《微信支付开发文档境内普通商户》,官方链接地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=3_1
一、申请支付账户
商户在微信公众平台(申请扫码支付、公众号支付)或开放平台(申请APP支付)按照相应提示,申请相应微信支付模式。微信支付工作人员审核资料无误后开通相应的微信支付权限。微信支付申请审核通过后,商户在申请资料填写的邮箱中收取到由微信支付小助手发送的邮件,此邮件包含开发时需要使用的支付账户信息,见图3.1所示。
二、配置支付目录和授权域名
请确保实际支付时的请求目录与后台配置的目录一致,否则将无法成功唤起微信支付。
在微信商户平台(pay.weixin.qq.com)设置您的公众号支付支付目录,设置路径:商户平台-->产品中心-->开发配置,如图7.7所示。公众号支付在请求支付的时候会校验请求来源是否有在商户平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。
开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。具体界面如图7.8所示:
三、支付业务流程
四、示例代码:
StringUtil
/**
* 按指定长度生成随机字符串
* @param length
* @return
*/
public static String getRandomString(int length) { //length表示生成字符串的长度
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
UnifiedOrderRequest
package com.common.entity;
public class UnifiedOrderRequest extends BaseEntity {
private String appid;// 公众账号ID
private String mch_id;// 微信支付分配的商户号
private String nonce_str;// 随机字符串
private String sign;// 签名
private String body;// 商品简单描述
private String out_trade_no;// 商户订单号
private String total_fee;// 标价金额
private String spbill_create_ip;// 终端IP
private String notify_url;// 通知地址
private String trade_type = "JSAPI";// 交易类型
// JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
// 其他不必填参数
private String device_info;// 设备号
private String sign_type;// 签名类型 默认为MD5
private String detail;// 商品详情
private String attach;// 附加数据
private String fee_type;// 标价币种
private String time_start;// 交易起始时间
private String time_expire;// 交易结束时间
private String goods_tag;// 商品标记
private String product_id;// 商品ID
private String limit_pay;// 指定支付方式
private String openid;// 用户标识
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getTotal_fee() {
return total_fee;
}
public void setTotal_fee(String total_fee) {
this.total_fee = total_fee;
}
public String getSpbill_create_ip() {
return spbill_create_ip;
}
public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
public String getDevice_info() {
return device_info;
}
public void setDevice_info(String device_info) {
this.device_info = device_info;
}
public String getSign_type() {
return sign_type;
}
public void setSign_type(String sign_type) {
this.sign_type = sign_type;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public String getFee_type() {
return fee_type;
}
public void setFee_type(String fee_type) {
this.fee_type = fee_type;
}
public String getTime_start() {
return time_start;
}
public void setTime_start(String time_start) {
this.time_start = time_start;
}
public String getTime_expire() {
return time_expire;
}
public void setTime_expire(String time_expire) {
this.time_expire = time_expire;
}
public String getGoods_tag() {
return goods_tag;
}
public void setGoods_tag(String goods_tag) {
this.goods_tag = goods_tag;
}
public String getProduct_id() {
return product_id;
}
public void setProduct_id(String product_id) {
this.product_id = product_id;
}
public String getLimit_pay() {
return limit_pay;
}
public void setLimit_pay(String limit_pay) {
this.limit_pay = limit_pay;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
}
后台
@RequestMapping(value = "/charge")
public Map charge(HttpServletRequest req) throws Exception {
super.pageLoad(req);
String total_fee = (String) super.parameterMap.get("total_fee");// 金额
// 将元转换为分
BigDecimal tf = new BigDecimal(total_fee);
BigDecimal real_total_fee = tf.multiply(new BigDecimal(100));
Map dataMap = new LinkedHashMap();
long timeStamp = System.currentTimeMillis() / 1000;// 时间戳(毫秒转换为秒)
String nonceStr = StringUtil.getRandomString(32);// 随机串
// 获取prepay_id,即获取微信生成的订单号,需要我们调用统一订单接口来获取
UnifiedOrderRequest uniOrder = new UnifiedOrderRequest();
uniOrder.setAppid(AppString.getAppid());
uniOrder.setMch_id(PublicConstants.MCH_ID);
uniOrder.setNonce_str(nonceStr);
uniOrder.setBody("xx充值");
uniOrder.setOut_trade_no(Utils.createTradeNo("O"));// 订单号生成
uniOrder.setTotal_fee(String.valueOf(real_total_fee.longValue()));
uniOrder.setOpenid((String) super.session.getAttribute("openId"));
uniOrder.setSpbill_create_ip(getIpAddr(req));
uniOrder.setNotify_url(
req.getScheme() + "://" + req.getServerName() + req.getContextPath() + "/wxpay/payNotify");
uniOrder.setSign(Utils.createSign(Utils.convertBeanToMap(uniOrder)));
// 调用微信接口获取prepay_id
String result = httpSender.sendPost("https://api.mch.weixin.qq.com/pay/unifiedorder",
Utils.reqObjectToXml(uniOrder));
logger.info(result);
UnifiedOrderResult uniOrderResult = (UnifiedOrderResult) Utils.getMsgResultEntity(result,
UnifiedOrderResult.class.getName());
if (!uniOrderResult.getReturn_code().equals("SUCCESS")) {
dataMap.put("success", false);
dataMap.put("msg", "调用微信统一下单接口失败,原因:" + uniOrderResult.getReturn_msg());
return dataMap;
}
dataMap.put("appId", AppString.getAppid());
dataMap.put("nonceStr", nonceStr);
dataMap.put("package", "prepay_id=" + uniOrderResult.getPrepay_id());
dataMap.put("signType", PublicConstants.SIGN_TYPE);
dataMap.put("timeStamp", String.valueOf(timeStamp));
dataMap.put("paySign", Utils.createSign(dataMap));
return dataMap;
}
@RequestMapping(value = "/payNotify")
public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map returnMap = new HashMap<>();
// 读取请求头,获取参数
StringBuilder requestSb = new StringBuilder();
BufferedReader reader = request.getReader();
String line = null;
while (null != (line = reader.readLine())) {
requestSb.append(line);
}
logger.info("payNotify支付回调通知结果:" + requestSb.toString());
Map wxResultMap = WXPayUtil.xmlToMap(requestSb.toString());
// 判断通知中的sign是否有效
WXPay wxPay = new WXPay(new WXPayConfig() {
@Override
public String getMchID() {
return PublicConstants.MCH_ID;
}
@Override
public String getAppID() {
return AppString.getAppid();
}
@Override
public String getKey() {
return PublicConstants.API_KEY;
}
@Override
public int getHttpReadTimeoutMs() {
return 5000;
}
@Override
public int getHttpConnectTimeoutMs() {
return 5000;
}
@Override
public InputStream getCertStream() {
return null;
}
});
boolean signValid = wxPay.isPayResultNotifySignatureValid(wxResultMap);
if (signValid) {
if ("SUCCESS".equals(wxResultMap.get("return_code")) && "SUCCESS".equals(wxResultMap.get("result_code"))) {
String guid = wxResultMap.get("openid");// 用户标识
Integer total_fee = Integer.valueOf(wxResultMap.get("openid"));// 订单金额,单位为分
String transaction_id = wxResultMap.get("transaction_id");// 微信支付订单号
/* 支付成功处理 写入交易记录 */
// 1、微信订单号处理过了不再处理 2.通过guid查找用户手机号 3、将充值记录写入数据库
Map paramMap = new HashMap<>();
paramMap.put("wxTransactionId", transaction_id);
List transactionRecordList = this.transactionRecordService.findByParams(paramMap);
if (!CollectionUtils.isEmpty(transactionRecordList)) {// 微信支付订单号已存在则不进行处理
returnMap.put("return_code", "SUCCESS");
returnMap.put("return_msg", "OK");
logger.info("payNotify支付回调返回微信参数【" + transaction_id + "已处理直接返回】:" + returnMap);
response.getWriter().write(WXPayUtil.mapToXml(returnMap));
return;
}
paramMap.put("guid", guid);
List userList = this.accountService.findByParams(paramMap);
String tellphone = userList.get(0).getTellphone();// 用户手机号
this.transactionRecordService.add(new TransactionRecord(tellphone, guid,
new BigDecimal(total_fee / 100), transaction_id, TransactionTypeEnum.RECHARGE.getValue()));// 保存充值记录
returnMap.put("return_code", "SUCCESS");
returnMap.put("return_msg", "OK");
} else {
returnMap.put("return_code", "FAIL");
returnMap.put("return_msg", wxResultMap.get("return_msg"));
}
} else {
returnMap.put("return_code", "FAIL");
returnMap.put("return_msg", "签名失败");
}
logger.info("payNotify支付回调返回微信参数:" + returnMap);
response.getWriter().write(WXPayUtil.mapToXml(returnMap));
}
jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
公众号支付