微信扫码支付、网站接入微信支付-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 条评论
登录 后参与评论

相关文章

来自专栏java 成神之路

使用 NIO 实现 echo 服务器

4617
来自专栏张善友的专栏

Mix 10 上的asp.net mvc 2的相关Session

Beyond File | New Company: From Cheesy Sample to Social Platform Scott Hansel...

2577
来自专栏魂祭心

原 canvas绘制clock

4064
来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

2968
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3145
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

2645
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

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

Spring Reactor 项目核心库Reactor Core

Non-Blocking Reactive Streams Foundation for the JVM both implementing a Reactiv...

2152
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

4025
来自专栏杨龙飞前端

scrollto 到指定位置

2494

扫码关注云+社区