java接入微信app支付
首先创建SortedMap<String, String> parameters = new TreeMap<String, String>();把所需的参数放到map里面
例如:
parameters.put(“appid”, myWXPayConfig.appID);//APPID(appID)
parameters.put(“mch_id”,myWXPayConfig.mchID);//商户号(mchID)
parameters.put(“nonce_str”, PayCommonUtil.CreateNoncestr());//随机字符串,不长于32位
parameters.put(“fee_type”, “CNY”);//货币类型
parameters.put(“notify_url”, myWXPayConfig.notifyUrl);//回调地址
parameters.put(“trade_type”, “APP”);//支付类型
parameters.put(“body”,“充值”+ amount+“个探心币”);//商品描述
parameters.put(“attach”,token + “;” + device + “;” + txmVisitorId);//自定义字段,此字段的值回调时原封返回
String outtradeno= PayCommonUtil.getDateStr();
String ip = request.getHeader(“x-forwarded-for”);
if(ip == null || ip.length() == 0 || “unknown”.equalsIgnoreCase(ip)) {
ip = request.getHeader(“Proxy-Client-IP”);
}
if(ip == null || ip.length() == 0 || “unknown”.equalsIgnoreCase(ip)) {
ip = request.getHeader(“WL-Proxy-Client-IP”);
}
if(ip == null || ip.length() == 0 || “unknown”.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
parameters.put(“spbill_create_ip”, ip);//支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP(就是用户的ip)
parameters.put(“out_trade_no”,outtradeno); // 订单id这里我的订单id生成规则是订单id+时间
amount=String.valueOf(Double.parseDouble(amount)*100);
amount=amount.substring(0,amount.indexOf("."));
parameters.put(“total_fee”, amount); // 订单总金额,单位为分 测试时,每次支付一分钱,微信支付所传的金额是以分为单位的,因此实际开发中需要x100
利用String sign = WXPayUtil.generateSignature(parameters, myWXPayConfig.key);得到sign,把sign页放到map里面
parameters.put(“sign”, sign);
把map集合转成xml格式String xml=WXPayUtil.mapToXml(parameters).replace("<?xml version=\"1.0\" endoding=\"1.0\" standalone=\"no\"?>","");
得到的结果是xml字符串,接下来把xml字符串转成map对象,得到prepay_id
Map<String,String> map=WXPayUtil.xmlToMap(xmlstr);
public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
Map<String, String> reqData = wxPayClient.getNotifyParameter(request);
log.info(“支付成功回调”);
log.info(reqData.toString());
String returnCode = reqData.get(“return_code”);
String resultCode = reqData.get(“result_code”);
try{
if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
//验证签名
boolean signatureValid = wxPay.isPayResultNotifySignatureValid(reqData);
//签名正确
if (signatureValid) {
//业务处理
Map<String, String> responseMap = new HashMap<>(2);
responseMap.put(“return_code”, “SUCCESS”);
responseMap.put(“return_msg”, “OK”);
//设置成功确认内容
String responseXml = WXPayUtil.mapToXml(responseMap);
/*
* 写入数据库内容在这里
*/
//获取用户数据
String attachStr =reqData.get(“attach”);
String token = attachStr.split(";")[0];
String device = attachStr.split(";")[1];
String txmVisitorId = attachStr.split(";")[2];
double money = Double.parseDouble(reqData.get(“cash_fee”))/100;
String outTradeNo = reqData.get(“out_trade_no”);
String transactionId = reqData.get(“transaction_id”);
if (!StringUtils.isEmpty(token)) {
Session session = (Session) MySessionContext.getInstance().getMymap().get(token);
if (Objects.nonNull(session)) {
TxmUser txmUser = (TxmUser) session.getAttribute(“session_user”);
if (Objects.nonNull(txmUser) && !StringUtils.isEmpty(txmUser.getTxmuserid())) {
String txmUserId = txmUser.getTxmuserid();
DecimalFormat df1 = new DecimalFormat(“0.00”);
TxmRecharge txmRecharge=new TxmRecharge();
txmRecharge.setTxmrechargemoney(String.valueOf(money));
txmRecharge.setTxmrechargeouttradeno(outTradeNo);
txmRecharge.setTxmrechargewxtradeno(transactionId);
txmRecharge.setTxmuserid(txmUserId);
txmRecharge.setTxmrechargetime(new Date());
// Android微信充值
if (StringUtils.isEmpty(device) || “null”.equals(device)) {
txmRecharge.setTxmrechargetype(“1”);
// 新增数据到充值记录表
txmRecahrgeService.insertRechargeByWx(txmRecharge);
// 查询该用户的原有余额
String txmuserbalance=txmUserService.selectMoneyByTxmuserid(txmUserId);
String allBalance=String.valueOf(df1.format(Double.parseDouble(txmuserbalance) + money));
//更新余额
int i= txmUserService.updateMoneyByTxmuserid(txmUserId,allBalance);
// 把新的余额添加到session里面去
txmUser.setTxmuserbalance(allBalance);
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute(“session_user”,txmUser);
} else {
// ios微信充值
txmRecharge.setTxmrechargetype(“1” + device);
// 新增数据到充值记录表
txmRecahrgeService.insertRechargeByWx(txmRecharge);
String allBalance = String.valueOf(df1.format(Double.parseDouble(txmUser.getTxmUserIOSBalance()) + money));
// 更新余额
int i = txmUserService.updateMoneyWithIOSByTxmUserId(txmUserId, allBalance);
// 查询是否与游客账户绑定,若绑定,则将用户账户余额同步【一个游客账户只能绑定一个正式账户,反之也是一样】
// 根据用户ID查询游客账户
TxmVisitor txmVisitor = txmVisitorMapper.selectTxmVisitorByUserId(txmUserId);
if (txmVisitor != null) {
// txmVisitor.getTxmVisitorBalance()不会为null,因为默认是0.00
String newTxmVisitorBalance = df1.format(Double.parseDouble(txmVisitor.getTxmVisitorBalance()) + money);
txmVisitorMapper.updateTxmVisitorBalanceByVisitorId(newTxmVisitorBalance, txmVisitor.getTxmVisitorId());
}
// 把新的余额添加到session里面去
txmUser.setTxmUserIOSBalance(allBalance);
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute(“session_user”,txmUser);
}
} else {
// 游客账户登录【不正常操作会走这里,基本不会发生】
updateAccountBalance(txmVisitorId, outTradeNo, transactionId, String.valueOf(money));
}
} else {
// 游客账户登录【当用户已经登录,但是服务器重启时会走这里】
updateAccountBalance(txmVisitorId, outTradeNo, transactionId, String.valueOf(money));
}
} else {
// 游客账户登录【正常退出登录token被清空会走这里】
updateAccountBalance(txmVisitorId, outTradeNo, transactionId, String.valueOf(money));
}
// 发送通知
response.setContentType(“text/xml”);
response.getWriter().write(responseXml);
response.flushBuffer();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
用到的工具类
package com.twenty.eight.star.config.wxpayconfig;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import sun.misc.BASE64Decoder;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
/*
*
* @ClassName: WXPayClient
* @Description: 对WXPay的简单封装,处理支付密切相关的逻辑.
* @author:sarahacao
* @date 2018年11月13日
*
*/
@Slf4j
public class WXPayClient extends WXPay {
/** 密钥算法 */
private static final String ALGORITHM = "AES";
/** 加解密算法/工作模式/填充方式 */
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
/** 用户支付中,需要输入密码 */
private static final String ERR_CODE_USERPAYING = "USERPAYING";
private static final String ERR_CODE_AUTHCODEEXPIRE = "AUTHCODEEXPIRE";
/** 交易状态: 未支付 */
private static final String TRADE_STATE_NOTPAY = "NOTPAY";
/** 用户输入密码,尝试30秒内去查询支付结果 */
private static Integer remainingTimeMs = 10000;
private WXPayConfig config;
public WXPayClient(WXPayConfig config, WXPayConstants.SignType signType, boolean useSandbox) {
super(config, signType, useSandbox);
this.config = config;
}
/**
* 刷卡支付
* 对WXPay#microPay(Map)增加了当支付结果为USERPAYING时去轮询查询支付结果的逻辑处理
* 注意:该方法没有处理return_code=FAIL的情况,暂时不考虑网络问题,这种情况直接返回错误
* @param reqData
* @return
* @throws Exception
*/
public Map<String, String> microPayWithPOS(Map<String, String> reqData) throws Exception {
// 开始时间(毫秒)
long startTimestampMs = System.currentTimeMillis();
Map<String, String> responseMapForPay = super.microPay(reqData);
log.info(responseMapForPay.toString());
//先判断 协议字段返回(return_code),再判断 业务返回,最后判断 交易状态(trade_state)
//通信标识,非交易标识
String returnCode = responseMapForPay.get("return_code");
if (WXPayConstants.SUCCESS.equals(returnCode)) {
String errCode = responseMapForPay.get("err_code");
// 余额不足,信用卡失效
if (ERR_CODE_USERPAYING.equals(errCode) || "SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode)) {
Map<String, String> orderQueryMap = null;
Map<String, String> requestData = new HashMap<>();
requestData.put("out_trade_no", reqData.get("out_trade_no"));
// 用户支付中,需要输入密码或系统错误则去重新查询订单API err_code, result_code, err_code_des
// 每次循环时的当前系统时间 - 开始时记录的时间 > 设定的30秒时间就退出
while (System.currentTimeMillis() - startTimestampMs < remainingTimeMs) {
// 商户收银台得到USERPAYING状态后,经过商户后台系统调用【查询订单API】查询实际支付结果。
orderQueryMap = super.orderQuery(requestData);
String returnCodeForQuery = orderQueryMap.get("return_code");
if (WXPayConstants.SUCCESS.equals(returnCodeForQuery)) {
// 通讯成功
String tradeState = orderQueryMap.get("trade_state");
if (WXPayConstants.SUCCESS.equals(tradeState)) {
// 如果成功了直接将查询结果返回
return orderQueryMap;
}
// 如果支付结果仍为USERPAYING,则每隔5秒循环调用【查询订单API】判断实际支付结果
Thread.sleep(1000);
}
}
// 如果用户取消支付或累计30秒用户都未支付,商户收银台退出查询流程后继续调用【撤销订单API】撤销支付交易。
String tradeState = orderQueryMap.get("trade_state");
if (TRADE_STATE_NOTPAY.equals(tradeState) || ERR_CODE_USERPAYING.equals(tradeState) || ERR_CODE_AUTHCODEEXPIRE.equals(tradeState)) {
Map<String, String> reverseMap = this.reverse(requestData);
String returnCodeForReverse = reverseMap.get("return_code");
String resultCode = reverseMap.get("result_code");
if (WXPayConstants.SUCCESS.equals(returnCodeForReverse) && WXPayConstants.SUCCESS.equals(resultCode)) {
// 如果撤销成功,需要告诉客户端已经撤销订单了
responseMapForPay.put("err_code_des", "用户取消支付或尚未支付,后台已经撤销该订单,请重新支付!");
}
}
}
}
return responseMapForPay;
}
/**
* 从request的inputStream中获取参数
* @param request
* @return
* @throws Exception
*/
public Map<String, String> getNotifyParameter(HttpServletRequest request) throws Exception {
InputStream inputStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = inputStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, length);
}
outSteam.close();
inputStream.close();
// 获取微信调用我们notify_url的返回信息
String resultXml = new String(outSteam.toByteArray(), "utf-8");
Map<String, String> notifyMap = WXPayUtil.xmlToMap(resultXml);
return notifyMap;
}
/**
* 解密退款通知
* <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_16&index=11>退款结果通知文档</a>
* @return
* @throws Exception
*/
public Map<String, String> decodeRefundNotify(HttpServletRequest request) throws Exception {
// 从request的流中获取参数
Map<String, String> notifyMap = this.getNotifyParameter(request);
log.info(notifyMap.toString());
String reqInfo = notifyMap.get("req_info");
//(1)对加密串A做base64解码,得到加密串B
byte[] bytes = new BASE64Decoder().decodeBuffer(reqInfo);
//(2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
SecretKeySpec key = new SecretKeySpec(WXPayUtil.MD5(config.getKey()).toLowerCase().getBytes(), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
//(3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
// java.security.InvalidKeyException: Illegal key size or default parameters
// https://www.cnblogs.com/yaks/p/5608358.html
String responseXml = new String(cipher.doFinal(bytes),"UTF-8");
Map<String, String> responseMap = WXPayUtil.xmlToMap(responseXml);
return responseMap;
}
/**
* 获取沙箱环境验签秘钥API
* <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=23_1">获取验签秘钥API文档</a>
* @return
* @throws Exception
*/
public Map<String, String> getSignKey() throws Exception {
Map<String, String> reqData = new HashMap<>();
reqData.put("mch_id", config.getMchID());
reqData.put("nonce_str", WXPayUtil.generateNonceStr());
String sign = WXPayUtil.generateSignature(reqData, config.getKey(), WXPayConstants.SignType.MD5);
reqData.put("sign", sign);
String responseXml = this.requestWithoutCert("https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey", reqData,
config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
Map<String, String> responseMap = WXPayUtil.xmlToMap(responseXml);
return responseMap;
}
}