欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-01-26 12:04:52
...

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);
    @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);
    }
}

FAQ

相关标签: 工作中