第三方支付接入(微信,支付宝)

写在最前面

以下内容关于微信相关的,除了binarywang,个人认为都不要再用了。相比较王大哥的封装,我自己写的真是连弟弟都不如。

支付宝相关的,好久不用了,不知道还能不能工作。建议找创建时间比较新的文章来看。

关于binarywang给我们二次封装的东西,目前本人使用了登录,支付两个最常见的功能。
具体的使用姿势,各位移步吧。

第三方API

时间 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之后问题就没有那么多了. 用起来还是蛮简单的,而且它支持多商户切换

QY

最近工作中安排了三方支付接入(一般就是微信,支付宝,银联),目前接入的是微信和支付宝;
阶段接近尾声并且测试很OK,现在记录一下开发过程.

目前只是对接了
支付宝的PC,H5;
微信的PC,H5,JSAPI这几种
APP的支付暂时没有需求,所以没做考虑

大家在对接过程中如果遇到问题,欢迎留言,我看到会及时回复的.发私信也可以.希望对大家有帮助;

对接前提

你需要在支付宝或微信平台创建自己的应用,得到我们后续需要用到的各种id和secret

支付宝是有SDK的,记得引入依赖

至于怎么在微信和支付宝的管理平台创建应用,大家自己百度一下吧.o(╥﹏╥)o

思考

对接外部接口,其实很简单,只需要不停尝试就好了,哈哈. 当前网上有很多资源都是相当可取的;
需要注意的是,支付宝所需要的公钥和私钥的生成方式,

此处需要特别注意下,开发者私钥,即我们通过支付宝的生成工具生成的应用私钥

而公钥,如下图
在这里插入图片描述
是我们通过上面生成的应用公钥,进行填写,保存之后,支付宝生成的,切记!!! 不然调不通接口的哦.
支付宝生成秘钥

其次
说白了,不管支付宝还是微信,支付逻辑无在乎一去一回这两下.
先做统一下单的操作,然后通过返回数据,做具体的逻辑处理
要注意的是,
支付宝的PC和H5支付返回给我们的是完整的form表单,我们只需要将其响应给前端,让前端做submit即可进行调起真正的支付动作;
而微信则有稍微不同, PC支付返回给我们的是一个二维码url,需要前端将其生成二维码展示; H5返回给我们的是一个web的url,由前端对其进行访问;

开发的时候本地可以,但是调试的时候,如果你没有调通接口,那么建议你线上进行调试;

类梳理

我业务中的controllerservice就不罗列了

  • 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);@Autowiredprivate SecAccountReceivableService secAccountReceivableService;/*** 支付宝支付充值* @param payParam* @return*/@Overridepublic 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 个人支付wapif (payParam.getTradeType().equals(PayCommons.TRADE_TYPE_PC)){return this.doPagePayRequest(alipayClient,payParam);}else {return this.doWapPayRequest(alipayClient,payParam);}}/*** 处理回调* @param request* @return*/@Overridepublic 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*/@Overridepublic 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);//业务类,应收账单,用来检验异步通知的数据准确性@Autowiredprivate SecAccountReceivableService secAccountReceivableService;/*** 支付充值** @param payParam* @return*/@Overridepublic 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 是用工卫士的nameparameterMap.put("scene_info", wxPayConfig.getSceneInfo());//生成签名String sign = WXPayUtil.createSign("UTF-8", parameterMap);//签名parameterMap.put("sign", sign);//map转xmlString 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*/@Overridepublic 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();//过滤空 设置 treeMapSortedMap<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);}//判断签名是否正确 isTenpaySignif (!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*/@Overridepublic 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);}
}

FAQ

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/71475.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WooCommerce接入支付宝微信支付

WooCommerce接入支付宝微信支付 前言安装支付宝插件&#xff08;方法一&#xff09;安装配置获取注册信息 安装支付宝插件&#xff08;方法二&#xff09;安装微信支付插件下载及安装获取微信公众号APPID&#xff0c;微信支付密钥获取微信公众号的AppID&#xff08;应用ID&…

Android接入支付宝和微信支付

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 前言 很多APP都需要支付功能&#xff0c;国内一般就是支付宝和微信了。目前这2种接入方式对于APP端来说都已经比较方便了&#xff0c;因为大部…

微信推送和支付宝付款接口的使用

我们在开发的过程中经常会碰到调用微信或者支付宝接口进行付款&#xff0c;付款完成之后&#xff0c;如果用户绑定了我的账号&#xff0c;我只要有活动了&#xff0c;就要给这个关注我的用户推动消息&#xff0c;让用户知道&#xff0c;比如说&#xff0c;我们经常会关注一些公…

网页调起支付宝付款和微信付款

网页调起支付宝付款和微信付款 昨天在指导客户制作付款网页的时候写了部分说明文档&#xff0c;觉得其中有很多对第一次接触这方面的人能有所帮助&#xff0c;在此分享一下 调起付款的步骤 1.获取微信code&#xff08;支付宝是auth_code&#xff0c;以下统称code) 不论是微…

个人如何接入支付宝或者微信支付等支付接口

企业途径&#xff1a;直接注册一个有资质的公司&#xff0c;并把工商局下发的企业许可证传给支付宝或者微信&#xff0c;让它们进行资质审核认证&#xff0c;如果微信或者支付宝觉得你的资质足够跟他们合作了&#xff08;整体来说很困难&#xff0c;不怎么容易达到足够的资质&a…

设计聊天机器人,5个误区小心别踩

设计聊天机器人&#xff0c;5个误区小心别踩&#xff01; 最近一段时间&#xff0c;各种AI对话工具大火&#xff0c;海外有ChatGPT&#xff0c;国内有百度下面的文心一言。越来越多人开始将智能工具搬到了自己的产品里。 而市场上有很多聊天工具都有自动化的功能&#xff0c;虽…

世界上最复杂的函数_巨蟹座是世界上最懒的星座。

01 巨蟹座是世界上最懒的星座。 1、世界上最懒的星座&#xff0c;别人整点拖延症&#xff0c;巨蟹整年拖延症。 让ta学习、工作、运动、洗头的最好方式是拿枪顶脑袋。 2、自我保护意识无比强&#xff01; 敏感排行居首位&#xff01; 自己说的话百般酝酿&#xff0c;别人说的话…

巨蟹座适合计算机应用和文秘,业务能力强 适合当秘书的星座

十二星座中&#xff0c;每种星座擅长的领域都不一样。有些人适合当老板&#xff0c;有些人适合当员工&#xff0c;有些人适合当秘书。一般来说&#xff0c;秘书是一个非常难做的位置&#xff0c;甚至比老板和员工还要辛苦。不过&#xff0c;对于下面这几个星座来说&#xff0c;…

巨蟹座 55e外表达2000度 火山活泼岩浆遍布

巨蟹座 55e外表达2000度 火山活泼岩浆遍布 这个发明暗示巨蟹座 55e这个超级地球的外表十分杂乱&#xff0c;猛烈的火山喷发招致熔岩流遍及寰球&#xff0c;全部地表大局部被掩盖着滚烫的火山喷发物。假如巨蟹座 55e 与木卫一进行对照&#xff0c;那可以巨蟹座 55e 更“绚烂”一…

计算机专业巨蟹座男生,巨蟹座男生适合的职业

巨蟹座男生适合的职业 巨蟹座是一个重视生活细节的星座&#xff0c;只要和生活有关的事物都会引起蟹子的重视。虽然做事低调的性格使蟹子更喜欢从事一些非前线的工作&#xff0c;但懂得如何掌握人心&#xff0c;在职场上往往也最容易展现人事处理的才干。所以蟹子最适合服务业&…

新媒体运营胡耀文教程:短视频脚本的3个套路,新手也能做爆款

你是否觉得&#xff0c;看短视频比看电视还过瘾&#xff1f;劲爆的画面、巧妙的神转折、不拖沓的剧情、嗨爆的BGM…都足以让人“忘却人间”。 而这一系列设计的背后&#xff0c;都承载于一个重要部分&#xff1a;短视频脚本。 就像拍电视剧、电影一样&#xff0c;短视频脚本是…

纯干货!短视频脚本怎么写?零基础新手小白也能写好短视频脚本!【覃小龙课堂】

hi&#xff0c;我是你的老朋友兼顾问&#xff1a;覃小龙&#xff0c;您可以叫我覃总。今天给您分享我做影视剪辑的一些经验方法&#xff0c;零粉丝也能做&#xff0c;主题名为&#xff1a; 纯干货&#xff01;短视频脚本怎么写&#xff0c;零基础也能写好短视频脚本 在写脚本以…

短视频脚本怎么写?6个套路

做短视频的基础就是脚本&#xff0c;脚本是创作的基石&#xff0c;是贯穿这个视频始末的逻辑。那么&#xff0c;短视频脚本怎么做呢&#xff1f;其实&#xff0c;在拍摄脚本里面&#xff0c;我们可以把所有的东西拆分为以下 6 个要素&#xff1a; 01 镜头景别 镜头分为远景、全…

实现排序的几种方式/sorted(o1,o2)

1 实现排序的几种方式 首先我们先看代码 List<Person> personList new ArrayList<>();personList.add(new Person("王一",1));personList.add(new Person("王二",2));personList.add(new Person("王五",5));personList.add(new Per…

经典十大排序算法(含升序降序,基数排序含负数排序)【Java版完整代码】【建议收藏系列】

经典十大排序算法【Java版完整代码】 写在前面的话十大排序算法对比冒泡排序快速排序直接选择排序堆排序归并排序插入排序希尔排序计数排序桶排序基数排序完整测试类 写在前面的话 虽然已经有很多人总结过这十大排序算法&#xff0c;优秀的文章也不少&#xff0c;但是Java完整版…

使用O2OA二次开发搭建企业办公平台(一)平台部署篇:平台下载和部署

转载&#xff1a;https://my.oschina.net/u/3931542/blog/2209110 本博客为O2OA系列教程&#xff0c;教程目录和各章节天梯将在连载完后更新。 服务器下载和安装 1、服务器安装包下载 访问http://www.o2oa.io网站&#xff0c;如下图所示: 在网站顶部导航里点击下载&#xff0…

O2PLS(绘制载荷图)--R

##绘制载荷图## gene_loading <- as.data.frame(fit0$W.) meta_loading <- as.data.frame(fit0$C.) colnames(gene_loading) <- c("pq1","pq2") colnames(meta_loading) <- c("pq1","pq2") #添加新的一列&#xff0c;按组…

Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合

Java8 Stream 1 Stream概述2 Stream的创建3 Stream的使用案例使用的员工类3.1 遍历/匹配&#xff08;foreach/find/match&#xff09;3.2 筛选&#xff08;filter&#xff09;3.3 聚合&#xff08;max/min/count)3.4 映射(map/flatMap)3.5 归约(reduce)3.6 收集(collect)3.6.1 …

O2S.Components.PDFView4NET显示pdf(winform)

O2S.Components.PDFView4NET显示pdf&#xff08;winform&#xff09; 首先得把这个dll导入工具箱 选中该dll组件 按确定就多出来这些东西 然后再按确定&#xff0c;工具箱就有下面这些组件啦 2. 拉一个PDFpageview&#xff0c;和一个pdfdocument就可以用来显示了 然后放代码…

Java8对中文汉字排序的Comparator实现类

最近由于工作需要需要对中文汉字排序&#xff0c;编写了Comparator实现类分享给大家。 直接上代码&#xff1a; import java.util.Comparator;public class ChineseComparator<T> implements Comparator<T> {private static boolean isDigit(char ch) {return ch …