第三方通过微信APP支付流程

it2025-11-16  12

本章文献基本都来源于微信支付平台,详情请看微信官方文档: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

最后,再次附上本次借鉴的文章:

微信APP支付-API列表

微信支付SDK与DEMO下载

微信APP支付-JAVA

如果问题,请提醒修正。

最新回复(0)