微信:APP支付-JAVA服务端-订单流程

本章文献基本都来源于微信支付平台,详情请看微信官方文档:APP支付

系统交互图

微信:APP支付-JAVA服务端-订单流程_第1张图片

文档位置: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 domainData = new HashMap();

    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绑定),进入授权申请页面;

微信:APP支付-JAVA服务端-订单流程_第2张图片

密钥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 map = new HashMap();
		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 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 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 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 data = new HashMap();
		data.put("out_trade_no", outTradeNo);
		try {
			Map 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 data = new HashMap<>();
		data.put("out_trade_no", outTradeNo);
		try {
			Map 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 data = new HashMap();
		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 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 data = new HashMap();
		data.put("out_trade_no", outTradeNo);
		try {
			Map 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 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 "\n" + "\n" + "  \n"
				+ "  \n" + "";
	}

	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

如果问题,请提醒修正。

 

 

 

你可能感兴趣的:(第三方集成,java)