微信扫码支付、网站接入微信支付-java

如果你的网站想接入微信支付,那么你的有个公众号(微信公众平台),然后开通支付功能,在微信商户平台操作。仔细看哦,这是两个平台,商家平台有详细的接入流程,这里只介绍程序方面。

1、准备

  • 准备商家帐户
  • 下载证书,重置密钥(密钥重置后请妥善保管)
  • 内网穿透软件(微信支付成功后会有回调)

2、代码

  • 加入依赖

微信支付比较麻烦,所以我们采用第三方封装的jar包

compile group: 'com.github.binarywang', name: 'weixin-java-pay', version: '3.0.0'
  • 新建 pay.properties
#wxpay
WX.APPID=wxdd088f0c1d70cbf9
WX.MCHID=你的商户id
WX.MCHKEY=你的密钥
WX.SUBAPPID=
WX.SUBMCHID=
WX.KEYPATH=
#下面这个是微信回调地址,要保证外网可以访问,内网测试就用内网穿透软件
WX.NOTIFYURL=http://tdcloud.trmap.cn/reward/getOrderNotifyResult
WX.TRADETYPE=NATIVE
  • 配置类

WxPayConfig 将配置文件里的值读取出来,微信支付需要的相关配置

@Configuration
@PropertySource(value="classpath:pay.properties", ignoreResourceNotFound=true)
public class WxPayConfig {

  /**
   * http请求连接超时时间
   */
  private int httpConnectionTimeout = 5000;

  /**
   * http请求数据读取等待时间
   */
  private int httpTimeout = 10000;

  private String appId;
  private String subAppId;
  private String mchId;
  private String mchKey;
  private String subMchId;
  private String notifyUrl;
  private String tradeType;
  private String signType;
  private SSLContext sslContext;
  private String keyPath;
  private boolean useSandboxEnv = false;
  private String httpProxyHost;
  private Integer httpProxyPort;
  private String httpProxyUsername;
  private String httpProxyPassword;

  public String getKeyPath() {
    return keyPath;
  }

  /**
   * 设置证书
   *
   * @param keyPath apiclient_cert.p12的文件的绝对路径
   */
  public void setKeyPath(String keyPath) {
    this.keyPath = keyPath;
  }

  /**
   * 商户号
   */
  public String getMchId() {
    return this.mchId;
  }

  public void setMchId(String mchId) {
    this.mchId = mchId;
  }

  /**
   * 商户密钥
   */
  public String getMchKey() {
    return this.mchKey;
  }

  public void setMchKey(String mchKey) {
    this.mchKey = mchKey;
  }

  /**
   * 公众号appid
   */
  public String getAppId() {
    return this.appId;
  }

  public void setAppId(String appId) {
    this.appId = appId;
  }

  /**
   * 服务商模式下的子商户公众账号ID
   */
  public String getSubAppId() {
    return this.subAppId;
  }

  public void setSubAppId(String subAppId) {
    this.subAppId = subAppId;
  }

  /**
   * 服务商模式下的子商户号
   */
  public String getSubMchId() {
    return this.subMchId;
  }

  public void setSubMchId(String subMchId) {
    this.subMchId = subMchId;
  }

  /**
   * 微信支付异步回掉地址,通知url必须为直接可访问的url,不能携带参数。
   */
  public String getNotifyUrl() {
    return this.notifyUrl;
  }

  public void setNotifyUrl(String notifyUrl) {
    this.notifyUrl = notifyUrl;
  }

  /**
   * 交易类型
   * <pre>
   * JSAPI--公众号支付
   * NATIVE--原生扫码支付
   * APP--app支付
   * </pre>
   */
  public String getTradeType() {
    return this.tradeType;
  }

  public void setTradeType(String tradeType) {
    this.tradeType = tradeType;
  }

  /**
   * 签名方式
   * 有两种HMAC_SHA256 和MD5
   * @see com.github.binarywang.wxpay.constant.WxPayConstants.SignType
   */
  public String getSignType() {
    return this.signType;
  }

  public void setSignType(String signType) {
    this.signType = signType;
  }

  public SSLContext getSslContext() {
    return this.sslContext;
  }

  public void setSslContext(SSLContext sslContext) {
    this.sslContext = sslContext;
  }

  /**
   * 微信支付是否使用仿真测试环境
   * 默认不使用
   */
  public boolean useSandbox() {
    return this.useSandboxEnv;
  }

  /**
   * 设置是否使用沙箱仿真测试环境
   */
  public void setUseSandboxEnv(boolean useSandboxEnv) {
    this.useSandboxEnv = useSandboxEnv;
  }

  public SSLContext initSSLContext() throws WxPayException {
    if (StringUtils.isBlank(this.getMchId())) {
      throw new WxPayException("请确保商户号mchId已设置");
    }

    if (StringUtils.isBlank(this.getKeyPath())) {
      throw new WxPayException("请确保证书文件地址keyPath已配置");
    }

    InputStream inputStream;
    final String prefix = "classpath:";
    String fileHasProblemMsg = "证书文件【" + this.getKeyPath() + "】有问题,请核实!";
    String fileNotFoundMsg = "证书文件【" + this.getKeyPath() + "】不存在,请核实!";
    if (this.getKeyPath().startsWith(prefix)) {
      String path = StringUtils.removeFirst(this.getKeyPath(), prefix);
      if (!path.startsWith("/")) {
        path = "/" + path;
      }
      inputStream = WxPayConfig.class.getResourceAsStream(path);
      if (inputStream == null) {
        throw new WxPayException(fileNotFoundMsg);
      }
    } else {
      try {
        File file = new File(this.getKeyPath());
        if (!file.exists()) {
          throw new WxPayException(fileNotFoundMsg);
        }

        inputStream = new FileInputStream(file);
      } catch (IOException e) {
        throw new WxPayException(fileHasProblemMsg, e);
      }
    }

    try {
      KeyStore keystore = KeyStore.getInstance("PKCS12");
      char[] partnerId2charArray = this.getMchId().toCharArray();
      keystore.load(inputStream, partnerId2charArray);
      this.sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
      return this.sslContext;
    } catch (Exception e) {
      throw new WxPayException(fileHasProblemMsg, e);
    } finally {
      IOUtils.closeQuietly(inputStream);
    }
  }

  /**
   * http请求连接超时时间
   */
  public int getHttpConnectionTimeout() {
    return this.httpConnectionTimeout;
  }

  public void setHttpConnectionTimeout(int httpConnectionTimeout) {
    this.httpConnectionTimeout = httpConnectionTimeout;
  }

  /**
   * http请求数据读取等待时间
   */
  public int getHttpTimeout() {
    return this.httpTimeout;
  }

  public void setHttpTimeout(int httpTimeout) {
    this.httpTimeout = httpTimeout;
  }

  public String getHttpProxyHost() {
    return httpProxyHost;
  }

  public void setHttpProxyHost(String httpProxyHost) {
    this.httpProxyHost = httpProxyHost;
  }

  public Integer getHttpProxyPort() {
    return httpProxyPort;
  }

  public void setHttpProxyPort(Integer httpProxyPort) {
    this.httpProxyPort = httpProxyPort;
  }

  public String getHttpProxyUsername() {
    return httpProxyUsername;
  }

  public void setHttpProxyUsername(String httpProxyUsername) {
    this.httpProxyUsername = httpProxyUsername;
  }

  public String getHttpProxyPassword() {
    return httpProxyPassword;
  }

  public void setHttpProxyPassword(String httpProxyPassword) {
    this.httpProxyPassword = httpProxyPassword;
  }
}
  • 发起支付,获取生成二维码的地址

微信支付的单位是分,例如你支付金额是9.9元(保留两位小数),那你微信支付的时候支付金额是9.9元*100 = 990分,最后支付金额是整数。

封装请求参数,请求微信接口后会返回生成二维码的地址

    @Autowired
    private WxPayConfig payConfig;
    @Autowired
    private WxPayService wxService; //上边依赖第三方库提供的

    
    @RequestMapping("wxpay")
    private ResponseEntity<Result> rewardWxpay(@RequestParam Double money, HttpServletRequest request,WxPayUnifiedOrderRequest wxreq){
        try {
                
    @RequestMapping("wxpay")
    private ResponseEntity<Result> rewardWxpay(String userid,@RequestParam String produceid,@RequestParam Double money,String remark, HttpServletRequest request,WxPayUnifiedOrderRequest wxreq){
        try {
            // TODO 生成你的订单信息,然后返回订单id,这里代码忽略,根据自己的业务调整
            String ip = req.getHeader("x-forwarded-for");
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
               ip = req.getHeader("Proxy-Client-IP");
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                   ip = req.getHeader("WL-Proxy-Client-IP");
            }
             if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                   ip = req.getRemoteAddr();
            }
            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {  
                // 多次反向代理后会有多个ip值,第一个ip才是真实ip
                if( ip.indexOf(",")!=-1 ){
                    ip = ip.split(",")[0];
                }
            }
            request.setBody("实景在线打赏-"); // 支付标题
            request.setOutTradeNo(你自己的订单编号);
            // 金额一定要以分为单位,所以最后支付金额为整数
            String string = String.valueOf((reward.getMoney()*100));
            request.setTotalFee(Integer.parseInt(string.substring(0,string.indexOf("."))));
            request.setAppid(payConfig.getAppId());
            request.setMchId(payConfig.getMchId());
            request.setNotifyUrl(payConfig.getNotifyUrl());
            request.setSpbillCreateIp(ip);
            
            request.setTradeType(payConfig.getTradeType());
            //this.wxService.unifiedOrder(request) 发起请求,获取地址,然后根据地址生成二维码
            return SUCCESS(this.wxService.unifiedOrder(request););
        } catch (WxPayException e) {
            e.printStackTrace();
        }
        return ERROR("发起支付失败");
    }
        } catch (WxPayException e) {
            e.printStackTrace();
        }
        return ERROR("发起支付失败");
    }
  • 生成二维码

根据上一步请求返回的地址生成二维码,生成的二维码是base64格式的字节码,前台用img标签直接显示即可,这时候用户就可以扫描生成的二维码进行支付了

/**
     * <pre>
     * 扫码支付模式二生成二维码的方法
     * 对应链接格式:weixin://wxpay/bizpayurl?sr=XXXXX。请商户调用第三方库将code_url生成二维码图片。
     * 该模式链接较短,生成的二维码打印到结账小票上的识别率较高。
     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
     * </pre>
     *
     * @param codeUrl
     *            微信返回的交易会话的二维码链接
     * @param logoFile
     *            商户logo图片的文件对象,可以为空
     * @param sideLength
     *            要生成的二维码的边长,如果为空,则取默认值400
     * @return 生成的二维码的字节数组
     */

    @PostMapping(value = "/createScanPayQrcode")
    public ResponseEntity<Result> createQrcode(String codeUrl,File logoFile, Integer sideLength) {
        byte[] by = this.wxService.createScanPayQrcodeMode2(codeUrl, logoFile, sideLength);
        return SUCCESS(by);
    }
  • 回调接口

用户扫描支付后,微信会异步通知,请求地址为配置文件中的接口地址,所以要保证公网可以访问。微信共会请求8次回调接口,如果处理成功后,将不在请求回调接口

    /**
     * 读取支付结果通知
     *
     * @param xmlData
     * @throws WxPayException 
     */
    @RequestMapping(value = "/getOrderNotifyResult", method = RequestMethod.POST)
    public String getOrderNotifyResult(@RequestBody String xmlData,HttpServletRequest request,
            HttpServletResponse response) throws WxErrorException, WxPayException {
        WxPayOrderNotifyResult orderNotifyResult = this.wxService.parseOrderNotifyResult(xmlData);
        String noticeStr = null;
        try {
            BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream());

            // 支付成功,商户处理后同步返回给微信参数
            if (!orderNotifyResult.getResultCode().equals("SUCCESS")) {
                // 支付失败, 记录流水失败
                System.out.println("===============支付失败==============");
                noticeStr = setXML("FAIL", "支付失败");
                outputStream.write(noticeStr.getBytes());
                outputStream.flush();
                outputStream.close();
            } else {
                // TODO 处理你的业务,修改订单状态等
                // 通知微信已经收到消息,不要再给我发消息了,否则微信会8连击调用本接口
                noticeStr = setXML("SUCCESS", "支付成功");
                outputStream.write(noticeStr.getBytes());
                outputStream.flush();
                outputStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return noticeStr;
    }
    
    public String setXML(String return_code, String return_msg) {
            return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
    }

到这里,网站接入微信扫码支付,代码部分就全部完成了,最后效果图请看下方图片。

这里写图片描述

这里写图片描述

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序猿DD

Spring Cloud Feign的文件上传实现

在Spring Cloud封装的Feign中并不直接支持传文件,但可以通过引入Feign的扩展包来实现,本文就来具体说说如何实现。 ? 服务提供方(接收文件) ...

31411
来自专栏一个会写诗的程序员的博客

第11章 Spring Boot应用监控第11章 Spring Boot应用监控小结

在实际的生产系统中,我们怎样知道我们的应用运行良好呢?我们往往需要对系统实际运行的情况(各种cpu,io,disk,db,业务功能等指标)进行监控运维。这需要耗...

1553
来自专栏码匠的流水账

storm drpc实例

492
来自专栏酷玩时刻

Android版-微信APP支付

服务端源码地址:http://git.oschina.net/javen205/weixin_guide 客户端源码地址:https://github.com...

1292
来自专栏猿天地

spring-data-mongodb之gridfs

mongodb除了能够存储大量的数据外,还内置了一个非常好用的文件系统。 基于mongodb集群的优势,GridFS当然也是分布式的,而且备份也方便。 当用户把...

37712
来自专栏Spring相关

oauth2.0实现sso单点登录的方式和相关代码

百科:SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的...

1362
来自专栏IT笔记

SpringBoot开发案例之微信小程序文件上传

最近在做一个口语测评的小程序服务端,小程序涉及到了音频文件的上传,按理说应该统一封装一个第三方上传接口服务提供给前段调用,但是开发没有那么多道理,暂且为了省事就...

5587
来自专栏菩提树下的杨过

分布式服务框架 dubbo/dubbox 入门示例

dubbo是一个分布式的服务架构,可直接用于生产环境作为SOA服务框架。 官网首页:http://dubbo.io/ ,官方用户指南 http://dubbo....

44010
来自专栏Java技术栈

Tomcat集群session复制与Oracle的坑。。

问题描述 公司某个系统使用了tomcat自带的集群session复制功能,然后后报了一个oracle驱动包里面的连接不能被序列化的异常。 01-Nov-2017...

3469
来自专栏码农笔录

微信扫码支付、网站接入微信支付-Java

1762

扫码关注云+社区