java實(shí)現(xiàn)微信App支付服務(wù)端
微信App支付服務(wù)端的實(shí)現(xiàn)方法,供大家參考,具體內(nèi)容如下
引言
主要實(shí)現(xiàn)app支付統(tǒng)一下單、異步通知、調(diào)起支付接口、支付訂單查詢(xún)、申請(qǐng)退款、查詢(xún)退款功能;封裝了https對(duì)發(fā)起退款的證書(shū)校驗(yàn)、簽名、xml解析等。
支付流程
具體支付流程參考“微信APP”文檔,文檔地址
APP支付:APP端點(diǎn)擊下單—-服務(wù)端生成訂單,并調(diào)起“統(tǒng)一下單”,返回app支付所需參數(shù)—–APP端“調(diào)起支付接口“,發(fā)起支付—-微信服務(wù)器端調(diào)用服務(wù)端回調(diào)地址—–服務(wù)端按照“支付結(jié)果通知”,處理支付結(jié)果
app查詢(xún):調(diào)起“查詢(xún)訂單”
APP退款:發(fā)起退款請(qǐng)求,調(diào)用“申請(qǐng)退款”,發(fā)起退款,需雙向證書(shū)驗(yàn)證
APP退款查詢(xún):調(diào)起“查詢(xún)退款”
支付代碼實(shí)現(xiàn)
代碼實(shí)現(xiàn)簽名、證書(shū)校驗(yàn)、http和https封裝等,項(xiàng)目結(jié)構(gòu)如下:
支付代碼
包含支付、支付查詢(xún)、異步通知、退款申請(qǐng)、退款查詢(xún)
package org.andy.wxpay.controller; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.andy.wxpay.model.JsonResult; import org.andy.wxpay.model.ResponseData; import org.andy.wxpay.utils.CollectionUtil; import org.andy.wxpay.utils.ConfigUtil; import org.andy.wxpay.utils.FileUtil; import org.andy.wxpay.utils.HttpUtils; import org.andy.wxpay.utils.PayUtil; import org.andy.wxpay.utils.SerializerFeatureUtil; import org.andy.wxpay.utils.StringUtil; import org.andy.wxpay.utils.WebUtil; import org.andy.wxpay.utils.XmlUtil; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import com.alibaba.fastjson.JSON; /** * 創(chuàng)建時(shí)間:2016年11月2日 下午4:16:32 * * @author andy * @version 2.2 */ @Controller @RequestMapping("/order") public class PayController { private static final Logger LOG = Logger.getLogger(PayController.class); private static final String ORDER_PAY = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 統(tǒng)一下單 private static final String ORDER_PAY_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery"; // 支付訂單查詢(xún) private static final String ORDER_REFUND = "https://api.mch.weixin.qq.com/secapi/pay/refund"; // 申請(qǐng)退款 private static final String ORDER_REFUND_QUERY = "https://api.mch.weixin.qq.com/pay/refundquery"; // 申請(qǐng)退款 private static final String APP_ID = ConfigUtil.getProperty("wx.appid"); private static final String MCH_ID = ConfigUtil.getProperty("wx.mchid"); private static final String API_SECRET = ConfigUtil.getProperty("wx.api.secret"); /** * 支付下訂單 * * @param request * @param response * @param cashnum * 支付金額 * @param mercid * 商品id * @param callback */ @RequestMapping(value = "/pay", method = RequestMethod.POST) public void orderPay(HttpServletRequest request, HttpServletResponse response, @RequestParam(required = false, defaultValue = "0") Double cashnum, String mercid, String callback) { LOG.info("[/order/pay]"); if (!"001".equals(mercid)) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "商品不存在", new ResponseData()), SerializerFeatureUtil.FEATURES))); } Map<String, String> restmap = null; boolean flag = true; // 是否訂單創(chuàng)建成功 try { String total_fee = BigDecimal.valueOf(cashnum).multiply(BigDecimal.valueOf(100)) .setScale(0, BigDecimal.ROUND_HALF_UP).toString(); Map<String, String> parm = new HashMap<String, String>(); parm.put("appid", APP_ID); parm.put("mch_id", MCH_ID); parm.put("device_info", "WEB"); parm.put("nonce_str", PayUtil.getNonceStr()); parm.put("body", "測(cè)試付費(fèi)"); parm.put("attach", "Andy"); parm.put("out_trade_no", PayUtil.getTradeNo()); parm.put("total_fee", total_fee); parm.put("spbill_create_ip", PayUtil.getRemoteAddrIp(request)); parm.put("notify_url", "https://www.andy.org/wxpay/order/pay/notify.shtml"); parm.put("trade_type", "APP"); parm.put("sign", PayUtil.getSign(parm, API_SECRET)); String restxml = HttpUtils.post(ORDER_PAY, XmlUtil.xmlFormat(parm, false)); restmap = XmlUtil.xmlParse(restxml); } catch (Exception e) { LOG.error(e.getMessage(), e); } Map<String, String> payMap = new HashMap<String, String>(); if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) { payMap.put("appid", APP_ID); payMap.put("partnerid", MCH_ID); payMap.put("prepayid", restmap.get("prepay_id")); payMap.put("package", "Sign=WXPay"); payMap.put("noncestr", PayUtil.getNonceStr()); payMap.put("timestamp", PayUtil.payTimestamp()); try { payMap.put("sign", PayUtil.getSign(payMap, API_SECRET)); } catch (Exception e) { flag = false; } } if (flag) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON.toJSONString(new JsonResult(1, "訂單獲取成功", new ResponseData(null, payMap)), SerializerFeatureUtil.FEATURES))); } else { if (CollectionUtil.isNotEmpty(restmap)) { LOG.info("訂單創(chuàng)建失?。? + restmap.get("err_code") + ":" + restmap.get("err_code_des")); } WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單獲取失敗", new ResponseData()), SerializerFeatureUtil.FEATURES))); } } /** * 查詢(xún)支付結(jié)果 * * @param request * @param response * @param tradeid 微信交易訂單號(hào) * @param tradeno 商品訂單號(hào) * @param callback */ @RequestMapping(value = "/pay/query", method = RequestMethod.POST) public void orderPayQuery(HttpServletRequest request, HttpServletResponse response, String tradeid, String tradeno, String callback) { LOG.info("[/order/pay/query]"); if (StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(tradeid)) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單號(hào)不能為空", new ResponseData()), SerializerFeatureUtil.FEATURES))); } Map<String, String> restmap = null; try { Map<String, String> parm = new HashMap<String, String>(); parm.put("appid", APP_ID); parm.put("mch_id", MCH_ID); parm.put("transaction_id", tradeid); parm.put("out_trade_no", tradeno); parm.put("nonce_str", PayUtil.getNonceStr()); parm.put("sign", PayUtil.getSign(parm, API_SECRET)); String restxml = HttpUtils.post(ORDER_PAY_QUERY, XmlUtil.xmlFormat(parm, false)); restmap = XmlUtil.xmlParse(restxml); } catch (Exception e) { LOG.error(e.getMessage(), e); } if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) { // 訂單查詢(xún)成功 處理業(yè)務(wù)邏輯 LOG.info("訂單查詢(xún):訂單" + restmap.get("out_trade_no") + "支付成功"); WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(1, "訂單支付成功", new ResponseData()), SerializerFeatureUtil.FEATURES))); } else { if (CollectionUtil.isNotEmpty(restmap)) { LOG.info("訂單支付失?。? + restmap.get("err_code") + ":" + restmap.get("err_code_des")); } WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單支付失敗", new ResponseData()), SerializerFeatureUtil.FEATURES))); } } /** * 訂單支付微信服務(wù)器異步通知 * * @param request * @param response */ @RequestMapping("/pay/notify") public void orderPayNotify(HttpServletRequest request, HttpServletResponse response) { LOG.info("[/order/pay/notify]"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/xml"); try { ServletInputStream in = request.getInputStream(); String resxml = FileUtil.readInputStream2String(in); Map<String, String> restmap = XmlUtil.xmlParse(resxml); LOG.info("支付結(jié)果通知:" + restmap); if ("SUCCESS".equals(restmap.get("result_code"))) { // 訂單支付成功 業(yè)務(wù)處理 String out_trade_no = restmap.get("out_trade_no"); // 商戶(hù)訂單號(hào) // 通過(guò)商戶(hù)訂單判斷是否該訂單已經(jīng)處理 如果處理跳過(guò) 如果未處理先校驗(yàn)sign簽名 再進(jìn)行訂單業(yè)務(wù)相關(guān)的處理 String sing = restmap.get("sign"); // 返回的簽名 restmap.remove("sign"); String signnow = PayUtil.getSign(restmap, API_SECRET); if (signnow.equals(sing)) { // 進(jìn)行業(yè)務(wù)處理 LOG.info("訂單支付通知: 支付成功,訂單號(hào)" + out_trade_no); // 處理成功后相應(yīng)給響應(yīng)xml Map<String, String> respMap = new HashMap<>(); respMap = new HashMap<String, String>(); respMap.put("return_code", "SUCCESS"); //相應(yīng)給微信服務(wù)器 respMap.put("return_msg", "OK"); String resXml = XmlUtil.xmlFormat(restmap, true); response.getWriter().write(resXml); } else { LOG.info("訂單支付通知:簽名錯(cuò)誤"); } } else { LOG.info("訂單支付通知:支付失敗," + restmap.get("err_code") + ":" + restmap.get("err_code_des")); } } catch (Exception e) { LOG.error(e.getMessage(), e); } } /** * 訂單退款 需要雙向證書(shū)驗(yàn)證 * * @param request * @param response * @param tradeno 微信訂單號(hào) * @param orderno 商家訂單號(hào) * @param callback */ @RequestMapping(value = "/pay/refund", method = RequestMethod.POST) public void orderPayRefund(HttpServletRequest request, HttpServletResponse response, String tradeno, String orderno, String callback) { LOG.info("[/pay/refund]"); if (StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(orderno)) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單號(hào)不能為空", new ResponseData()), SerializerFeatureUtil.FEATURES))); } Map<String, String> restmap = null; try { Map<String, String> parm = new HashMap<String, String>(); parm.put("appid", APP_ID); parm.put("mch_id", MCH_ID); parm.put("nonce_str", PayUtil.getNonceStr()); parm.put("transaction_id", tradeno); parm.put("out_trade_no", orderno);//訂單號(hào) parm.put("out_refund_no", PayUtil.getRefundNo()); //退款單號(hào) parm.put("total_fee", "10"); // 訂單總金額 從業(yè)務(wù)邏輯獲取 parm.put("refund_fee", "10"); // 退款金額 parm.put("op_user_id", MCH_ID); parm.put("refund_account", "REFUND_SOURCE_RECHARGE_FUNDS");//退款方式 parm.put("sign", PayUtil.getSign(parm, API_SECRET)); //String restxml = HttpUtils.posts(ORDER_REFUND, XmlUtil.xmlFormat(parm, false)); String restxml = HttpUtils.posts(ORDER_REFUND, XmlUtil.xmlFormat(parm, false)); restmap = XmlUtil.xmlParse(restxml); } catch (Exception e) { LOG.error(e.getMessage(), e); } Map<String, String> refundMap = new HashMap<>(); if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) { refundMap.put("transaction_id", restmap.get("transaction_id")); refundMap.put("out_trade_no", restmap.get("out_trade_no")); refundMap.put("refund_id", restmap.get("refund_id")); refundMap.put("out_refund_no", restmap.get("out_refund_no")); LOG.info("訂單退款:訂單" + restmap.get("out_trade_no") + "退款成功,商戶(hù)退款單號(hào)" + restmap.get("out_refund_no") + ",微信退款單號(hào)" + restmap.get("refund_id")); WebUtil.response(response, WebUtil.packJsonp(callback, JSON.toJSONString(new JsonResult(1, "訂單獲取成功", new ResponseData(null, refundMap)), SerializerFeatureUtil.FEATURES))); } else { if (CollectionUtil.isNotEmpty(restmap)) { LOG.info("訂單退款失敗:" + restmap.get("err_code") + ":" + restmap.get("err_code_des")); } WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單退款失敗", new ResponseData()), SerializerFeatureUtil.FEATURES))); } } /** * 訂單退款查詢(xún) * @param request * @param response * @param tradeid 微信訂單號(hào) * @param tradeno 商戶(hù)訂單號(hào) * @param refundid 微信退款號(hào) * @param refundno 商家退款號(hào) * @param callback */ @RequestMapping(value = "/pay/refund/query", method = RequestMethod.POST) public void orderPayRefundQuery(HttpServletRequest request, HttpServletResponse response, String refundid, String refundno, String tradeid, String tradeno, String callback) { LOG.info("[/pay/refund/query]"); if (StringUtil.isEmpty(tradeid) && StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(refundno) && StringUtil.isEmpty(refundid)) { WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "退單號(hào)或訂單號(hào)不能為空", new ResponseData()), SerializerFeatureUtil.FEATURES))); } Map<String, String> restmap = null; try { Map<String, String> parm = new HashMap<String, String>(); parm.put("appid", APP_ID); parm.put("mch_id", MCH_ID); parm.put("transaction_id", tradeid); parm.put("out_trade_no", tradeno); parm.put("refund_id", refundid); parm.put("out_refund_no", refundno); parm.put("nonce_str", PayUtil.getNonceStr()); parm.put("sign", PayUtil.getSign(parm, API_SECRET)); String restxml = HttpUtils.post(ORDER_REFUND_QUERY, XmlUtil.xmlFormat(parm, false)); restmap = XmlUtil.xmlParse(restxml); } catch (Exception e) { LOG.error(e.getMessage(), e); } Map<String, String> refundMap = new HashMap<>(); if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code")) && "SUCCESS".equals(restmap.get("result_code"))) { // 訂單退款查詢(xún)成功 處理業(yè)務(wù)邏輯 LOG.info("退款訂單查詢(xún):訂單" + restmap.get("out_trade_no") + "退款成功,退款狀態(tài)"+ restmap.get("refund_status_0")); refundMap.put("transaction_id", restmap.get("transaction_id")); refundMap.put("out_trade_no", restmap.get("out_trade_no")); refundMap.put("refund_id", restmap.get("refund_id_0")); refundMap.put("refund_no", restmap.get("out_refund_no_0")); refundMap.put("refund_status", restmap.get("refund_status_0")); WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(1, "訂單退款成功", new ResponseData(null, refundMap)), SerializerFeatureUtil.FEATURES))); } else { if (CollectionUtil.isNotEmpty(restmap)) { LOG.info("訂單退款失敗:" + restmap.get("err_code") + ":" + restmap.get("err_code_des")); } WebUtil.response(response, WebUtil.packJsonp(callback, JSON .toJSONString(new JsonResult(-1, "訂單退款失敗", new ResponseData()), SerializerFeatureUtil.FEATURES))); } } }
微信支付接口參數(shù)含義具體參考微信APP支付文檔。
微信支付工具類(lèi)
包含簽名、訂單號(hào)、退單號(hào)、隨機(jī)串、服務(wù)器ip地址、客戶(hù)端ip地址等方法。
package org.andy.wxpay.utils; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Arrays; import java.util.Date; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; /** * 創(chuàng)建時(shí)間:2016年11月2日 下午7:12:44 * * @author andy * @version 2.2 */ public class PayUtil { /** * 生成訂單號(hào) * * @return */ public static String getTradeNo() { // 自增8位數(shù) 00000001 return "TNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001"; } /** * 退款單號(hào) * * @return */ public static String getRefundNo() { // 自增8位數(shù) 00000001 return "RNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001"; } /** * 退款單號(hào) * * @return */ public static String getTransferNo() { // 自增8位數(shù) 00000001 return "TNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001"; } /** * 返回客戶(hù)端ip * * @param request * @return */ public static String getRemoteAddrIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) { // 多次反向代理后會(huì)有多個(gè)ip值,第一個(gè)ip才是真實(shí)ip int index = ip.indexOf(","); if (index != -1) { return ip.substring(0, index); } else { return ip; } } ip = request.getHeader("X-Real-IP"); if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) { return ip; } return request.getRemoteAddr(); } /** * 獲取服務(wù)器的ip地址 * * @param request * @return */ public static String getLocalIp(HttpServletRequest request) { return request.getLocalAddr(); } public static String getSign(Map<String, String> params, String paternerKey) throws UnsupportedEncodingException { return MD5Utils.getMD5(createSign(params, false) + "&key=" + paternerKey).toUpperCase(); } /** * 構(gòu)造簽名 * * @param params * @param encode * @return * @throws UnsupportedEncodingException */ public static String createSign(Map<String, String> params, boolean encode) throws UnsupportedEncodingException { Set<String> keysSet = params.keySet(); Object[] keys = keysSet.toArray(); Arrays.sort(keys); StringBuffer temp = new StringBuffer(); boolean first = true; for (Object key : keys) { if (key == null || StringUtil.isEmpty(params.get(key))) // 參數(shù)為空不參與簽名 continue; if (first) { first = false; } else { temp.append("&"); } temp.append(key).append("="); Object value = params.get(key); String valueStr = ""; if (null != value) { valueStr = value.toString(); } if (encode) { temp.append(URLEncoder.encode(valueStr, "UTF-8")); } else { temp.append(valueStr); } } return temp.toString(); } /** * 創(chuàng)建支付隨機(jī)字符串 * @return */ public static String getNonceStr(){ return RandomUtil.randomString(RandomUtil.LETTER_NUMBER_CHAR, 32); } /** * 支付時(shí)間戳 * @return */ public static String payTimestamp() { return Long.toString(System.currentTimeMillis() / 1000); } }
其他所需工具類(lèi)參考項(xiàng)目源碼
支付結(jié)果
APP支付測(cè)試完成
源代碼地址:微信APP支付
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 詳解Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀
- Java基于Tcp/ip連接的多人交互聊天室
- tcp、udp、ip協(xié)議分析_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java基于socket實(shí)現(xiàn)的客戶(hù)端和服務(wù)端通信功能完整實(shí)例
- 微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例
- Java編程Socket實(shí)現(xiàn)多個(gè)客戶(hù)端連接同一個(gè)服務(wù)端代碼
- java服務(wù)端微信APP支付接口詳解
- Java實(shí)現(xiàn)TCP/IP協(xié)議的收發(fā)數(shù)據(jù)(服務(wù)端)代碼實(shí)例
相關(guān)文章
spring boot @ResponseBody轉(zhuǎn)換JSON 時(shí) Date 類(lèi)型處理方法【兩種方法】
這篇文章主要介紹了spring boot @ResponseBody轉(zhuǎn)換JSON 時(shí) Date 類(lèi)型處理方法,主要給大家介紹Jackson和FastJson兩種方式,每一種方法給大家介紹的都非常詳細(xì),需要的朋友可以參考下2018-08-08Java關(guān)于桶排序的知識(shí)點(diǎn)總結(jié)
這篇文章給大家總結(jié)了關(guān)于JAVA中J桶排序的相關(guān)知識(shí)點(diǎn)和用法分享,有興趣的讀者跟著學(xué)習(xí)下。2018-04-04MyBatis使用動(dòng)態(tài)SQL標(biāo)簽的小陷阱
MyBatis是一個(gè)支持普通SQL查詢(xún),存儲(chǔ)過(guò)程和高級(jí)映射的優(yōu)秀持久層框架,MyBatis越來(lái)越受大家的喜愛(ài)了。下面給大家分享MyBatis使用動(dòng)態(tài)SQL標(biāo)簽的小陷阱,感興趣的朋友一起看看吧2016-10-10Mybatis逆向生成使用擴(kuò)展類(lèi)的實(shí)例代碼詳解
這篇文章主要介紹了Mybatis逆向生成使用擴(kuò)展類(lèi)的實(shí)例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-05-05Spring中的@PostConstruct注解使用方法解析
這篇文章主要介紹了Spring中的@PostConstruct注解使用方法解析,@PostConstruct注解是用來(lái)處理在@Autowired注入屬性后init()方法之前,對(duì)一些零散的屬性進(jìn)行賦值的注解,需要的朋友可以參考下2023-11-11commons fileupload實(shí)現(xiàn)文件上傳的實(shí)例代碼
這篇文章主要介紹了commons fileupload實(shí)現(xiàn)文件上傳的實(shí)例代碼,包括文件上傳的原理分析等相關(guān)知識(shí)點(diǎn),本文給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-10-10