步驟:
- 導入maven依賴
<!--微信支付--> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
- 微信支付參數配置
import com.github.wxpay.sdk.WXPayConfig; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; /** * 微信支付配置(單例) */ public class WXConfigUtil implements WXPayConfig { private byte[] certData; private static WXConfigUtil INSTANCE; public static final String APP_ID = "*****";//應用AppID public static final String KEY = "******";//商戶密鑰 public static final String MCH_ID = "******";//商戶號 public WXConfigUtil() throws Exception { String certPath = WXConfigUtil.class.getClassLoader().getResource("").getPath();//從微信商戶平台下載的安全證書存放的路徑 File file = new File(certPath+ "apiclient_cert.p12"); InputStream certStream = new FileInputStream(file); this.certData = new byte[(int) file.length()]; certStream.read(this.certData); certStream.close(); } //雙重檢查加鎖 public static WXConfigUtil getInstance() throws Exception { if (INSTANCE == null) { synchronized (WXConfigUtil.class) { if (INSTANCE == null) { INSTANCE = new WXConfigUtil(); } } } return INSTANCE; } @Override public String getAppID() { return APP_ID; } //parnerid,商戶號 @Override public String getMchID() { return MCH_ID; } @Override public String getKey() { return KEY; } @Override public InputStream getCertStream() { ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); return certBis; } @Override public int getHttpConnectTimeoutMs() { return 8000; } @Override public int getHttpReadTimeoutMs() { return 10000; } }
- 業務層統一下單以及異步通知后的XML數據處理
import com.aone.app.common.wx.WXConfigUtil; import com.aone.app.common.wx.WxCfg; import com.aone.app.service.PayService; import com.aone.app.service.WXAppPayService; import com.github.wxpay.sdk.WXPay; import com.github.wxpay.sdk.WXPayUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.HashMap; import java.util.Map; @Service public class WXAppPayServiceImpl implements WXAppPayService { private static final Logger logger = LoggerFactory.getLogger("WXAppPayServiceImpl"); @Autowired private WxCfg wxCfg; /** * 調用官方SDK 獲取預支付訂單等參數 * @param type * @param out_trade_no * @param money * @return * @throws Exception */ @Override public Map<String, String> dounifiedOrder(String type,String out_trade_no,String money) throws Exception { Map<String, String> returnMap = new HashMap<>(); //支付參數 WXConfigUtil config = new WXConfigUtil(); WXPay wxpay = new WXPay(config); //請求參數封裝 Map<String, String> data = new HashMap<>(); data.put("appid", config.getAppID()); data.put("mch_id", config.getMchID()); data.put("nonce_str", WXPayUtil.generateNonceStr()); data.put("body", "訂單支付"); data.put("out_trade_no", out_trade_no); data.put("total_fee", "1"); data.put("spbill_create_ip", wxCfg.getIp()); //自己的服務器IP地址 data.put("notify_url", wxCfg.getAppNotifyUrl());//異步通知地址(請注意必須是外網) data.put("trade_type", wxCfg.getAppType());//交易類型 data.put("attach", type);//附加數據,在查詢API和支付通知中原樣返回,該字段主要用於商戶攜帶訂單的自定義數據 String s = WXPayUtil.generateSignature(data, config.getKey()); //簽名 data.put("sign", s);//簽名 try { //使用官方API請求預付訂單 Map<String, String> response = wxpay.unifiedOrder(data); System.out.println(response); String returnCode = response.get("return_code"); //獲取返回碼 //若返回碼為SUCCESS,則會返回一個result_code,再對該result_code進行判斷 if (returnCode.equals("SUCCESS")) { //主要返回以下5個參數(必須按照順序,否則APP報錯:-1) String resultCode = response.get("result_code"); returnMap.put("appid", response.get("appid")); returnMap.put("noncestr", response.get("nonce_str")); if ("SUCCESS".equals(resultCode)) {//resultCode 為SUCCESS,才會返回prepay_id和trade_type returnMap.put("package","Sign=WXPay"); returnMap.put("partnerid", response.get("mch_id")); returnMap.put("prepayid", response.get("prepay_id")); returnMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));//單位為秒 String sign = WXPayUtil.generateSignature(returnMap, config.getKey());// 二次簽名 returnMap.put("sign",sign); //簽名 returnMap.put("trade_type", response.get("trade_type"));//獲取預支付交易回話標志 return returnMap; } else { //此時返回沒有預付訂單的數據 return returnMap; } } else { return returnMap; } } catch (Exception e) { System.out.println(e); //系統等其他錯誤的時候 } return returnMap; } /** * * @param notifyData 異步通知后的XML數據 * @return */ @Override public String payBack(String notifyData) { WXConfigUtil config = null; try { config = new WXConfigUtil(); } catch (Exception e) { e.printStackTrace(); } WXPay wxpay = new WXPay(config); String xmlBack = ""; Map<String, String> notifyMap = null; try { notifyMap = WXPayUtil.xmlToMap(notifyData); // 調用官方SDK轉換成map類型數據 if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {//驗證簽名是否有效,有效則進一步處理 String return_code = notifyMap.get("return_code");//狀態 String out_trade_no = notifyMap.get("out_trade_no");//商戶訂單號 if (return_code.equals("SUCCESS")) { if (out_trade_no != null) { // 注意特殊情況:訂單已經退款,但收到了支付結果成功的通知,不應把商戶的訂單狀態從退款改成支付成功 // 注意特殊情況:微信服務端同樣的通知可能會多次發送給商戶系統,所以數據持久化之前需要檢查是否已經處理過了,處理了直接返回成功標志 //業務數據持久化 System.err.println("支付成功"+"\n"); String attach = notifyMap.get("attach");//附加數據,用於區分是那張表訂單 System.out.print("附加數據類型為:{}"+attach+"\n"); if(StringUtils.isEmpty(attach)){ logger.info("附加數據類型為:{}", attach); }else{ //@TODO 預支付下單后回調的邏輯 } logger.info("微信手機支付回調成功訂單號:{}", out_trade_no); xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { logger.info("微信手機支付回調失敗訂單號:{}", out_trade_no); xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> "; } } return xmlBack; } else { // 簽名錯誤,如果數據里沒有sign字段,也認為是簽名錯誤 //失敗的數據要不要存儲? logger.error("手機支付回調通知簽名錯誤"); xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> "; return xmlBack; } } catch (Exception e) { logger.error("手機支付回調通知失敗", e); xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> "; } return xmlBack; } }
- WXCfg中配置的是微信支付的回調地址以及交易類型
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * 微信參數配置中心 */ @Component public class WxCfg { @Value("${wx.appType}") private String appType;//App支付交易類型 @Value("${wx.appNotifyUrl}") private String appNotifyUrl;//App回調地址 @Value("${wx.h5Type}") private String h5Type;//H5支付交易類型 @Value("${wx.h5NotifyUrl}") private String h5NotifyUrl;//H5回調地址 @Value("${wx.ip}") private String ip;//服務器ip @Value("${wx.redirect_url}") private String redirect_url;//跳轉地址 public String getAppNotifyUrl() { return appNotifyUrl; } public void setAppNotifyUrl(String appNotifyUrl) { this.appNotifyUrl = appNotifyUrl; } public String getH5NotifyUrl() { return h5NotifyUrl; } public void setH5NotifyUrl(String h5NotifyUrl) { this.h5NotifyUrl = h5NotifyUrl; } public String getAppType() { return appType; } public void setAppType(String appType) { this.appType = appType; } public String getH5Type() { return h5Type; } public void setH5Type(String h5Type) { this.h5Type = h5Type; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getRedirect_url() { return redirect_url; } public void setRedirect_url(String redirect_url) { this.redirect_url = redirect_url; } }
- 控制層下單接口以及回調接口
import com.aone.app.service.WXAppPayService; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @RestController @RequestMapping("pay") @Api("App支付") public class PayAppController { @Autowired private WXAppPayService wxAppPayService; /** * * App支付統一下單 * @param out_trade_no 訂單號 * @param total_fee 支付金額 * @param type 0:預約訂單1:專家保證金2:即時咨詢訂單 * @return * @throws Exception */ @PostMapping("order") public Map<String, String> order(@RequestParam(value = "type") String type,@RequestParam(value = "out_trade_no") String out_trade_no,@RequestParam(value = "total_fee") String total_fee)throws Exception{ return wxAppPayService.dounifiedOrder(type,out_trade_no,total_fee); } /** * 微信支付異步結果通知 */ @RequestMapping(value = "wxPayNotify", method = {RequestMethod.GET, RequestMethod.POST}) public String wxPayNotify(HttpServletRequest request, HttpServletResponse response) { System.out.print("微信回調開始"+"\n"); String resXml = ""; try { InputStream inputStream = request.getInputStream(); //將InputStream轉換成xmlString BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder sb = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } } catch (IOException e) { System.out.println(e.getMessage()); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } resXml = sb.toString(); String result = wxAppPayService.payBack(resXml); System.out.print("微信回調結束"+"\n"); return result; } catch (Exception e) { System.out.println("微信手機支付失敗:" + e.getMessage()); String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> "; return result; } } }
- 下單封裝的Map中封裝參數服務器IP寫死可能會報錯,提供獲取服務器IP工具類
import javax.servlet.http.HttpServletRequest; /** * 獲取服務器IP工具類 */ public class IpAddr { public static String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("PRoxy-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }
主要用於安卓APP微信支付,IOS微信支付可能是需要交錢,太貴了,所以IOS的微信支付提供外鏈接使用H5進行支付。