步骤:
- 导入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进行支付。