ScanPayBusiness.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. package com.tencent.business;
  2. import com.tencent.common.Configure;
  3. import com.tencent.common.Log;
  4. import com.tencent.common.Signature;
  5. import com.tencent.common.Util;
  6. import com.tencent.common.report.ReporterFactory;
  7. import com.tencent.common.report.protocol.ReportReqData;
  8. import com.tencent.common.report.service.ReportService;
  9. import com.tencent.protocol.pay_protocol.ScanPayReqData;
  10. import com.tencent.protocol.pay_protocol.ScanPayResData;
  11. import com.tencent.protocol.pay_query_protocol.ScanPayQueryReqData;
  12. import com.tencent.protocol.pay_query_protocol.ScanPayQueryResData;
  13. import com.tencent.protocol.reverse_protocol.ReverseReqData;
  14. import com.tencent.protocol.reverse_protocol.ReverseResData;
  15. import com.tencent.service.ReverseService;
  16. import com.tencent.service.ScanPayQueryService;
  17. import com.tencent.service.ScanPayService;
  18. import org.slf4j.LoggerFactory;
  19. import static java.lang.Thread.sleep;
  20. /**
  21. * User: rizenguo
  22. * Date: 2014/12/1
  23. * Time: 17:05
  24. */
  25. public class ScanPayBusiness {
  26. public ScanPayBusiness() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
  27. scanPayService = new ScanPayService();
  28. scanPayQueryService = new ScanPayQueryService();
  29. reverseService = new ReverseService();
  30. }
  31. public interface ResultListener {
  32. //API返回ReturnCode不合法,支付请求逻辑错误,请仔细检测传过去的每一个参数是否合法,或是看API能否被正常访问
  33. void onFailByReturnCodeError(ScanPayResData scanPayResData);
  34. //API返回ReturnCode为FAIL,支付API系统返回失败,请检测Post给API的数据是否规范合法
  35. void onFailByReturnCodeFail(ScanPayResData scanPayResData);
  36. //支付请求API返回的数据签名验证失败,有可能数据被篡改了
  37. void onFailBySignInvalid(ScanPayResData scanPayResData);
  38. //用户用来支付的二维码已经过期,提示收银员重新扫一下用户微信“刷卡”里面的二维码
  39. void onFailByAuthCodeExpire(ScanPayResData scanPayResData);
  40. //授权码无效,提示用户刷新一维码/二维码,之后重新扫码支付"
  41. void onFailByAuthCodeInvalid(ScanPayResData scanPayResData);
  42. //用户余额不足,换其他卡支付或是用现金支付
  43. void onFailByMoneyNotEnough(ScanPayResData scanPayResData);
  44. //支付失败
  45. void onFail(ScanPayResData scanPayResData);
  46. //支付成功
  47. void onSuccess(ScanPayResData scanPayResData);
  48. }
  49. //打log用
  50. private static Log log = new Log(LoggerFactory.getLogger(ScanPayBusiness.class));
  51. //每次调用订单查询API时的等待时间,因为当出现支付失败的时候,如果马上发起查询不一定就能查到结果,所以这里建议先等待一定时间再发起查询
  52. private int waitingTimeBeforePayQueryServiceInvoked = 5000;
  53. //循环调用订单查询API的次数
  54. private int payQueryLoopInvokedCount = 3;
  55. //每次调用撤销API的等待时间
  56. private int waitingTimeBeforeReverseServiceInvoked = 5000;
  57. private ScanPayService scanPayService;
  58. private ScanPayQueryService scanPayQueryService;
  59. private ReverseService reverseService;
  60. /**
  61. * 直接执行被扫支付业务逻辑(包含最佳实践流程)
  62. *
  63. * @param scanPayReqData 这个数据对象里面包含了API要求提交的各种数据字段
  64. * @param resultListener 商户需要自己监听被扫支付业务逻辑可能触发的各种分支事件,并做好合理的响应处理
  65. * @throws Exception
  66. */
  67. public void run(ScanPayReqData scanPayReqData, ResultListener resultListener) throws Exception {
  68. //--------------------------------------------------------------------
  69. //构造请求“被扫支付API”所需要提交的数据
  70. //--------------------------------------------------------------------
  71. String outTradeNo = scanPayReqData.getOut_trade_no();
  72. //接受API返回
  73. String payServiceResponseString;
  74. long costTimeStart = System.currentTimeMillis();
  75. log.i("支付API返回的数据如下:");
  76. payServiceResponseString = scanPayService.request(scanPayReqData);
  77. long costTimeEnd = System.currentTimeMillis();
  78. long totalTimeCost = costTimeEnd - costTimeStart;
  79. log.i("api请求总耗时:" + totalTimeCost + "ms");
  80. //打印回包数据
  81. log.i(payServiceResponseString);
  82. //将从API返回的XML数据映射到Java对象
  83. ScanPayResData scanPayResData = (ScanPayResData) Util.getObjectFromXML(payServiceResponseString, ScanPayResData.class);
  84. //异步发送统计请求
  85. //*
  86. ReportReqData reportReqData = new ReportReqData(
  87. scanPayReqData.getDevice_info(),
  88. Configure.PAY_API,
  89. (int) (totalTimeCost),//本次请求耗时
  90. scanPayResData.getReturn_code(),
  91. scanPayResData.getReturn_msg(),
  92. scanPayResData.getResult_code(),
  93. scanPayResData.getErr_code(),
  94. scanPayResData.getErr_code_des(),
  95. scanPayResData.getOut_trade_no(),
  96. scanPayReqData.getSpbill_create_ip()
  97. );
  98. long timeAfterReport;
  99. if (Configure.isUseThreadToDoReport()) {
  100. ReporterFactory.getReporter(reportReqData).run();
  101. timeAfterReport = System.currentTimeMillis();
  102. log.i("pay+report总耗时(异步方式上报):" + (timeAfterReport - costTimeStart) + "ms");
  103. } else {
  104. ReportService.request(reportReqData);
  105. timeAfterReport = System.currentTimeMillis();
  106. log.i("pay+report总耗时(同步方式上报):" + (timeAfterReport - costTimeStart) + "ms");
  107. }
  108. if (scanPayResData == null || scanPayResData.getReturn_code() == null) {
  109. log.e("【支付失败】支付请求逻辑错误,请仔细检测传过去的每一个参数是否合法,或是看API能否被正常访问");
  110. resultListener.onFailByReturnCodeError(scanPayResData);
  111. return;
  112. }
  113. if (scanPayResData.getReturn_code().equals("FAIL")) {
  114. //注意:一般这里返回FAIL是出现系统级参数错误,请检测Post给API的数据是否规范合法
  115. log.e("【支付失败】支付API系统返回失败,请检测Post给API的数据是否规范合法");
  116. resultListener.onFailByReturnCodeFail(scanPayResData);
  117. return;
  118. } else {
  119. log.i("支付API系统成功返回数据");
  120. //--------------------------------------------------------------------
  121. //收到API的返回数据的时候得先验证一下数据有没有被第三方篡改,确保安全
  122. //--------------------------------------------------------------------
  123. if (!Signature.checkIsSignValidFromResponseString(payServiceResponseString)) {
  124. log.e("【支付失败】支付请求API返回的数据签名验证失败,有可能数据被篡改了");
  125. resultListener.onFailBySignInvalid(scanPayResData);
  126. return;
  127. }
  128. //获取错误码
  129. String errorCode = scanPayResData.getErr_code();
  130. //获取错误描述
  131. String errorCodeDes = scanPayResData.getErr_code_des();
  132. if (scanPayResData.getResult_code().equals("SUCCESS")) {
  133. //--------------------------------------------------------------------
  134. //1)直接扣款成功
  135. //--------------------------------------------------------------------
  136. log.i("【一次性支付成功】");
  137. resultListener.onSuccess(scanPayResData);
  138. }else{
  139. //出现业务错误
  140. log.i("业务返回失败");
  141. log.i("err_code:" + errorCode);
  142. log.i("err_code_des:" + errorCodeDes);
  143. //业务错误时错误码有好几种,商户重点提示以下几种
  144. if (errorCode.equals("AUTHCODEEXPIRE") || errorCode.equals("AUTH_CODE_INVALID") || errorCode.equals("NOTENOUGH")) {
  145. //--------------------------------------------------------------------
  146. //2)扣款明确失败
  147. //--------------------------------------------------------------------
  148. //对于扣款明确失败的情况直接走撤销逻辑
  149. doReverseLoop(outTradeNo);
  150. //以下几种情况建议明确提示用户,指导接下来的工作
  151. if (errorCode.equals("AUTHCODEEXPIRE")) {
  152. //表示用户用来支付的二维码已经过期,提示收银员重新扫一下用户微信“刷卡”里面的二维码
  153. log.w("【支付扣款明确失败】原因是:" + errorCodeDes);
  154. resultListener.onFailByAuthCodeExpire(scanPayResData);
  155. } else if (errorCode.equals("AUTH_CODE_INVALID")) {
  156. //授权码无效,提示用户刷新一维码/二维码,之后重新扫码支付
  157. log.w("【支付扣款明确失败】原因是:" + errorCodeDes);
  158. resultListener.onFailByAuthCodeInvalid(scanPayResData);
  159. } else if (errorCode.equals("NOTENOUGH")) {
  160. //提示用户余额不足,换其他卡支付或是用现金支付
  161. log.w("【支付扣款明确失败】原因是:" + errorCodeDes);
  162. resultListener.onFailByMoneyNotEnough(scanPayResData);
  163. }
  164. } else if (errorCode.equals("USERPAYING")) {
  165. //--------------------------------------------------------------------
  166. //3)需要输入密码
  167. //--------------------------------------------------------------------
  168. //表示有可能单次消费超过300元,或是免输密码消费次数已经超过当天的最大限制,这个时候提示用户输入密码,商户自己隔一段时间去查单,查询一定次数,看用户是否已经输入了密码
  169. if (doPayQueryLoop(payQueryLoopInvokedCount, outTradeNo)) {
  170. log.i("【需要用户输入密码、查询到支付成功】");
  171. resultListener.onSuccess(scanPayResData);
  172. } else {
  173. log.i("【需要用户输入密码、在一定时间内没有查询到支付成功、走撤销流程】");
  174. doReverseLoop(outTradeNo);
  175. resultListener.onFail(scanPayResData);
  176. }
  177. } else {
  178. //--------------------------------------------------------------------
  179. //4)扣款未知失败
  180. //--------------------------------------------------------------------
  181. if (doPayQueryLoop(payQueryLoopInvokedCount, outTradeNo)) {
  182. log.i("【支付扣款未知失败、查询到支付成功】");
  183. resultListener.onSuccess(scanPayResData);
  184. } else {
  185. log.i("【支付扣款未知失败、在一定时间内没有查询到支付成功、走撤销流程】");
  186. doReverseLoop(outTradeNo);
  187. resultListener.onFail(scanPayResData);
  188. }
  189. }
  190. }
  191. }
  192. }
  193. /**
  194. * 进行一次支付订单查询操作
  195. *
  196. * @param outTradeNo 商户系统内部的订单号,32个字符内可包含字母, [确保在商户系统唯一]
  197. * @return 该订单是否支付成功
  198. * @throws Exception
  199. */
  200. private boolean doOnePayQuery(String outTradeNo) throws Exception {
  201. sleep(waitingTimeBeforePayQueryServiceInvoked);//等待一定时间再进行查询,避免状态还没来得及被更新
  202. String payQueryServiceResponseString;
  203. ScanPayQueryReqData scanPayQueryReqData = new ScanPayQueryReqData("",outTradeNo);
  204. payQueryServiceResponseString = scanPayQueryService.request(scanPayQueryReqData);
  205. log.i("支付订单查询API返回的数据如下:");
  206. log.i(payQueryServiceResponseString);
  207. //将从API返回的XML数据映射到Java对象
  208. ScanPayQueryResData scanPayQueryResData = (ScanPayQueryResData) Util.getObjectFromXML(payQueryServiceResponseString, ScanPayQueryResData.class);
  209. if (scanPayQueryResData == null || scanPayQueryResData.getReturn_code() == null) {
  210. log.i("支付订单查询请求逻辑错误,请仔细检测传过去的每一个参数是否合法");
  211. return false;
  212. }
  213. if (scanPayQueryResData.getReturn_code().equals("FAIL")) {
  214. //注意:一般这里返回FAIL是出现系统级参数错误,请检测Post给API的数据是否规范合法
  215. log.i("支付订单查询API系统返回失败,失败信息为:" + scanPayQueryResData.getReturn_msg());
  216. return false;
  217. } else {
  218. if (scanPayQueryResData.getResult_code().equals("SUCCESS")) {//业务层成功
  219. if (scanPayQueryResData.getTrade_state().equals("SUCCESS")) {
  220. //表示查单结果为“支付成功”
  221. log.i("查询到订单支付成功");
  222. return true;
  223. } else {
  224. //支付不成功
  225. log.i("查询到订单支付不成功");
  226. return false;
  227. }
  228. } else {
  229. log.i("查询出错,错误码:" + scanPayQueryResData.getErr_code() + " 错误信息:" + scanPayQueryResData.getErr_code_des());
  230. return false;
  231. }
  232. }
  233. }
  234. /**
  235. * 由于有的时候是因为服务延时,所以需要商户每隔一段时间(建议5秒)后再进行查询操作,多试几次(建议3次)
  236. *
  237. * @param loopCount 循环次数,至少一次
  238. * @param outTradeNo 商户系统内部的订单号,32个字符内可包含字母, [确保在商户系统唯一]
  239. * @return 该订单是否支付成功
  240. * @throws InterruptedException
  241. */
  242. private boolean doPayQueryLoop(int loopCount, String outTradeNo) throws Exception {
  243. //至少查询一次
  244. if (loopCount == 0) {
  245. loopCount = 1;
  246. }
  247. //进行循环查询
  248. for (int i = 0; i < loopCount; i++) {
  249. if (doOnePayQuery(outTradeNo)) {
  250. return true;
  251. }
  252. }
  253. return false;
  254. }
  255. //是否需要再调一次撤销,这个值由撤销API回包的recall字段决定
  256. private boolean needRecallReverse = false;
  257. /**
  258. * 进行一次撤销操作
  259. *
  260. * @param outTradeNo 商户系统内部的订单号,32个字符内可包含字母, [确保在商户系统唯一]
  261. * @return 该订单是否支付成功
  262. * @throws Exception
  263. */
  264. private boolean doOneReverse(String outTradeNo) throws Exception {
  265. sleep(waitingTimeBeforeReverseServiceInvoked);//等待一定时间再进行查询,避免状态还没来得及被更新
  266. String reverseResponseString;
  267. ReverseReqData reverseReqData = new ReverseReqData("",outTradeNo);
  268. reverseResponseString = reverseService.request(reverseReqData);
  269. log.i("撤销API返回的数据如下:");
  270. log.i(reverseResponseString);
  271. //将从API返回的XML数据映射到Java对象
  272. ReverseResData reverseResData = (ReverseResData) Util.getObjectFromXML(reverseResponseString, ReverseResData.class);
  273. if (reverseResData == null) {
  274. log.i("支付订单撤销请求逻辑错误,请仔细检测传过去的每一个参数是否合法");
  275. return false;
  276. }
  277. if (reverseResData.getReturn_code().equals("FAIL")) {
  278. //注意:一般这里返回FAIL是出现系统级参数错误,请检测Post给API的数据是否规范合法
  279. log.i("支付订单撤销API系统返回失败,失败信息为:" + reverseResData.getReturn_msg());
  280. return false;
  281. } else {
  282. if (reverseResData.getResult_code().equals("FAIL")) {
  283. log.i("撤销出错,错误码:" + reverseResData.getErr_code() + " 错误信息:" + reverseResData.getErr_code_des());
  284. if (reverseResData.getRecall().equals("Y")) {
  285. //表示需要重试
  286. needRecallReverse = true;
  287. return false;
  288. } else {
  289. //表示不需要重试,也可以当作是撤销成功
  290. needRecallReverse = false;
  291. return true;
  292. }
  293. } else {
  294. //查询成功,打印交易状态
  295. log.i("支付订单撤销成功");
  296. return true;
  297. }
  298. }
  299. }
  300. /**
  301. * 由于有的时候是因为服务延时,所以需要商户每隔一段时间(建议5秒)后再进行查询操作,是否需要继续循环调用撤销API由撤销API回包里面的recall字段决定。
  302. *
  303. * @param outTradeNo 商户系统内部的订单号,32个字符内可包含字母, [确保在商户系统唯一]
  304. * @throws InterruptedException
  305. */
  306. private void doReverseLoop(String outTradeNo) throws Exception {
  307. //初始化这个标记
  308. needRecallReverse = true;
  309. //进行循环撤销,直到撤销成功,或是API返回recall字段为"Y"
  310. while (needRecallReverse) {
  311. if (doOneReverse(outTradeNo)) {
  312. return;
  313. }
  314. }
  315. }
  316. /**
  317. * 设置循环多次调用订单查询API的时间间隔
  318. *
  319. * @param duration 时间间隔,默认为10秒
  320. */
  321. public void setWaitingTimeBeforePayQueryServiceInvoked(int duration) {
  322. waitingTimeBeforePayQueryServiceInvoked = duration;
  323. }
  324. /**
  325. * 设置循环多次调用订单查询API的次数
  326. *
  327. * @param count 调用次数,默认为三次
  328. */
  329. public void setPayQueryLoopInvokedCount(int count) {
  330. payQueryLoopInvokedCount = count;
  331. }
  332. /**
  333. * 设置循环多次调用撤销API的时间间隔
  334. *
  335. * @param duration 时间间隔,默认为5秒
  336. */
  337. public void setWaitingTimeBeforeReverseServiceInvoked(int duration) {
  338. waitingTimeBeforeReverseServiceInvoked = duration;
  339. }
  340. public void setScanPayService(ScanPayService service) {
  341. scanPayService = service;
  342. }
  343. public void setScanPayQueryService(ScanPayQueryService service) {
  344. scanPayQueryService = service;
  345. }
  346. public void setReverseService(ReverseService service) {
  347. reverseService = service;
  348. }
  349. }