本章文献基本都来源于微信支付平台,详情请看微信官方文档:APP支付
系统交互图
文档位置:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3 APP支付-业务流程
根据文档内容,服务端只要做好获取 prepay_id 和 sign 传送给客户端,并做好回调接收处理就行
服务端demo
APP支付文档里面的demo,主要是供客户端使用的,对后台来说,基本没什么用。
不过,依旧有我们后端可以直接拿来使用的demo: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
这个demo主要用于付款码/扫码/H5支付使用,但里面提供了很多方便的工具类。
这里我们引用demo里面的工具类并继承WXPayConfig和实现IWXPayDomain的抽象接口。
-
public class WxPayConfigImpl extends WXPayConfig{ -
// 设置应用Id -
public static String appid = "2019102168481752"; -
// 设置商户号 -
public static String mch_id = "1230000109"; -
// 设置设备号(终端设备号(门店号或收银设备ID),默认请传"WEB",非必需) -
public static String device_info = "WEB"; -
// 设置字符集 -
public static String key = "192006250b4c09247ec02edce69f6a2d"; -
private static WxPayConfigImpl INSTANCE; -
public static WxPayConfigImpl getInstance() throws Exception { -
if (INSTANCE == null) { -
synchronized (WxPayConfigImpl.class) { -
if (INSTANCE == null) { -
INSTANCE = new WxPayConfigImpl(); -
} -
} -
} -
return INSTANCE; -
} -
@Override -
public String getAppID() { -
return appid; -
} -
@Override -
public String getMchID() { -
return mch_id; -
} -
@Override -
public String getKey() { -
return key; -
} -
@Override -
public InputStream getCertStream() { -
//这个可以看我另一篇“支付宝:APP支付接口2.0(alipay.trade.app.pay)” -
//里的“使用demo”步骤,有讲解,如果是个springboot构成的jar,如何设置证书路径 -
//文章链接:https://blog.csdn.net/u014799292/article/details/102680149 -
String fileUrl = "证书路径"; -
InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileUrl); -
byte[] certData = null; -
try { -
certData = IOUtils.toByteArray(certStream); -
} catch (IOException e) { -
// TODO Auto-generated catch block -
e.printStackTrace(); -
}finally{ -
if(certStream != null){ -
try { -
certStream.close(); -
} catch (IOException e) { -
// TODO Auto-generated catch block -
e.printStackTrace(); -
} -
} -
} -
return new ByteArrayInputStream(certData); -
} -
@Override -
public IWXPayDomain getWXPayDomain() { -
return WxPayDomainImpl.instance(); -
} -
}
-
public class WxPayDomainImpl implements IWXPayDomain { -
private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000; //3 minutes -
private long switchToAlternateDomainTime = 0; -
private Map<String, DomainStatics> domainData = new HashMap<String, DomainStatics>(); -
private WxPayDomainImpl(){ -
} -
private static class WxpayDomainHolder{ -
private static IWXPayDomain holder = new WxPayDomainImpl(); -
} -
public static IWXPayDomain instance(){ -
return WxpayDomainHolder.holder; -
} -
@Override -
public void report(String domain, long elapsedTimeMillis, Exception ex) { -
DomainStatics info = domainData.get(domain); -
if(info == null){ -
info = new DomainStatics(domain); -
domainData.put(domain, info); -
} -
if(ex == null){ //success -
if(info.succCount >= 2){ //continue succ, clear error count -
info.connectTimeoutCount = info.dnsErrorCount = info.otherErrorCount = 0; -
}else{ -
++info.succCount; -
} -
}else if(ex instanceof ConnectTimeoutException){ -
info.succCount = info.dnsErrorCount = 0; -
++info.connectTimeoutCount; -
}else if(ex instanceof UnknownHostException){ -
info.succCount = 0; -
++info.dnsErrorCount; -
}else{ -
info.succCount = 0; -
++info.otherErrorCount; -
} -
} -
@Override -
public DomainInfo getDomain(WXPayConfig config) { -
DomainStatics primaryDomain = domainData.get(WXPayConstants.DOMAIN_API); -
if(primaryDomain == null || -
primaryDomain.isGood()) { -
return new DomainInfo(WXPayConstants.DOMAIN_API, true); -
} -
long now = System.currentTimeMillis(); -
if(switchToAlternateDomainTime == 0){ //first switch -
switchToAlternateDomainTime = now; -
return new DomainInfo(WXPayConstants.DOMAIN_API2, false); -
}else if(now - switchToAlternateDomainTime < MIN_SWITCH_PRIMARY_MSEC){ -
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2); -
if(alternateDomain == null || -
alternateDomain.isGood() || -
alternateDomain.badCount() < primaryDomain.badCount()){ -
return new DomainInfo(WXPayConstants.DOMAIN_API2, false); -
}else{ -
return new DomainInfo(WXPayConstants.DOMAIN_API, true); -
} -
}else{ //force switch back -
switchToAlternateDomainTime = 0; -
primaryDomain.resetCount(); -
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2); -
if(alternateDomain != null) -
alternateDomain.resetCount(); -
return new DomainInfo(WXPayConstants.DOMAIN_API, true); -
} -
} -
static class DomainStatics { -
final String domain; -
int succCount = 0; -
int connectTimeoutCount = 0; -
int dnsErrorCount =0; -
int otherErrorCount = 0; -
DomainStatics(String domain) { -
this.domain = domain; -
} -
void resetCount(){ -
succCount = connectTimeoutCount = dnsErrorCount = otherErrorCount = 0; -
} -
boolean isGood(){ return connectTimeoutCount <= 2 && dnsErrorCount <= 2; } -
int badCount(){ -
return connectTimeoutCount + dnsErrorCount * 5 + otherErrorCount / 4; -
} -
} -
}
给 WXPayUtil 再添加几个时间工具方法(看自己需求定制),项目会在最后附链接。
appid & 商户号位置
登录商户平台-产品中心-账号关联(AppID绑定),进入授权申请页面;
**key位置
登录商户平台-->>账户中心-->>API安全-->>设置API**(32位,下面给出生成方式,然后复制粘贴到秘钥位置,记得保留,之后无法查看,只能再次生成,测试环境随便改,正式服记得最好固定一次,修改可能会引起服务端数据错误)
-
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; -
private static final Random RANDOM = new SecureRandom(); -
/** -
* 获取随机字符串 Nonce Str -
* -
* @return String 随机字符串 -
*/ -
public static String generateNonceStr() { -
char[] nonceChars = new char[32]; -
for (int index = 0; index < nonceChars.length; ++index) { -
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); -
} -
return new String(nonceChars); -
}
再来写一个请求类,处理微信订单(订单--统一下单/调起支付接口/支付结果通知/查询订单/关闭订单)
-
/** -
* 订单--统一下单/调起支付接口/退款/结果通知/查询订单/关闭订单 -
* 借鉴:https://blog.csdn.net/asd54090/article/details/81028323 -
*/ -
public class WxAppPayRequest { -
private static final Logger logger = LoggerFactory.getLogger(WxAppPayRequest.class); -
private WxPayConfigImpl config; -
private WXPay wxpay; -
/** -
* 微信支付请求 -
*/ -
public WxAppPayRequest() { -
try { -
config = WxPayConfigImpl.getInstance(); -
wxpay = new WXPay(config); -
} catch (Exception e) { -
e.printStackTrace(); -
logger.error("微信配置初始化错误", e); -
} -
} -
/** -
* APP支付订单请求 -
* -
* @param body -
* 格式:APP名字-实际商品名称,如:天天爱消除-游戏充值 -
* @param attach -
* 附加数据,在查询API和支付通知中原样返回 -
* @param outTradeNo -
* 商户订单号 -
* @param totalFee -
* 总金额 -
* @param startTime -
* 订单开始时间String格式: yyyy-MM-dd HH:mm:ss -
* @param expireMinute -
* 有效时间(分钟) -
* @param notifyUrl -
* 微信支付异步通知回调地址 -
* @return -
*/ -
private WxAppPayResponseCode getOrderSign(String body, String attach, String outTradeNo, BigDecimal totalFee, -
String startTime, int expireMinute, String notifyUrl) { -
// 准备好请求参数 -
Map<String, String> map = new HashMap<String, String>(); -
map.put("device_info", WxPayConfigImpl.device_info); -
map.put("body", body); -
if (attach != null && !attach.isEmpty()) { -
map.put("attach", attach); -
} -
map.put("out_trade_no", outTradeNo); -
map.put("total_fee", totalFee.toString()); -
map.put("spbill_create_ip", WXPayUtil.getLocalAddress()); -
map.put("time_start", WXPayUtil.getFormatTime(startTime)); -
String endTime = WXPayUtil.getNSecondTime(startTime, expireMinute); -
map.put("time_expire", WXPayUtil.getFormatTime(endTime)); -
map.put("notify_url", notifyUrl); -
map.put("trade_type", "APP"); -
// 生成带sign的xml字符串 -
Map<String, String> unifiedOrderMap = null; -
try { -
unifiedOrderMap = wxpay.unifiedOrder(map); -
if (unifiedOrderMap == null || (unifiedOrderMap != null -
&& WxAppPayResponseCode.FAIL.code().equals(unifiedOrderMap.get("return_code")))) { -
String errorMsg = "调用微信“统一下单”获取prepayid 失败..."; -
logger.info("getOrderSign --unifiedOrder: 调用微信“统一下单”获取prepayid 失败."); -
logger.info("getOrderSign --unifiedOrder: 请求参数:" + map.toString()); -
logger.info("getOrderSign --unifiedOrder: 返回Map:" + unifiedOrderMap); -
if (unifiedOrderMap != null) { -
errorMsg += " 异常信息为:" + unifiedOrderMap.get("return_msg"); -
} -
WxAppPayResponseCode error = WxAppPayResponseCode.ERROR; -
error.setAlias(errorMsg); -
return error; -
} -
} catch (Exception e) { -
// TODO Auto-generated catch block -
e.printStackTrace(); -
logger.error("getOrderSign : 调用微信“统一下单”失败 。", e); -
WxAppPayResponseCode error = WxAppPayResponseCode.ERROR; -
error.setAlias("调用微信“统一下单”失败 。" + e.toString()); -
return error; -
} -
// 调用微信请求成功,但响应失败 -
String resultCode = unifiedOrderMap.get("result_code"); -
if (WxAppPayResponseCode.FAIL.code().equals(resultCode)) { -
WxAppPayResponseCode error = WxAppPayResponseCode.findCode(unifiedOrderMap.get("err_code")); -
return error; -
} -
return parseWXOrderResponse(unifiedOrderMap); -
} -
/** -
* 将map转成客户端订单用的封装体 -
* -
* @param map -
* map -
* @return 用户端用的封装体 -
*/ -
private WxAppPayResponseCode parseWXOrderResponse(Map<String, String> map) { -
WxOrderResponse response = new WxOrderResponse(); -
response.setAppid(map.get("appid")); -
response.setPartnerid(map.get("partnerid")); -
response.setPrepayid(map.get("prepay_id")); -
response.setPack("Sign=WXPay"); -
response.setNoncestr(map.get("noncestr")); -
String timestamp = WXPayUtil.getCurrentTimestamp() + ""; -
response.setTimestamp(timestamp); -
// 前人踩坑,咱们乘凉 -
// sgin(签名),不是拿微信“统一下单”返回的sgin,而是自己再签一次,返回给客户端 -
// 签名的参数拿的不是“统一下单”,而是拿“调起支付接口”里面的参数,这步API文档写的是客户端生成,不过咱们服务端就帮他做了 -
// 注意:map的key不能是大写 -
Map<String, String> params = new HashMap<>(); -
params.put("appid", map.get("appid")); -
params.put("partnerid", map.get("partnerid")); -
params.put("prepayid", map.get("prepay_id")); -
params.put("package", "Sign=WXPay"); -
params.put("noncestr", map.get("nonce_str")); -
params.put("timestamp", timestamp); -
try { -
// 这个sign是移动端要请求微信服务端的,也是我们要保存后面校验的 -
String sgin = WXPayUtil.generateSignature(params, config.getKey()); -
response.setSign(sgin); -
} catch (Exception e) { -
e.printStackTrace(); -
logger.error("parseWXOrderResponse : 订单第二次供客户端签名信息失败 。"); -
logger.error("parseWXOrderResponse : 请求参数:" + params.toString()); -
logger.error("parseWXOrderResponse : 返回错误信息:", e); -
WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR; -
errorData.setAlias("调用支付接口生成签名sign失败!"); -
return errorData; -
} -
WxAppPayResponseCode successData = WxAppPayResponseCode.SUCCESS; -
successData.setAlias(JSONObject.toJSONString(response)); -
return successData; -
} -
/** -
* 查微信订单 -
* -
* @param outTradeNo -
* 订单号 -
*/ -
public WxAppPayResponseCode queryOrderByOutTradeNo(String outTradeNo) { -
logger.info("查询微信支付订单信息,订单号为:" + outTradeNo); -
HashMap<String, String> data = new HashMap<String, String>(); -
data.put("out_trade_no", outTradeNo); -
try { -
Map<String, String> orderQueryMap = wxpay.orderQuery(data); -
return disposeReturnInfo(orderQueryMap); -
} catch (Exception e) { -
e.printStackTrace(); -
logger.error("queryOrderByOutTradeNo : 查询微信订单支付信息失败 。订单号:" + outTradeNo); -
logger.error("queryOrderByOutTradeNo : 返回错误信息:", e); -
WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR; -
errorData.setAlias("调用查微信订单支付信息接口失败!"); -
return errorData; -
} -
} -
/** -
* 关闭订单(刚刚生成的订单不能立马关闭,要间隔5分钟,请自行做好判断) -
* -
* @param outTradeNo -
* 订单号 -
* @return -
*/ -
public WxAppPayResponseCode closeOrder(String outTradeNo) { -
logger.info("关闭微信支付订单信息,订单号为:" + outTradeNo); -
HashMap<String, String> data = new HashMap<>(); -
data.put("out_trade_no", outTradeNo); -
try { -
Map<String, String> closeOrderMap = wxpay.closeOrder(data); -
return disposeReturnInfo(closeOrderMap); -
} catch (Exception e) { -
e.printStackTrace(); -
logger.error("closeOrder : 微信关闭订单失败 。订单号:" + outTradeNo); -
logger.error("closeOrder : 返回错误信息:", e); -
WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR; -
errorData.setAlias("调用查微信订单支付信息接口失败!"); -
return errorData; -
} -
} -
/** -
* 微信退款申请 -
* -
* @param outTradeNo -
* 商户订单号 -
* @param amount -
* 金额 -
* @param refund_desc -
* 退款原因(可空) -
* @param notifyUrl -
* 退款异步通知链接 -
* -
* @return 返回map(已做过签名验证),具体数据参见微信退款API -
*/ -
public WxAppPayResponseCode refundOrder(String outTradeNo, BigDecimal amount, String refundDesc, String notifyUrl) -
throws Exception { -
Map<String, String> data = new HashMap<String, String>(); -
data.put("out_trade_no", outTradeNo); -
data.put("out_refund_no", outTradeNo); -
data.put("total_fee", amount + ""); -
data.put("refund_fee", amount + ""); -
data.put("refund_fee_type", "CNY"); -
data.put("refund_desc", refundDesc); -
data.put("notifyUrl", notifyUrl); -
try { -
Map<String, String> refundOrderMap = wxpay.refund(data); -
return disposeReturnInfo(refundOrderMap); -
} catch (Exception e) { -
e.printStackTrace(); -
logger.error("closeOrder : 微信退款申请失败 。订单号:" + outTradeNo); -
logger.error("closeOrder : 返回错误信息:", e); -
WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR; -
errorData.setAlias("调用微信退款申请信息接口失败!"); -
return errorData; -
} -
} -
/** -
* 查微信退款订单 注:如果单个支付订单部分退款次数超过20次请使用退款单号查询 -
* -
* @param outTradeNo -
* 订单号 -
*/ -
public WxAppPayResponseCode queryRefundOrderByOutTradeNo(String outTradeNo) { -
logger.info("查询微信支付订单信息,订单号为:" + outTradeNo); -
HashMap<String, String> data = new HashMap<String, String>(); -
data.put("out_trade_no", outTradeNo); -
try { -
Map<String, String> refundQueryMap = wxpay.refundQuery(data); -
return disposeReturnInfo(refundQueryMap); -
} catch (Exception e) { -
e.printStackTrace(); -
logger.error("queryRefundOrderByOutTradeNo : 查询微信退款订单信息失败 。订单号:" + outTradeNo); -
logger.error("queryRefundOrderByOutTradeNo : 返回错误信息:", e); -
WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR; -
errorData.setAlias("调用查微信退款订单接口失败!"); -
return errorData; -
} -
} -
/** -
* 对接口接收成功后的返回进行处理 -
* -
* @param resultMap -
* @return -
*/ -
private WxAppPayResponseCode disposeReturnInfo(Map<String, String> resultMap) { -
if (resultMap == null -
|| (resultMap != null && WxAppPayResponseCode.FAIL.code().equals(resultMap.get("return_code")))) { -
WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR; -
errorData.setAlias("调用微信接口失败!返回数据为 : " + resultMap); -
return errorData; -
} -
if (WxAppPayResponseCode.FAIL.code().equals(resultMap.get("result_code"))) { -
WxAppPayResponseCode errorData = WxAppPayResponseCode.findCode(resultMap.get("err_code")); -
return errorData; -
} -
WxAppPayResponseCode successData = WxAppPayResponseCode.SUCCESS; -
successData.setAlias(JSONObject.toJSONString(resultMap)); -
return successData; -
} -
/** -
* 是否成功接收微信支付回调 用于回复微信,否则微信回默认为商户后端没有收到回调 -
* -
* @return -
*/ -
public String returnWXPayVerifyMsg() { -
return "<xml>\n" + "\n" + " <return_code><![CDATA[SUCCESS]]></return_code>\n" -
+ " <return_msg><![CDATA[OK]]></return_msg>\n" + "</xml>"; -
} -
public static void main(String[] args) { -
WxAppPayRequest pay = new WxAppPayRequest(); -
WxAppPayResponseCode orderSign = pay.getOrderSign("APP-商品订单支付", "data1;data2", "212458542512542542", -
new BigDecimal("100.12"), "2019-01-02 23:55:14", 10, "http://XXX"); -
System.out.println(orderSign); -
} -
}
OK,基本搞定,构建成一个处理微信订单功能的JAR。
项目已推送github:https://github.com/leopardF/wxpay
最后,再次附上本次借鉴的文章:
如果问题,请提醒修正。