java 微信小程序支付

时间:2024-02-26 10:22:24

开发前的准备:(必须)

  1.小程序标识(appid) 2.商户号(mch_id)3.商户密钥(key

  4.API证书(apiclient_cert.p12)这个证书根据自己需求添加

  API证书(我这里用到所以要加,自己不需要的就不要加吧):可以去微信商户平台下载,自己百度 

 我们使用官网的SDK进行开发: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1

 

 

 我们将官网的SDK用IDEA打开看看

 

 

 可以看到目前微信官网最新的SDK是v3.0.9版本

点击README.md可以看到官方给的Demo

直接将SDKjava类复制到项目中,注意也要讲pom文件中的依赖也要添加到自己的项目中

我的API证书放在了这里

 

 

 第一步:配置MyWXConfig类 继承WXPayConfig 抽象类

/**
 * @author: zhuhualian
 * @date: 2019-11-15 10:46
 * @description:
 */

public class MyWXConfig extends WXPayConfig {

    private byte[] certData;

    public MyWXConfig() throws Exception {
        String path = "cert/apiclient_cert.p12";
        File file = new File(this.getClass().getClassLoader().getResource(path).getFile());
        //File file = new File("/data/project/cert/apiclient_cert.p12");//这个是你API证书放在服务器上的位置(上传服务器时记得换这里)
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }
    @Override
    public String getAppID() {
        return "xxxxx";//你自己的小程序AppID
    }

    @Override
    public String getMchID() {
        return "xxxxx";//你自己的微信商户ID
    }

    @Override
    public String getKey() {
        return "xxxxx";//你自己的微信商户密钥
    }

    @Override
    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    @Override
    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    @Override
    public int getHttpReadTimeoutMs() {
        return 10000;
    }

    @Override
    IWXPayDomain getWXPayDomain() {

        IWXPayDomain iwxPayDomain=new IWXPayDomain() {
            @Override
            public void report(String domain, long elapsedTimeMillis, Exception ex) {

            }

            @Override
            public DomainInfo getDomain(WXPayConfig config) {
                return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API,true);//微信工具常量类有 "api.mch.weixin.qq.com";   wxpay.unifiedorder()  /pay/unifiedorder
            }
        };
        return iwxPayDomain;
    }
}

  

第二步:找到 SDK 中的 WxPay 类 修改里面的代码

public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
        this.config = config;
        this.notifyUrl = notifyUrl;
        this.autoReport = autoReport;
        this.useSandbox = useSandbox;
        if (useSandbox) {
            this.signType = SignType.MD5; // 沙箱环境
        }
        else {
            //this.signType = SignType.HMACSHA256;
            this.signType = SignType.MD5;  //注意:这点是个坑!   默认是HMACSHAS56加密 一定要修改成MD5  不然无论如何都会报  “微信签名失败” 的错误!
} this.wxPayRequest = new WXPayRequest(config); }

第三步:编写统一下单和返回给小程序需要的参数即可

openid就是微信用户登录你的小程序授权你获得的openid

outtradeno:订单号

IpUtils:获取IP的工具类,后面会将代码贴出来

交易类型trade_type:

  NATIVE -- 原生支付
  JSAPI -- 公众号支付 - 小程序支付
  MWEB -- H5支付
  APP
 -- app支付

 

public Object pay2(String openid,String outtradeno, HttpServletRequest request) throws Exception {
    //统一下单支付
    HashMap<String, String> map = new HashMap<>();
    Map<String, String> data = new HashMap<>();
    data.put("body", "微信支付");                     //商品描述
    data.put("total_fee", "1");                   //  标价金额  单位:分
    data.put("openid", openid);                  //用户标识 trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识
    data.put("out_trade_no", outtradeno + "");                       //商户系统内部订单号

    data.put("nonce_str", WXPayUtil.generateNonceStr());        //随机字符串,长度要求在32位以内。推荐随机数生成算法
    data.put("spbill_create_ip",IpUtils.getIpAddr(request));             //支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP  自定获取ip
    data.put("notify_url", "120.25.214.5:8082/wx/notify");    // 没用到.通知地址:通知url必须为外网可访问的url,不能携带参数。
    data.put("trade_type", "JSAPI");//交易类型
    data.put("sign_type", WXPayConstants.MD5); //签名类型//MyWxPayConfig 配置了一些默认信息 appid,商户号,商户秘钥,请求域名 ..
    MyWXConfig myWxPayConfig = new MyWXConfig();
    WXPay wxpay = new WXPay(myWxPayConfig);
    Map<String, String> rMap = wxpay.unifiedOrder(data);//生成一次签名 sign
    System.out.println(rMap);
    // 下面只是为了生成第二次签名  仅此而已
    String return_code = rMap.get("return_code");//返回状态码
    String result_code = rMap.get("result_code");//结果状态码
    String nonce_str = rMap.get("nonce_str"); //随即字符串
    Long s = System.currentTimeMillis() / 1000;//获取时间戳除以千变字符串
    String timeStamp = String.valueOf(s);
    if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) {
        map.put("appId", myWxPayConfig.getAppID());//你的appid
        map.put("timeStamp", timeStamp);//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
        map.put("nonceStr", nonce_str);
        map.put("package", "prepay_id=" + rMap.get("prepay_id"));
        map.put("signType", "MD5");
        System.out.println("二次签名参数 : " + map);//需要生成二次签名 所用的参数
        //再次签名sign,这个签名用于小程序端调用wx.requesetPayment方法
        String sign = WXPayUtil.generateSignature(map, myWxPayConfig.getKey());//你的商户号key
        map.put("paySign", sign); // 生成签名 重要
        System.out.println("生成的签名paySign : " + sign);
    return map;      //将map响应给前端   微信支付接口需要的参数
    }
    return new Result<>("0",Result.ERROR);
}

  

  

IpUtils

public class IpUtils {

    /* IpUtils工具类方法
     * 获取真实的ip地址
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");
            if(index != -1){
                return ip.substring(0,index);
            }else{
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
            return ip;
        }
        return request.getRemoteAddr();
    }
}

  容易遇到的错误 !   容易遇到的错误 !    容易遇到的错误 !

  1 商户号key 不要与 appid secret 弄混淆了

   2 SDK 工具类中 Wxpay 类中     this.signType = SignType.HMACSHA256;  HMACSHA256 改成 MD5

  3 第二次签名需要的五个参数一个不能少 appId,nonceStr,package,signType,timeStamp。 注意 都是以 驼峰命名  不然也会报错

  OK,搞定了!!!!!!!!!!!!!!!!!!