赞
踩
以下内容关于微信相关的,除了binarywang,个人认为都不要再用了。相比较王大哥的封装,我自己写的真是连弟弟都不如。
支付宝相关的,好久不用了,不知道还能不能工作。建议找创建时间比较新的文章来看。
关于binarywang给我们二次封装的东西,目前本人使用了登录,支付两个最常见的功能。
具体的使用姿势,各位移步吧。
时间 2022年3月30
目前很多企业在做支付的时候为了方便已经开始直接对接第四方了
但是也有一些开源大神们对支付甚至是整个微信开发提供了API
笔者公司的微信支付目前使用的第三方API是binarywang
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${github.binarywang.version}</version>
</dependency>
这位大神将微信整个模块的开发都做成了API开源出来,感兴趣的可以去Github学习
支付宝支付对接其实蛮简单清晰的,可能就是在微信支付的时候会有一些坑,但是在使用这个API之后问题就没有那么多了. 用起来还是蛮简单的,而且它支持多商户切换
最近工作中安排了三方支付接入(一般就是微信,支付宝,银联),目前接入的是微信和支付宝;
阶段接近尾声并且测试很OK,现在记录一下开发过程.
目前只是对接了
支付宝的PC,H5;
微信的PC,H5,JSAPI这几种
APP的支付暂时没有需求,所以没做考虑
大家在对接过程中如果遇到问题,欢迎留言,我看到会及时回复的.发私信也可以.希望对大家有帮助;
你需要在支付宝或微信平台创建自己的应用,得到我们后续需要用到的各种id和secret
支付宝是有SDK的,记得引入依赖
至于怎么在微信和支付宝的管理平台创建应用,大家自己百度一下吧.o(╥﹏╥)o
对接外部接口,其实很简单,只需要不停尝试就好了,哈哈. 当前网上有很多资源都是相当可取的;
需要注意的是,支付宝所需要的公钥和私钥的生成方式,
此处需要特别注意下,开发者私钥,即我们通过支付宝的生成工具生成的应用私钥
而公钥,如下图
是我们通过上面生成的应用公钥,进行填写,保存之后,支付宝生成的,切记!!! 不然调不通接口的哦.
支付宝生成秘钥
其次
说白了,不管支付宝还是微信,支付逻辑无在乎一去一回这两下.
先做统一下单的操作,然后通过返回数据,做具体的逻辑处理
要注意的是,
支付宝的PC和H5支付返回给我们的是完整的form表单,我们只需要将其响应给前端,让前端做submit即可进行调起真正的支付动作;
而微信则有稍微不同, PC支付返回给我们的是一个二维码url,需要前端将其生成二维码展示; H5返回给我们的是一个web的url,由前端对其进行访问;
开发的时候本地可以,但是调试的时候,如果你没有调通接口,那么建议你线上进行调试;
我业务中的
controller
和service
就不罗列了
PayPlatformService
姑且称之为收银台接口AliPayServiceImpl
支付宝服务,实现了上面的收银台接口WeChatPayServiceImpl
微信服务,实现了上面的收银台接口PayCommons
支付用到的通用属性PayProperties
支付相关的配置参数PayConstant
public interface PayConstant { /** * 由谁支付 0 企业 1 个人 */ int PAY_SIDE_CORP = 0; int PAY_SIDE_PERSONAL = 1; /** * 支付方式 */ String PAY_TYPE_WX = "wcpay"; String PAY_TYPE_ALI = "alipay"; /** * 微信支付类型 */ String TRADE_TYPE_WX_JSAPI = "JSAPI"; String TRADE_TYPE_WX_NATIVE = "NATIVE"; String TRADE_TYPE_WX_APP = "APP"; String TRADE_TYPE_WX_MWEB = "MWEB"; }
PayCommons
@Data @Accessors(chain = true) public class PayCommons { public static final String TRADE_TYPE_PC = "pc"; public static final String TRADE_TYPE_H5 = "h5"; /** * sec_account_receivable的id */ private Integer sarId; /** * 订单标题 */ private String subject; /** * 第三方(对于支付宝,微信来说)的订单号 */ private String securityOrderNo; /** * 订单总金额 */ private Integer totalAmount; private BigDecimal fromTotalAmount; /** * 支付方式,PC还是WAP等等 */ private String tradeType; /** * 交易该笔订单的设备IP */ private String clientIp; /** * 支付方,企业:0 还是个人:1 */ private Integer paySide; /** * alipay:支付宝 wcpay:微信 */ private String payType; /** * 业务类型 充值:RECHARGE */ private String bizType; /** * 业务单号 */ private String bizNum; /** * 附件 */ private String attach; /** * 微信获取token的code */ private String wxCode; /** * 微信的openId */ private String wxOpenId; }
PayProperties
这个类可以放到配置文件中,后续我们会将其中一些配置移至Apollo配置中心
public class PayProperties { /** ============================支付宝========================================== */ /** * URL */ public static String ALI_PAY_BASE_URL = "https://openapi.alipay.com/gateway.do"; /** * 对接支付宝时创建的应用 */ public static String ALI_PAY_APPID = ""; /** * 支付宝分配的商户号(账户中心,主账户ID) */ public static String ALI_SELLER_ID = ""; /** * 开发者私钥,由开发者自己生成 */ public static String ALI_PAY_APP_PRIVATE_KEY = ""; /** * 开发者公钥,由支付宝生成 */ public static String ALI_PAY_APP_PUBLIC_KEY = ""; /** * 销售产品码,商家和支付宝签约的产品码 * 1、app支付product_code:QUICK_MSECURITY_PAY; * 2、手机网站支付product_code:QUICK_WAP_WAY; * 3、电脑网站支付product_code:FAST_INSTANT_TRADE_PAY; * 4、统一收单交易支付接口product_code:FACE_TO_FACE_PAYMENT; * 5、周期扣款签约product_code:CYCLE_PAY_AUTH; */ public static String ALI_WAP_PAY_PRODUCT_CODE = "QUICK_WAP_WAY"; public static String ALI_PAGE_PAY_PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY"; /** * 参数返回格式 */ public static String ALI_PAY_FORMAT = "json"; /** * 编码集 */ public static String ALI_PAY_CHARSET = "UTF-8"; /** * 签名方式 */ public static String ALI_PAY_SIGN_TYPE = "RSA2"; /**异步 * 回调接口 */ public static String ALI_PAY_NOTIFY_URL = ""; /** * 用户付款中途退出,返回商户网站的地址 */ public static String ALI_PAY_QUIT_URL = ""; /**同步 * 用户支付成功返回的地址 */ public static String ALI_PAY_PAGE_RETURN_URL = ""; public static String ALI_PAY_WAP_RETURN_URL = ""; /** * 支付接口 */ public static String ALI_PAY_WAP_PAY = "alipay.trade.wap.pay"; /** ** ============================微信======================================= */ /** * 统一下单 */ public static String WX_PAY_PAY_UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** * 订单查询 */ public static String WX_PAY_PAY_ORDERQUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery"; /** * 微信支付appid */ public static String WX_PAY_APPID = ""; /** * 微信支付商户ID */ public static String WX_PAY_MCHID = ""; /** * 商户秘钥 * API秘钥 */ public static String WX_PAY_SIGN_SECRET_KEY = ""; /** * 微信支付AppSecret */ public static String WX_PAY_APP_SECRET = ""; /** * 微信支付回调 */ public static String WX_PAY_NOTIFY_URL = ""; /** * 微信H5支付成功之后返回的页面 */ public static String WX_WAP_PAY_RETURN_URL = ""; }
AliPayClientFactory
当你阅读过支付宝文档之后你会发现,文档中明确说明,当
AlipayClient
创建完成之后,可以重复使用,因此我们在这里做一个单例的操作,算是代码的一个优化吧, 微信也会有类似的处理;
public class AliPayClientFactory { private volatile static AlipayClient aliPayClient = null; /** * 私有化构造器 */ private AliPayClientFactory(){} /** * 获取对象 */ public static AlipayClient getInstance(){ if (aliPayClient == null){ synchronized (AliPayClientFactory.class){ if (aliPayClient == null){ aliPayClient = new DefaultAlipayClient(PayProperties.ALI_PAY_BASE_URL,PayProperties.ALI_PAY_APPID,PayProperties.ALI_PAY_APP_PRIVATE_KEY ,PayProperties.ALI_PAY_FORMAT,PayProperties.ALI_PAY_CHARSET,PayProperties.ALI_PAY_APP_PUBLIC_KEY,PayProperties.ALI_PAY_SIGN_TYPE); } } } return aliPayClient; } }
通用结果类Result
@Data public class Result<T> { private String code; private String mesg; private T data; private Result() { this.code = "000000"; this.mesg = "success"; this.data = null; } private Result(T data) { this.code = "000000"; this.mesg = "success"; this.data = data; } private Result(TaurusErrorCodeEnum tec) { if (tec==null){ return; } this.code = tec.getCode(); this.mesg = tec.getDescription(); } /** * 成功时调用 * @param <T> * @return */ public static <T> Result<T> success(){ return new Result(); } /** * 成功时调用 * @param data * @param <T> * @return */ public static <T> Result<T> success(T data){ return new Result<T>(data); } /** * 失败时调用 * @param * @param <T> * @return */ public static <T> Result<String> fail(){ Result<String> result = new Result<>(); result.setCode("2000"); result.setMesg("系统异常"); return result; } /** * 失败时调用 * @param * @param <T> * @return */ public static <T> Result<String> fail(String code, String msg){ Result<String> result = new Result<String>(); result.setCode(code); result.setMesg(msg); return result; } /** * 失败时调用 * @param tec * @param <T> * @return */ public static <T> Result<T> fail(TaurusErrorCodeEnum tec){ return new Result<T>(tec); } }
PayPlatformService
public interface SecurityPayPlatformService { /** * 支付 * @param pc * @return */ Result securityPay(PayCommons pc); /** * 处理回调 * @param request * @return */ Result handleNotify(HttpServletRequest request); /** * 查询订单状态 * @param payCommons * @return */ Result selectOrderInfo(PayCommons payCommons); }
AliPayServiceImpl
@Service public class AliPayServiceImpl implements PayPlatformService{ private static final Logger log = LoggerFactory.getLogger(AliPayServiceImpl.class); @Autowired private SecAccountReceivableService secAccountReceivableService; /** * 支付宝支付充值 * @param payParam * @return */ @Override public Result securityPay(PayCommons payParam) { if (null == payParam) { return Result.fail().setMesg("参数未传"); } AlipayClient alipayClient = AliPayClientFactory.getInstance(); //转义携带参数 JSONObject attach = (JSONObject)JSON.parse(payParam.getAttach()); StringBuilder attachSB = new StringBuilder(); attachSB.append("bizType=").append(attach.get("bizType")).append("&paySide=") .append(attach.get("paySide")); payParam.setAttach(attachSB.toString()); //0企业支付page 1 个人支付wap if (payParam.getTradeType().equals(PayCommons.TRADE_TYPE_PC)){ return this.doPagePayRequest(alipayClient,payParam); }else { return this.doWapPayRequest(alipayClient,payParam); } } /** * 处理回调 * @param request * @return */ @Override public Result handleNotify(HttpServletRequest request) { try { Enumeration<String> names = request.getParameterNames(); HashMap<String, String> resData = new HashMap<>(); while (names.hasMoreElements()){ String name = names.nextElement(); resData.put(name,request.getParameter(name)); } log.info("======支付宝支付的异步回调通知参数:{}",resData.toString()); //1.验签 boolean flag = AlipaySignature.rsaCheckV1(resData, PayProperties.ALI_PAY_APP_PUBLIC_KEY, "UTF-8", "RSA2"); if (!flag){ return Result.fail().setMesg("验签失败"); } //2.必要参数非空验证 String tradeStatus = resData.get("trade_status"); //己方单号 String secTradeNo = resData.get("out_trade_no"); //商户号 String sellerId = resData.get("seller_id"); String totalAmount = resData.get("total_amount"); String appId = resData.get("app_id"); if (StringUtils.isAnyBlank(tradeStatus, secTradeNo, sellerId, totalAmount, appId)){ return Result.fail().setMesg("解析非空参数trade_status,out_trade_no,seller_id,total_amount,app_id部分为空"); } //数据匹配验证 List<SecAccountReceivable> list = secAccountReceivableService.getList(new SecAccountReceivableQuery().setAccountNum(secTradeNo)); if (CollectionUtils.isEmpty(list)){ return Result.fail().setMesg("secTradeNo no found:"+secTradeNo); } SecAccountReceivable sar = list.get(0); if (!(sellerId.equals(PayProperties.ALI_SELLER_ID) && secTradeNo.equals(sar.getAccountNum()) && appId.equals(PayProperties.ALI_PAY_APPID) && totalAmount.equals(sar.getAccountAmount().toString()))){ return Result.fail().setMesg("数据匹配失败,当前回调数据与查询数据不一致"); } //sar_id 是我们应收账单的id,此处可以忽略,删除 resData.put("sar_id",String.valueOf(sar.getId())); return Result.success(resData); }catch (Exception e){ e.printStackTrace(); log.error("支付宝支付的异步回调处理出现错误:{}",e.getStackTrace()); return Result.fail().setMesg("支付宝支付的异步回调处理出现错误"); } } /** * 查詢支付宝订单信息 * @param payCommons * @return */ @Override public Result selectOrderInfo(PayCommons payCommons) { AlipayClient client = AliPayClientFactory.getInstance(); AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no",payCommons.getSecurityOrderNo()); request.setBizContent(bizContent.toJSONString()); AlipayTradeQueryResponse response; try { response = client.execute(request); log.info("支付宝订单【{}】---查詢結果:{}",payCommons.getSecurityOrderNo(),response.getBody()); }catch (Exception e){ e.printStackTrace(); log.error("调用支付宝查询接口异常"); return Result.fail().setMesg("调用支付宝查询接口异常"); } String body = response.getBody(); if (StringUtils.isBlank(body)){ return Result.fail(); } JSONObject bodyObj = JSON.parseObject(body); JSONObject bodybody = bodyObj.getJSONObject("alipay_trade_query_response"); //sar_id 是我们应收账单的id,此处可以忽略,删除 bodybody.put("sar_id",payCommons.getSarId()); return Result.success(bodybody.toString()); } //=====================private method=========================== /** * 处理PC支付 * @param alipayClient * @param payParam * @return */ private Result doPagePayRequest(AlipayClient alipayClient,PayCommons payParam) { AlipayTradePagePayRequest pagePayRequest = new AlipayTradePagePayRequest(); JSONObject bizContent = new JSONObject(); Result result = null; //回调接口,PC支付方式,returnUrl如果没有必要可以不必配置 //pagePayRequest.setReturnUrl(PayProperties.ALI_PAY_PAGE_RETURN_URL); //异步通知调用接口 pagePayRequest.setNotifyUrl(PayProperties.ALI_PAY_NOTIFY_URL); /** * 以下必传项 */ bizContent.put("subject",payParam.getSubject()); bizContent.put("out_trade_no",payParam.getSecurityOrderNo()); bizContent.put("total_amount",payParam.getFromTotalAmount()); bizContent.put("product_code",PayProperties.ALI_PAGE_PAY_PRODUCT_CODE); /** * 以下选传项 */ //公共回传参数,如果请求时传递了该参数,支付宝只会在同步返回和异步通知时将该参数原样返回 try { String encodeAttach = URLEncoder.encode(payParam.getAttach(), PayProperties.ALI_PAY_CHARSET); bizContent.put("passback_params",encodeAttach); }catch (Exception e){ log.error("ali pay passBackParams encode exception:{}",e.getStackTrace()); } pagePayRequest.setBizContent(bizContent.toJSONString()); String form = ""; try { form = alipayClient.pageExecute(pagePayRequest,"get").getBody(); result = Result.success(form); }catch (Exception e){ e.printStackTrace(); //调用异常 log.error("调用支付宝PC支付异常,信息:{}",e.getMessage()); result = Result.fail().setMesg("支付宝PC支付出现异常"); } return result; } /** * 处理H5支付 * @param alipayClient * @param payParam * @return */ private Result doWapPayRequest(AlipayClient alipayClient, PayCommons payParam) { AlipayTradeWapPayRequest wapPayRequest = new AlipayTradeWapPayRequest(); Result result = null; //支付成功访问接口 wapPayRequest.setReturnUrl(PayProperties.ALI_PAY_WAP_RETURN_URL); //异步通知调用接口 wapPayRequest.setNotifyUrl(PayProperties.ALI_PAY_NOTIFY_URL); /** * 以下必传项 */ SortedMap<String, Object> bizContent = new TreeMap<>(); bizContent.put("subject",payParam.getSubject()); bizContent.put("out_trade_no",payParam.getSecurityOrderNo()); BigDecimal amount = payParam.getFromTotalAmount().setScale(2,BigDecimal.ROUND_HALF_UP); bizContent.put("total_amount",amount); bizContent.put("product_code",PayProperties.ALI_WAP_PAY_PRODUCT_CODE); /** * 以下选传项 */ //公共回传参数,如果请求时传递了该参数,支付宝只会在同步返回和异步通知时将该参数原样返回 try { String encodeAttach = URLEncoder.encode(payParam.getAttach(), PayProperties.ALI_PAY_CHARSET); bizContent.put("passback_params",encodeAttach); }catch (Exception e){ log.error("ali pay passBackParams encode exception:{}",e.getStackTrace()); } wapPayRequest.setBizContent(JSON.toJSONString(bizContent)); try { String form = alipayClient.pageExecute(wapPayRequest).getBody(); result = Result.success().setData(form); }catch (AlipayApiException e){ e.printStackTrace(); //调用异常 log.error("调用支付宝WAP支付异常,信息:{}",e.getMessage()); result = Result.fail().setMesg("支付宝WAP支付出现异常"); } return result; } }
微信支付,需要一个特殊工具类,在此贴出来WXPayUtil
WXPayUtil
public class WXPayUtil { public static Logger log = LoggerFactory.getLogger(WXPayUtil.class); /** * 获取随机串 */ public static String createNonceStr() { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String res = ""; for (int i = 0; i < 16; i++) { Random rd = new Random(); res += chars.charAt(rd.nextInt(chars.length() - 1)); } return res; } /** * 获取client_ip * * @param request * @return */ public static String getRemoteHost(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(); } String[] ips = ip.split(","); return ips[0].equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ips[0]; } /** * @param key * @param characterEncoding * @param parameters * @return */ public static String createSign(String key, String characterEncoding, SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + key); String sign = MD5Util.encode(sb.toString()).toUpperCase(); return sign; } /** * @param characterEncoding 编码格式 * @param parameters 请求参数 * @return * @Description:创建sign签名 */ public static String createSign(String characterEncoding, SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if ("attach".equalsIgnoreCase(k)) { sb.append(k + "=" + v + "&"); } else if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + PayProperties.WX_PAY_SIGN_SECRET_KEY); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } /** * 封装xml request */ public static String getRequestXml(SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if ("sign".equalsIgnoreCase(k)) { } else if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k)) { sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">"); } else { sb.append("<" + k + ">" + v + "</" + k + ">"); } } sb.append("<" + "sign" + ">" + "<![CDATA[" + parameters.get("sign") + "]]></" + "sign" + ">"); sb.append("</xml>"); return sb.toString(); } /** * 封装xml 通知返回 */ public static String getnotifyRespXml(String isSuccess, String reason) { SortedMap<String, Object> parameters = new TreeMap<>(); parameters.put("return_code", isSuccess); parameters.put("return_msg", reason); StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set<Map.Entry<String, Object>> es = parameters.entrySet(); Iterator<Map.Entry<String, Object>> it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if ("sign".equalsIgnoreCase(k)) { } else if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "return_code".equalsIgnoreCase(k) || "return_msg".equalsIgnoreCase(k)) { sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">"); } else { sb.append("<" + k + ">" + v + "</" + k + ">"); } } sb.append("</xml>"); return sb.toString(); } public static SortedMap<String, Object> startWXPay(String result) throws Exception { SecurityWXPayConfig wxPayConfig = SecurityWXPayConfig.getInstance(); Map<String, String> map = doXMLParse(result); String prepayId = map.get("prepay_id"); SortedMap<String, Object> parameterMap = new TreeMap<>(); parameterMap.put("appId", wxPayConfig.getWxAppId()); parameterMap.put("timeStamp", String.valueOf(System.currentTimeMillis())); parameterMap.put("nonceStr", map.get("nonce_str")); parameterMap.put("package", "prepay_id=" + prepayId); parameterMap.put("signType","MD5"); String sign = createSign("UTF-8", parameterMap); parameterMap.put("paySign", sign); parameterMap.putAll(map); return parameterMap; } /** * xml 解析 * * @param strxml * @return */ public static Map doXMLParse(String strxml) throws Exception { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if (StringUtils.isBlank(strxml)) { return null; } Map map = new HashMap(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder saxBuilder = new SAXBuilder(); Document doc = saxBuilder.build(in); Element rootEle = doc.getRootElement(); List childrenList = rootEle.getChildren(); Iterator it = childrenList.iterator(); while (it.hasNext()) { Element element = (Element) it.next(); String k = element.getName(); String v = ""; List children = element.getChildren(); if (children.isEmpty()) { v = element.getTextNormalize(); } else { v = getChildrenText(children); } map.put(k, v); } //关闭流 in.close(); return map; } private static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 接收微信的异步通知,取出参数 * * @param request * @return */ public static String reciverWx(HttpServletRequest request) throws IOException { InputStream inputStream; StringBuffer sb = new StringBuffer(); inputStream = request.getInputStream(); String s; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((s = in.readLine()) != null) { sb.append(s); } in.close(); inputStream.close(); return sb.toString(); } /** * 是否签名正确 * 规则:按参数名称a-z排序,遇到空值的参数不参与签名 * * @param characterEncoding * @param packageParams * @return */ public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, SecurityWXPayConfig payConfig) { StringBuffer sb = new StringBuffer(); Set<Map.Entry<Object, Object>> es = packageParams.entrySet(); Iterator<Map.Entry<Object, Object>> it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if (!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + payConfig.getWxSecretKey()); //算出摘要 String mySign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase(); String sign = ((String) packageParams.get("sign")).toLowerCase(); return sign.equals(mySign); } /** * 测试main方法 * * @param args */ public static void main(String[] args) { String 测试 = "<xml><return_code><![CDATA[FAIL]]></return_code>\n" + "<return_msg><![CDATA[签名错误]]></return_msg>\n" + "</xml>"; try { Map sortedMap = WXPayUtil.doXMLParse(测试); String string = JSON.toJSONString(sortedMap); System.out.println("jsonString:" + string); } catch (Exception e) { e.printStackTrace(); } } }
SecurityWXPayConfig该类是微信的公共参数生成类
public class SecurityWXPayConfig{ /** * 微信支付appid */ private String wxAppId; /** * 微信支付商户ID */ private String wxMchId; /** * 微信支付回调 */ private String wxNotifyUrl; /** * 微信支付AppSecret */ private String wxAppSecret; /** * 微信支付秘钥 */ private String wxSecretKey; /** * 微信包名 */ private String wxPkg; private String sceneInfo; private static volatile SecurityWXPayConfig securityWXPayConfig = null; private SecurityWXPayConfig() { this.wxAppId = PayProperties.WX_PAY_APPID; this.wxNotifyUrl = PayProperties.WX_PAY_NOTIFY_URL; this.wxAppSecret = PayProperties.WX_PAY_APP_SECRET; this.wxMchId = PayProperties.WX_PAY_MCHID; this.wxSecretKey = PayProperties.WX_PAY_SIGN_SECRET_KEY; Map<String,Object> map = new HashMap<>(); Map<String,String> infoMap = new HashMap<>(); infoMap.put("type","Wap"); infoMap.put("wap_url","https://www.xxx.com/safe/"); infoMap.put("wap_name","xx"); map.put("h5_info",infoMap); this.sceneInfo = JSON.toJSONString(map); } public static SecurityWXPayConfig getInstance(){ if (securityWXPayConfig == null){ synchronized (SecurityWXPayConfig.class){ if (securityWXPayConfig == null){ securityWXPayConfig = new SecurityWXPayConfig(); } } } return securityWXPayConfig; } public String getWxAppId() { return wxAppId; } public String getWxMchId() { return wxMchId; } public String getWxNotifyUrl() { return wxNotifyUrl; } public String getWxAppSecret() { return wxAppSecret; } public String getWxSecretKey() { return wxSecretKey; } public String getWxPkg() { return wxPkg; } public String getSceneInfo() { return sceneInfo; } }
WeChatPayServiceImpl
@Service public class WeChatPayServiceImpl implements SecurityPayPlatformService { private static final Logger log = LoggerFactory.getLogger(AliPayServiceImpl.class); //业务类,应收账单,用来检验异步通知的数据准确性 @Autowired private SecAccountReceivableService secAccountReceivableService; /** * 支付充值 * * @param payParam * @return */ @Override public Result securityPay(PayCommons payParam) { if (null == payParam) { return Result.fail().setMesg("参数必传"); } //获取微信公共配置 SecurityWXPayConfig wxPayConfig = SecurityWXPayConfig.getInstance(); SortedMap<String, Object> parameterMap = new TreeMap<>(); //获取随机串 String nonceStr = WXPayUtil.createNonceStr(); parameterMap.put("appid", wxPayConfig.getWxAppId()); parameterMap.put("mch_id", wxPayConfig.getWxMchId()); parameterMap.put("notify_url", wxPayConfig.getWxNotifyUrl()); //随机字符串 parameterMap.put("nonce_str", nonceStr); //商品描述 parameterMap.put("body", payParam.getSubject()); parameterMap.put("out_trade_no", payParam.getSecurityOrderNo()); //单位:分 //payCommons.setTotalAmount(); int totalAmount = payParam.getFromTotalAmount().multiply(new BigDecimal("100")).intValue(); parameterMap.put("total_fee", totalAmount); parameterMap.put("spbill_create_ip", payParam.getClientIp()); parameterMap.put("attach", payParam.getAttach()); String tradeType = payParam.getTradeType(); if (tradeType.equals(PayCommons.TRADE_TYPE_PC)){ tradeType = PayConstant.TRADE_TYPE_WX_NATIVE; }else if (tradeType.equals(PayCommons.TRADE_TYPE_H5)){ tradeType = PayConstant.TRADE_TYPE_WX_MWEB; }else if (tradeType.equals(PayConstant.TRADE_TYPE_WX_JSAPI)){ parameterMap.put("openid",payParam.getWxOpenId()); } payParam.setTradeType(tradeType); parameterMap.put("trade_type", tradeType); //{"h5_info": {"type":"Wap","wap_url": "https://pay.qq.com","wap_name": "腾讯充值"}} //wap_url 是用工卫士的url wap_name 是用工卫士的name parameterMap.put("scene_info", wxPayConfig.getSceneInfo()); //生成签名 String sign = WXPayUtil.createSign("UTF-8", parameterMap); //签名 parameterMap.put("sign", sign); //map转xml String requestXml = WXPayUtil.getRequestXml(parameterMap); log.info("请求微信支付xml参数requestXml:{}", requestXml); //http post 请求 String result = HttpUtils.post(PayProperties.WX_PAY_PAY_UNIFIEDORDER_URL, requestXml); log.info("微信支付请求结果:{}", result); if (StringUtils.isBlank(result)) { log.error("调用微信支付接口失败:返回结果为空"); return Result.fail().setMesg("微信支付接口调用失败"); } SortedMap<String, Object> map = null; try { map = WXPayUtil.startWXPay(result); } catch (Exception e) { e.printStackTrace(); log.error("pay error WeChatPayServiceImpl:{}", e.getMessage()); return Result.fail().setMesg("请求结果解析有误"); } //对result转成的map进行解析 if (map.get("return_code").equals("FAIL")) { log.error("调用微信支付接口失败:{}", map.get("return_msg").toString()); return Result.fail().setMesg("微信支付接口调用失败"); } if (map.get("return_code").equals("SUCCESS") && map.get("result_code").equals("FAIL")) { log.error("微信支付出现错误,错误代码:{},错误描述:{}", map.get("err_code"), map.get("err_code_des")); return Result.fail().setMesg("微信支付出现错误"); } map.put("accountNumber", payParam.getSecurityOrderNo()); //此处只有H5支付有的需求 if (payParam.getTradeType().equals(PayConstant.TRADE_TYPE_WX_MWEB)){ String mwebUrl = (String)map.get("mweb_url"); String encodeReturnUrl = ""; try { encodeReturnUrl = URLEncoder.encode(PayProperties.WX_WAP_PAY_RETURN_URL+"?out_trade_no="+payParam.getSecurityOrderNo(), "UTF-8"); }catch (Exception e){ e.printStackTrace(); } if (StringUtils.isBlank(encodeReturnUrl)){ encodeReturnUrl = PayProperties.WX_WAP_PAY_RETURN_URL+"?out_trade_no="+payParam.getSecurityOrderNo(); } map.put("mweb_url",mwebUrl+"&redirect_url="+encodeReturnUrl); } log.info("微信支付请求数据处理,返回前端结果:{}", map); return Result.success(map); } /** * 处理回调 * * @param request * @return */ @Override public Result handleNotify(HttpServletRequest request) { Result result = Result.fail(); String notifyXml = ""; try { notifyXml = WXPayUtil.reciverWx(request); } catch (Exception e) { e.printStackTrace(); return result.setMesg("参数格式校验错误,reciverWx()方法出现异常"); } //参数为空 if (StringUtils.isBlank(notifyXml)) { //这里返回一个参数错误的xml字符串 return result.setMesg("参数格式校验错误" + "解析的请求参数为空"); } try { Map map = WXPayUtil.doXMLParse(notifyXml); log.info("wx handleNotify doXMLParse result:{}", map); SecurityWXPayConfig payConfig = SecurityWXPayConfig.getInstance(); //过滤空 设置 treeMap SortedMap<Object, Object> packageParams = new TreeMap<>(); Iterator it = map.keySet().iterator(); while (it.hasNext()) { String parameter = (String) it.next(); String parameterVal = (String) map.get(parameter); String v = ""; if (StringUtils.isNotBlank(parameterVal)) { v = parameterVal.trim(); } packageParams.put(parameter, v); } //判断签名是否正确 isTenpaySign if (!WXPayUtil.isTenpaySign("UTF-8", packageParams, payConfig)) { //返回参数格式校验错误 return result.setMesg("签名失败"); } //如果通讯异常,即return_code为fail,返回参数格式校验错误 if (StringUtils.isBlank((String) packageParams.get("return_code")) || "FAIL".equals(packageParams.get("return_code"))) { //返回参数格式校验错误 return result.setMesg("参数格式校验错误" + "return_code" + "为空或返回为FAIL"); } String mchId = (String) packageParams.get("mch_id"); String securityTradeNo = (String) packageParams.get("out_trade_no"); String totalFee = (String) packageParams.get("total_fee"); //查询应收账单,用来对通知中的单号,金额等做验证,此操作可以放到外面,使支付更加通用 List<SecAccountReceivable> list = secAccountReceivableService.getList(new SecAccountReceivableQuery().setAccountNum(securityTradeNo)); if (CollectionUtils.isEmpty(list)) { return result.setMesg("未查询到相应out_trade_no的订单"); } SecAccountReceivable sar = list.get(0); //验证商户ID和价格,以防止篡改金额 BigDecimal accountAmount = sar.getAccountAmount().multiply(new BigDecimal(100)); if (StringUtils.isAnyBlank(mchId, totalFee) || !payConfig.getWxMchId().equals(mchId) || (accountAmount.compareTo(new BigDecimal(totalFee)) != 0)) { //这里返回一个参数错误的xml字符串 return result.setMesg("参数格式校验错误,mchId,totalFee为空,或者其中一个与我方所持资源不匹配"); } map.put("sar_id", sar.getId()); return Result.success().setData(map); } catch (Exception e) { e.printStackTrace(); //这里返回一个参数错误 return result.setMesg("参数格式校验错误,出现异常"); } } /** * 查询订单状态 * @param payCommons * @return */ @Override public Result selectOrderInfo(PayCommons payCommons) { SortedMap<String,Object> paramMap = new TreeMap<>(); paramMap.put("appid",PayProperties.WX_PAY_APPID); paramMap.put("mch_id",PayProperties.WX_PAY_MCHID); paramMap.put("out_trade_no",payCommons.getSecurityOrderNo()); paramMap.put("nonce_str",WXPayUtil.createNonceStr()); //获取签名 String sign = WXPayUtil.createSign("UTF-8", paramMap); paramMap.put("sign",sign); String requestXml = WXPayUtil.getRequestXml(paramMap); String result = HttpUtils.post(PayProperties.WX_PAY_PAY_ORDERQUERY_URL, requestXml); log.info("查询单号【{}】結果:{}",payCommons.getSecurityOrderNo(),result); if (StringUtils.isBlank(result)){ return Result.fail().setMesg("单号:"+payCommons.getSecurityOrderNo()+"查询结果为空"); } SortedMap<String, Object> map = null; try { map = WXPayUtil.startWXPay(result); } catch (Exception e) { e.printStackTrace(); log.error("微信支付订单号:{},查询结果解析异常",payCommons.getSecurityOrderNo()); return Result.fail().setMesg("请求结果解析有误"); } //对result转成的map进行解析 if (map.get("return_code").equals("FAIL")) { log.error("微信订单查询接口调用失败,原因:{}", map.get("return_msg").toString()); return Result.fail().setMesg("微信支付接口调用失败"); } if (map.get("return_code").equals("SUCCESS") && map.get("result_code").equals("FAIL")) { log.error("微信订单查询错误,错误代码:{},错误描述:{}", map.get("err_code"), map.get("err_code_des")); return Result.fail().setMesg("微信支付出现错误"); } map.put("sar_id",payCommons.getSarId()); return Result.success(map); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。