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

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

 
  1. public class WxPayConfigImpl extends WXPayConfig{

  2.  
  3. // 设置应用Id

  4. public static String appid = "2019102168481752";

  5. // 设置商户号

  6. public static String mch_id = "1230000109";

  7. // 设置设备号(终端设备号(门店号或收银设备ID),默认请传"WEB",非必需)

  8. public static String device_info = "WEB";

  9. // 设置字符集

  10. public static String key = "192006250b4c09247ec02edce69f6a2d";

  11.  
  12. private static WxPayConfigImpl INSTANCE;

  13.  
  14. public static WxPayConfigImpl getInstance() throws Exception {

  15. if (INSTANCE == null) {

  16. synchronized (WxPayConfigImpl.class) {

  17. if (INSTANCE == null) {

  18. INSTANCE = new WxPayConfigImpl();

  19. }

  20. }

  21. }

  22. return INSTANCE;

  23. }

  24.  
  25. @Override

  26. public String getAppID() {

  27. return appid;

  28. }

  29. @Override

  30. public String getMchID() {

  31. return mch_id;

  32. }

  33. @Override

  34. public String getKey() {

  35. return key;

  36. }

  37. @Override

  38. public InputStream getCertStream() {

  39. //这个可以看我另一篇“支付宝:APP支付接口2.0(alipay.trade.app.pay)”

  40. //里的“使用demo”步骤,有讲解,如果是个springboot构成的jar,如何设置证书路径

  41. //文章链接:https://blog.csdn.net/u014799292/article/details/102680149

  42. String fileUrl = "证书路径";

  43. InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileUrl);

  44. byte[] certData = null;

  45. try {

  46. certData = IOUtils.toByteArray(certStream);

  47. } catch (IOException e) {

  48. // TODO Auto-generated catch block

  49. e.printStackTrace();

  50. }finally{

  51. if(certStream != null){

  52. try {

  53. certStream.close();

  54. } catch (IOException e) {

  55. // TODO Auto-generated catch block

  56. e.printStackTrace();

  57. }

  58. }

  59. }

  60. return new ByteArrayInputStream(certData);

  61. }

  62. @Override

  63. public IWXPayDomain getWXPayDomain() {

  64. return WxPayDomainImpl.instance();

  65. }

  66. }

 
  1. public class WxPayDomainImpl implements IWXPayDomain {

  2.  
  3. private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000; //3 minutes

  4. private long switchToAlternateDomainTime = 0;

  5. private Map<String, DomainStatics> domainData = new HashMap<String, DomainStatics>();

  6.  
  7. private WxPayDomainImpl(){

  8.  
  9. }

  10. private static class WxpayDomainHolder{

  11. private static IWXPayDomain holder = new WxPayDomainImpl();

  12. }

  13.  
  14. public static IWXPayDomain instance(){

  15. return WxpayDomainHolder.holder;

  16. }

  17.  
  18. @Override

  19. public void report(String domain, long elapsedTimeMillis, Exception ex) {

  20.  
  21. DomainStatics info = domainData.get(domain);

  22. if(info == null){

  23. info = new DomainStatics(domain);

  24. domainData.put(domain, info);

  25. }

  26.  
  27. if(ex == null){ //success

  28. if(info.succCount >= 2){ //continue succ, clear error count

  29. info.connectTimeoutCount = info.dnsErrorCount = info.otherErrorCount = 0;

  30. }else{

  31. ++info.succCount;

  32. }

  33. }else if(ex instanceof ConnectTimeoutException){

  34. info.succCount = info.dnsErrorCount = 0;

  35. ++info.connectTimeoutCount;

  36. }else if(ex instanceof UnknownHostException){

  37. info.succCount = 0;

  38. ++info.dnsErrorCount;

  39. }else{

  40. info.succCount = 0;

  41. ++info.otherErrorCount;

  42. }

  43. }

  44.  
  45. @Override

  46. public DomainInfo getDomain(WXPayConfig config) {

  47.  
  48. DomainStatics primaryDomain = domainData.get(WXPayConstants.DOMAIN_API);

  49. if(primaryDomain == null ||

  50. primaryDomain.isGood()) {

  51. return new DomainInfo(WXPayConstants.DOMAIN_API, true);

  52. }

  53.  
  54. long now = System.currentTimeMillis();

  55. if(switchToAlternateDomainTime == 0){ //first switch

  56. switchToAlternateDomainTime = now;

  57. return new DomainInfo(WXPayConstants.DOMAIN_API2, false);

  58. }else if(now - switchToAlternateDomainTime < MIN_SWITCH_PRIMARY_MSEC){

  59. DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);

  60. if(alternateDomain == null ||

  61. alternateDomain.isGood() ||

  62. alternateDomain.badCount() < primaryDomain.badCount()){

  63. return new DomainInfo(WXPayConstants.DOMAIN_API2, false);

  64. }else{

  65. return new DomainInfo(WXPayConstants.DOMAIN_API, true);

  66. }

  67. }else{ //force switch back

  68. switchToAlternateDomainTime = 0;

  69. primaryDomain.resetCount();

  70. DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);

  71. if(alternateDomain != null)

  72. alternateDomain.resetCount();

  73. return new DomainInfo(WXPayConstants.DOMAIN_API, true);

  74. }

  75. }

  76.  
  77. static class DomainStatics {

  78. final String domain;

  79. int succCount = 0;

  80. int connectTimeoutCount = 0;

  81. int dnsErrorCount =0;

  82. int otherErrorCount = 0;

  83.  
  84. DomainStatics(String domain) {

  85. this.domain = domain;

  86. }

  87. void resetCount(){

  88. succCount = connectTimeoutCount = dnsErrorCount = otherErrorCount = 0;

  89. }

  90. boolean isGood(){ return connectTimeoutCount <= 2 && dnsErrorCount <= 2; }

  91. int badCount(){

  92. return connectTimeoutCount + dnsErrorCount * 5 + otherErrorCount / 4;

  93. }

  94. }

  95.  
  96. }

给 WXPayUtil  再添加几个时间工具方法(看自己需求定制),项目会在最后附链接。

appid & 商户号位置

登录商户平台-产品中心-账号关联(AppID绑定),进入授权申请页面;

密钥key位置

登录商户平台-->>账户中心-->>API安全-->>设置API密钥(32位,下面给出生成方式,然后复制粘贴到秘钥位置,记得保留,之后无法查看,只能再次生成,测试环境随便改,正式服记得最好固定一次,修改可能会引起服务端数据错误)

 
  1.  
  2. private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

  3.  
  4. private static final Random RANDOM = new SecureRandom();

  5.  
  6. /**

  7. * 获取随机字符串 Nonce Str

  8. *

  9. * @return String 随机字符串

  10. */

  11. public static String generateNonceStr() {

  12. char[] nonceChars = new char[32];

  13. for (int index = 0; index < nonceChars.length; ++index) {

  14. nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));

  15. }

  16. return new String(nonceChars);

  17. }

再来写一个请求类,处理微信订单(订单--统一下单/调起支付接口/支付结果通知/查询订单/关闭订单)

 
  1. /**

  2. * 订单--统一下单/调起支付接口/退款/结果通知/查询订单/关闭订单

  3. * 借鉴:https://blog.csdn.net/asd54090/article/details/81028323

  4. */

  5. public class WxAppPayRequest {

  6.  
  7. private static final Logger logger = LoggerFactory.getLogger(WxAppPayRequest.class);

  8.  
  9. private WxPayConfigImpl config;

  10. private WXPay wxpay;

  11.  
  12. /**

  13. * 微信支付请求

  14. */

  15. public WxAppPayRequest() {

  16. try {

  17. config = WxPayConfigImpl.getInstance();

  18. wxpay = new WXPay(config);

  19. } catch (Exception e) {

  20. e.printStackTrace();

  21. logger.error("微信配置初始化错误", e);

  22. }

  23. }

  24.  
  25. /**

  26. * APP支付订单请求

  27. *

  28. * @param body

  29. * 格式:APP名字-实际商品名称,如:天天爱消除-游戏充值

  30. * @param attach

  31. * 附加数据,在查询API和支付通知中原样返回

  32. * @param outTradeNo

  33. * 商户订单号

  34. * @param totalFee

  35. * 总金额

  36. * @param startTime

  37. * 订单开始时间String格式: yyyy-MM-dd HH:mm:ss

  38. * @param expireMinute

  39. * 有效时间(分钟)

  40. * @param notifyUrl

  41. * 微信支付异步通知回调地址

  42. * @return

  43. */

  44. private WxAppPayResponseCode getOrderSign(String body, String attach, String outTradeNo, BigDecimal totalFee,

  45. String startTime, int expireMinute, String notifyUrl) {

  46.  
  47. // 准备好请求参数

  48. Map<String, String> map = new HashMap<String, String>();

  49. map.put("device_info", WxPayConfigImpl.device_info);

  50. map.put("body", body);

  51. if (attach != null && !attach.isEmpty()) {

  52. map.put("attach", attach);

  53. }

  54. map.put("out_trade_no", outTradeNo);

  55. map.put("total_fee", totalFee.toString());

  56. map.put("spbill_create_ip", WXPayUtil.getLocalAddress());

  57. map.put("time_start", WXPayUtil.getFormatTime(startTime));

  58. String endTime = WXPayUtil.getNSecondTime(startTime, expireMinute);

  59. map.put("time_expire", WXPayUtil.getFormatTime(endTime));

  60. map.put("notify_url", notifyUrl);

  61. map.put("trade_type", "APP");

  62.  
  63. // 生成带sign的xml字符串

  64. Map<String, String> unifiedOrderMap = null;

  65. try {

  66. unifiedOrderMap = wxpay.unifiedOrder(map);

  67. if (unifiedOrderMap == null || (unifiedOrderMap != null

  68. && WxAppPayResponseCode.FAIL.code().equals(unifiedOrderMap.get("return_code")))) {

  69. String errorMsg = "调用微信“统一下单”获取prepayid 失败...";

  70. logger.info("getOrderSign --unifiedOrder: 调用微信“统一下单”获取prepayid 失败.");

  71. logger.info("getOrderSign --unifiedOrder: 请求参数:" + map.toString());

  72. logger.info("getOrderSign --unifiedOrder: 返回Map:" + unifiedOrderMap);

  73. if (unifiedOrderMap != null) {

  74. errorMsg += " 异常信息为:" + unifiedOrderMap.get("return_msg");

  75. }

  76. WxAppPayResponseCode error = WxAppPayResponseCode.ERROR;

  77. error.setAlias(errorMsg);

  78. return error;

  79. }

  80. } catch (Exception e) {

  81. // TODO Auto-generated catch block

  82. e.printStackTrace();

  83. logger.error("getOrderSign : 调用微信“统一下单”失败 。", e);

  84. WxAppPayResponseCode error = WxAppPayResponseCode.ERROR;

  85. error.setAlias("调用微信“统一下单”失败 。" + e.toString());

  86. return error;

  87. }

  88.  
  89. // 调用微信请求成功,但响应失败

  90. String resultCode = unifiedOrderMap.get("result_code");

  91. if (WxAppPayResponseCode.FAIL.code().equals(resultCode)) {

  92. WxAppPayResponseCode error = WxAppPayResponseCode.findCode(unifiedOrderMap.get("err_code"));

  93. return error;

  94. }

  95.  
  96. return parseWXOrderResponse(unifiedOrderMap);

  97. }

  98.  
  99. /**

  100. * 将map转成客户端订单用的封装体

  101. *

  102. * @param map

  103. * map

  104. * @return 用户端用的封装体

  105. */

  106. private WxAppPayResponseCode parseWXOrderResponse(Map<String, String> map) {

  107.  
  108. WxOrderResponse response = new WxOrderResponse();

  109. response.setAppid(map.get("appid"));

  110. response.setPartnerid(map.get("partnerid"));

  111. response.setPrepayid(map.get("prepay_id"));

  112. response.setPack("Sign=WXPay");

  113. response.setNoncestr(map.get("noncestr"));

  114. String timestamp = WXPayUtil.getCurrentTimestamp() + "";

  115. response.setTimestamp(timestamp);

  116.  
  117. // 前人踩坑,咱们乘凉

  118. // sgin(签名),不是拿微信“统一下单”返回的sgin,而是自己再签一次,返回给客户端

  119. // 签名的参数拿的不是“统一下单”,而是拿“调起支付接口”里面的参数,这步API文档写的是客户端生成,不过咱们服务端就帮他做了

  120. // 注意:map的key不能是大写

  121. Map<String, String> params = new HashMap<>();

  122. params.put("appid", map.get("appid"));

  123. params.put("partnerid", map.get("partnerid"));

  124. params.put("prepayid", map.get("prepay_id"));

  125. params.put("package", "Sign=WXPay");

  126. params.put("noncestr", map.get("nonce_str"));

  127. params.put("timestamp", timestamp);

  128. try {

  129. // 这个sign是移动端要请求微信服务端的,也是我们要保存后面校验的

  130. String sgin = WXPayUtil.generateSignature(params, config.getKey());

  131. response.setSign(sgin);

  132. } catch (Exception e) {

  133. e.printStackTrace();

  134. logger.error("parseWXOrderResponse : 订单第二次供客户端签名信息失败 。");

  135. logger.error("parseWXOrderResponse : 请求参数:" + params.toString());

  136. logger.error("parseWXOrderResponse : 返回错误信息:", e);

  137. WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR;

  138. errorData.setAlias("调用支付接口生成签名sign失败!");

  139. return errorData;

  140. }

  141. WxAppPayResponseCode successData = WxAppPayResponseCode.SUCCESS;

  142. successData.setAlias(JSONObject.toJSONString(response));

  143. return successData;

  144. }

  145.  
  146. /**

  147. * 查微信订单

  148. *

  149. * @param outTradeNo

  150. * 订单号

  151. */

  152. public WxAppPayResponseCode queryOrderByOutTradeNo(String outTradeNo) {

  153.  
  154. logger.info("查询微信支付订单信息,订单号为:" + outTradeNo);

  155. HashMap<String, String> data = new HashMap<String, String>();

  156. data.put("out_trade_no", outTradeNo);

  157. try {

  158. Map<String, String> orderQueryMap = wxpay.orderQuery(data);

  159. return disposeReturnInfo(orderQueryMap);

  160. } catch (Exception e) {

  161. e.printStackTrace();

  162. logger.error("queryOrderByOutTradeNo : 查询微信订单支付信息失败 。订单号:" + outTradeNo);

  163. logger.error("queryOrderByOutTradeNo : 返回错误信息:", e);

  164. WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR;

  165. errorData.setAlias("调用查微信订单支付信息接口失败!");

  166. return errorData;

  167. }

  168. }

  169.  
  170. /**

  171. * 关闭订单(刚刚生成的订单不能立马关闭,要间隔5分钟,请自行做好判断)

  172. *

  173. * @param outTradeNo

  174. * 订单号

  175. * @return

  176. */

  177. public WxAppPayResponseCode closeOrder(String outTradeNo) {

  178.  
  179. logger.info("关闭微信支付订单信息,订单号为:" + outTradeNo);

  180. HashMap<String, String> data = new HashMap<>();

  181. data.put("out_trade_no", outTradeNo);

  182. try {

  183. Map<String, String> closeOrderMap = wxpay.closeOrder(data);

  184. return disposeReturnInfo(closeOrderMap);

  185. } catch (Exception e) {

  186. e.printStackTrace();

  187. logger.error("closeOrder : 微信关闭订单失败 。订单号:" + outTradeNo);

  188. logger.error("closeOrder : 返回错误信息:", e);

  189. WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR;

  190. errorData.setAlias("调用查微信订单支付信息接口失败!");

  191. return errorData;

  192. }

  193. }

  194.  
  195. /**

  196. * 微信退款申请

  197. *

  198. * @param outTradeNo

  199. * 商户订单号

  200. * @param amount

  201. * 金额

  202. * @param refund_desc

  203. * 退款原因(可空)

  204. * @param notifyUrl

  205. * 退款异步通知链接

  206. *

  207. * @return 返回map(已做过签名验证),具体数据参见微信退款API

  208. */

  209. public WxAppPayResponseCode refundOrder(String outTradeNo, BigDecimal amount, String refundDesc, String notifyUrl)

  210. throws Exception {

  211.  
  212. Map<String, String> data = new HashMap<String, String>();

  213. data.put("out_trade_no", outTradeNo);

  214. data.put("out_refund_no", outTradeNo);

  215. data.put("total_fee", amount + "");

  216. data.put("refund_fee", amount + "");

  217. data.put("refund_fee_type", "CNY");

  218. data.put("refund_desc", refundDesc);

  219. data.put("notifyUrl", notifyUrl);

  220.  
  221. try {

  222. Map<String, String> refundOrderMap = wxpay.refund(data);

  223. return disposeReturnInfo(refundOrderMap);

  224. } catch (Exception e) {

  225. e.printStackTrace();

  226. logger.error("closeOrder : 微信退款申请失败 。订单号:" + outTradeNo);

  227. logger.error("closeOrder : 返回错误信息:", e);

  228. WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR;

  229. errorData.setAlias("调用微信退款申请信息接口失败!");

  230. return errorData;

  231. }

  232. }

  233.  
  234. /**

  235. * 查微信退款订单 注:如果单个支付订单部分退款次数超过20次请使用退款单号查询

  236. *

  237. * @param outTradeNo

  238. * 订单号

  239. */

  240. public WxAppPayResponseCode queryRefundOrderByOutTradeNo(String outTradeNo) {

  241.  
  242. logger.info("查询微信支付订单信息,订单号为:" + outTradeNo);

  243. HashMap<String, String> data = new HashMap<String, String>();

  244. data.put("out_trade_no", outTradeNo);

  245. try {

  246. Map<String, String> refundQueryMap = wxpay.refundQuery(data);

  247. return disposeReturnInfo(refundQueryMap);

  248. } catch (Exception e) {

  249. e.printStackTrace();

  250. logger.error("queryRefundOrderByOutTradeNo : 查询微信退款订单信息失败 。订单号:" + outTradeNo);

  251. logger.error("queryRefundOrderByOutTradeNo : 返回错误信息:", e);

  252. WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR;

  253. errorData.setAlias("调用查微信退款订单接口失败!");

  254. return errorData;

  255. }

  256. }

  257.  
  258. /**

  259. * 对接口接收成功后的返回进行处理

  260. *

  261. * @param resultMap

  262. * @return

  263. */

  264. private WxAppPayResponseCode disposeReturnInfo(Map<String, String> resultMap) {

  265.  
  266. if (resultMap == null

  267. || (resultMap != null && WxAppPayResponseCode.FAIL.code().equals(resultMap.get("return_code")))) {

  268. WxAppPayResponseCode errorData = WxAppPayResponseCode.ERROR;

  269. errorData.setAlias("调用微信接口失败!返回数据为 : " + resultMap);

  270. return errorData;

  271. }

  272.  
  273. if (WxAppPayResponseCode.FAIL.code().equals(resultMap.get("result_code"))) {

  274. WxAppPayResponseCode errorData = WxAppPayResponseCode.findCode(resultMap.get("err_code"));

  275. return errorData;

  276. }

  277.  
  278. WxAppPayResponseCode successData = WxAppPayResponseCode.SUCCESS;

  279. successData.setAlias(JSONObject.toJSONString(resultMap));

  280. return successData;

  281. }

  282.  
  283. /**

  284. * 是否成功接收微信支付回调 用于回复微信,否则微信回默认为商户后端没有收到回调

  285. *

  286. * @return

  287. */

  288. public String returnWXPayVerifyMsg() {

  289. return "<xml>\n" + "\n" + " <return_code><![CDATA[SUCCESS]]></return_code>\n"

  290. + " <return_msg><![CDATA[OK]]></return_msg>\n" + "</xml>";

  291. }

  292.  
  293. public static void main(String[] args) {

  294.  
  295. WxAppPayRequest pay = new WxAppPayRequest();

  296. WxAppPayResponseCode orderSign = pay.getOrderSign("APP-商品订单支付", "data1;data2", "212458542512542542",

  297. new BigDecimal("100.12"), "2019-01-02 23:55:14", 10, "http://XXX");

  298. System.out.println(orderSign);

  299. }

  300.  
  301. }

 

OK,基本搞定,构建成一个处理微信订单功能的JAR。

项目已推送github:https://github.com/leopardF/wxpay

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

微信APP支付-API列表

微信支付SDK与DEMO下载

微信APP支付-JAVA

如果问题,请提醒修正。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值