曆時兩周,終於把銀聯支付退款搞定了。由於沒人指導,走了不少彎路,博主在此貼出相關代碼,希望能幫到像我一樣沒人指導的小夥伴。
銀聯支付
1:支付,退款流程。
2:支付的相關準備 去官網下載sdk,官網相關地址會在本文結尾出提供 下載官網的測試組態檔案acp_sdk.properties,測試相關認證acp_test_enc.cer,acp_test_sign.pfx,修改acp_sdk.properties後放在classpath下,如果要用生產環境,相關認證檔案有四個,設定檔也有對應的生產環境認證。 如果支付成功後是跳轉頁面,則需要準備相關頁面,如果是java代碼拼接字串,需要準備相關的java類,用來動態生產html頁面。 3:支付,退款相關代碼
1:代碼結構
2:AutoLoadServlet
import com.kemile.common.pay.chinapay.sdk.SDKConfig;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;/** * 功能:從應用的classpath下載入acp_sdk.properties屬性檔案並將該屬性檔案中的索引值對賦值到SDKConfig類中 <br> * */public class AutoLoadServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { SDKConfig.getConfig().loadPropertiesFromSrc(); super.init(); }}
3:CharsetEncodingFilter
import com.kemile.common.pay.chinapay.config.ChinapayConfig;import java.io.IOException;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;public class CharsetEncodingFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(ChinapayConfig.encoding_UTF8); response.setContentType("text/html; charset="+ ChinapayConfig.encoding_UTF8); chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { }}
4:ChinapayConfig
import java.text.SimpleDateFormat;import java.util.Date;public class ChinapayConfig { //商家號 //public static String MERID = "777290058137116";//填自己網站相關的商家號 //前台請求地址 public static String FRONTURL = "fontrev"; //後台請求地址 public static String BACKURL = "backrev"; //預設配置的是UTF-8 public static String encoding_UTF8 = "UTF-8"; // public static String encoding_GBK = "GBK"; //全渠道固定值 public static String version = "5.0.0"; // 商戶發送交易時間 格式:YYYYMMDDhhmmss public static String getCurrentTime() { return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); } // AN8..40 商戶訂單號,不能含"-"或"_" public static String getOrderId() { return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); } /** 招商銀行借記卡:6226090000000048 手機號:18100000000 密碼:111101 簡訊驗證碼:123456(手機)/111111(PC)(先點擷取驗證碼之後再輸入) 證件類型:01 證件號:510265790128303 姓名:張三 */}
5:CYChinaPayController
@Controller@RequestMapping(value = "/CYChinapay")public class CYChinaPayController { Logger logger = Logger.getLogger(AliPayController.class); @Autowired private IMobileCyDishorderService mobileCyDishorderService;//注入service @Autowired private IMobileServiceStyleService mobileServiceStyleService; //支付 @RequestMapping("/{order_nbr}") public String pay(@PathVariable String order_nbr, HttpServletRequest request, HttpServletResponse response) { String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort(); CyDishorderEntity order = (CyDishorderEntity) mobileCyDishorderService.findOne("orderNum", order_nbr); Map<String, String> requestData = new HashMap<String, String>(); requestData.put("version", ChinapayConfig.version); //版本號碼,全渠道預設值 requestData.put("encoding", ChinapayConfig.encoding_UTF8); //字元集編碼,可以使用UTF-8,GBK兩種方式 requestData.put("signMethod", "01"); //簽名方法,只支援 01:RSA方式認證加密 requestData.put("txnType", "01"); //交易類型 ,01:消費 requestData.put("txnSubType", "01"); //交易子類型, 01:自助消費 requestData.put("bizType", "000201"); //業務類型,B2C網關支付,手機wap支付 requestData.put("channelType", "08"); //渠道類型,這個欄位區分B2C網關支付和手機wap支付;07:PC,平板 08:手機 /***商戶接入參數***/ requestData.put("merId", ChinapayConfig.MERID); //商戶號碼,請改成自己申請的正式商戶號或者open上註冊得來的777測試商戶號 requestData.put("accessType", "0"); //接入類型,0:直連商戶 requestData.put("orderId", order_nbr); //商戶訂單號,8-40位元字字母,不能含“-”或“_”,可以自行定製規則 requestData.put("txnTime", ChinapayConfig.getCurrentTime()); //訂單發送時間,取系統時間,格式為YYYYMMDDhhmmss,必須取目前時間,否則會報txnTime無效 requestData.put("currencyCode", "156"); //交易幣種(境內商戶一般是156 人民幣) BigDecimal totPrice = null; //==========價格計算========= totPrice = new BigDecimal(order.getPayMoney());//查詢價格 requestData.put("txnAmt", String.valueOf((totPrice.multiply(new BigDecimal(100))).longValue())); //交易金額,單位分,不要帶小數點 try { //=============================== //:TODO: //============================= requestData.put("frontUrl", basePath + "/CYChinapay/" + ChinapayConfig.FRONTURL); //前台請求地址 requestData.put("backUrl", basePath + "/CYChinapay/" + ChinapayConfig.BACKURL); //後台請求地址 logger.info("回調地址"+basePath + "/CYChinapay/" + ChinapayConfig.FRONTURL); logger.info("回調地址"+basePath + "/CYChinapay/" + ChinapayConfig.BACKURL); Map<String, String> submitFromData = AcpService.sign(requestData, ChinapayConfig.encoding_UTF8); //簽名 String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl(); //擷取請求銀聯的前台地址:對應屬性檔案acp_sdk.properties檔案中的acpsdk.frontTransUrl String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, ChinapayConfig.encoding_UTF8); //產生自動跳轉的Html表單 //將產生的html寫到瀏覽器中完成自動跳轉開啟銀聯支付頁面;這裡調用signData之後,將html寫到瀏覽器跳轉到銀聯頁面之前均不能對html中的表單項的名稱和值進行修改,如果修改會導致驗簽不通過 response.getWriter().write(html); } catch (Exception e) { e.printStackTrace(); } return null; } private static Map<String, String> valideData(HttpServletRequest req, HttpServletResponse resp) throws IOException { String encoding = req.getParameter(SDKConstants.param_encoding); // 擷取銀聯通知伺服器發送的後台通知參數 Map<String, String> reqParam = getAllRequestParam(req); Map<String, String> valideData = null; if (null != reqParam && !reqParam.isEmpty()) { Iterator<Map.Entry<String, String>> it = reqParam.entrySet().iterator(); valideData = new HashMap<String, String>(reqParam.size()); while (it.hasNext()) { Map.Entry<String, String> e = it.next(); String key = (String) e.getKey(); String value = (String) e.getValue(); value = new String(value.getBytes(encoding), encoding); valideData.put(key, value); } } return valideData; } //後台請求地址 @RequestMapping(value = "/backrev") public void BackRcv(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //Map<String,String> map=getAllRequestParam(request); logger.info("[進入銀聯支付回調方法]"); String encoding = req.getParameter(SDKConstants.param_encoding); // 擷取銀聯通知伺服器發送的後台通知參數 Map<String, String> reqParam = getAllRequestParam(req); // LogUtil.printRequestLog(reqParam); Map<String, String> valideData = valideData(req, resp); PrintWriter out = null; out = resp.getWriter(); //重要。驗證簽名前不要修改reqParam中的索引值對的內容,否則會驗簽不過 if (!AcpService.validate(valideData, encoding)) { logger.info("驗證簽名結果[失敗]."); out.write(CyReturnPayEndHtml.failedHtml(valideData.get("orderId"), "驗證簽名失敗", Const.validateFaliUrl)); } else { logger.info("驗證簽名結果[成功]."); //【註:為了安全驗簽成功才應該寫商戶的成功處理邏輯】交易成功,更新商戶訂單狀態 String respCode = valideData.get("respCode"); if ("00".equals(respCode)) {//銀聯返回00代表支付成功 //:TODO:該方法在使用者支付成功後銀聯自動非同步回調。此處可以寫訂單支付成功後的商務邏輯 if (訂單支付成功後) { resp.getWriter().print("ok");//這裡一定要寫響應。否則銀聯會認為商戶後台沒有收到回調資訊。 } } } LogUtil.writeLog("BackRcvResponse接收後台通知結束"); } //前台請求地址 @RequestMapping("/fontrev") public void fontrev(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("utf-8"); response.setContentType("text/html; charset=utf-8");//設定編碼 PrintWriter out = null; out = response.getWriter();//擷取響應輸出資料流 logger.info("FrontRcvResponse前台接收報文返回開始"); String encoding = request.getParameter(SDKConstants.param_encoding); logger.info("返回報文中encoding=[" + encoding + "]"); Map<String, String> respParam = getAllRequestParam(request);//擷取銀聯回調的參數 // 列印請求報文 LogUtil.printRequestLog(respParam); Map<String, String> valideData = valideData(request, response);//驗證簽名 if (!AcpService.validate(valideData, encoding)) {//驗證簽名失敗 logger.info("驗證簽名結果[失敗].");// return "redirect:/pay-failed.html"; out.write(CyReturnPayEndHtml.failedHtml(valideData.get("orderId"), "驗證簽名失敗", Const.validateFaliUrl)); } else { String respCode = valideData.get("respCode");//驗證成功後擷取銀聯響應碼 if ("00".equals(respCode)) {//響應碼為00表示支付成功。 logger.info("驗證簽名結果[成功]"); //===================== //TODO:這個方法在使用者支付成功後點擊返回商戶時,銀聯回調,這裡寫回調成功後的一些商務邏輯。 } else { out.write(CyReturnPayEndHtml.failedHtml(valideData.get("orderId"), "銀聯支付失敗", Const.validateFaliUrl)); } } out.flush(); out.close(); } /** * 更新訂單資料(狀態)此方法根據自己的業務需求編寫,最後要返回驗證簽名後的訂單編號。 * * @param valideData * @return */ private String updateOrder(Map<String, String> valideData) { return valideData.get("orderId");//返回驗證簽名成功後的訂單編號 } //退款 @ResponseBody @RequestMapping("/Refund/{order_nbr}") public String doPost(@PathVariable String order_nbr, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String basePath = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort(); CyDishorderEntity order = (CyDishorderEntity) mobileCyDishorderService.findOne("orderNum", order_nbr); Map<String, String> data = new HashMap<String, String>(); /***銀聯全渠道系統,產品參數 ,除了encoding自行選擇外其他不需修改***/ data.put("version", ChinapayConfig.version); //版本號碼 data.put("encoding", ChinapayConfig.encoding_UTF8); //字元集編碼 可以使用UTF-8,GBK兩種方式 data.put("signMethod", "01"); //簽名方法 目前只支援01-RSA方式認證加密 data.put("txnType", "04"); //交易類型 04-退貨 data.put("txnSubType", "00"); //交易子類型 預設00 data.put("bizType", "000201"); //業務類型 B2C網關支付,手機wap支付 data.put("channelType", "07"); //渠道類型,07-PC,08-手機 /***商戶接入參數***/ /*ChinapayConfig.MERID*/ data.put("merId", ChinapayConfig.MERID); //商戶號碼,請改成自己申請的商戶號或者open上註冊得來的777商戶號測試 data.put("accessType", "0"); //接入類型,商戶接入固定填0,不需修改 data