vue+springboot+微信支付+native+apiv3

前言

微信支付的开发文档每次看都觉得无从下手,看了很多的博客,终于跑通了,现在记录一下,以便下次使用。

准备工作

准备工作可以自己去网上百度,这里主要写的是后端请求支付。

springboot方面

maven引入wechatpay-java

  		<dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.6</version>
        </dependency>
        
        <!--json处理器-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

下面的两个类是重点,很多网上的博客都是因为没有这两个类才跑不通的,我也是自己下载了官方的实例,从里面找的

CloseOrderRequest类

// Copyright 2021 Tencent Inc. All rights reserved.
//
// Native支付
//
// Native支付API
//
// API version: 1.2.3

// Code generated by WechatPay APIv3 Generator based on [OpenAPI
// Generator](https://openapi-generator.tech); DO NOT EDIT.

package cn.lasons.eps.wxpay.model;

import static com.wechat.pay.java.core.util.StringUtil.toIndentedString;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

/** CloseOrderRequest */
public class CloseOrderRequest {
  /** outTradeNo */
  @SerializedName("out_trade_no")
  @Expose(serialize = false)
  private String outTradeNo;
  /** mchid 说明:直连商户号 */
  @SerializedName("mchid")
  private String mchid;

  public String getOutTradeNo() {
    return outTradeNo;
  }

  public void setOutTradeNo(String outTradeNo) {
    this.outTradeNo = outTradeNo;
  }

  public String getMchid() {
    return mchid;
  }

  public void setMchid(String mchid) {
    this.mchid = mchid;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class CloseOrderRequest {\n");
    sb.append("    outTradeNo: ").append(toIndentedString(outTradeNo)).append("\n");
    sb.append("    mchid: ").append(toIndentedString(mchid)).append("\n");
    sb.append("}");
    return sb.toString();
  }
}

PrepayRequest类

// Copyright 2021 Tencent Inc. All rights reserved.
//
// Native支付
//
// Native支付API
//
// API version: 1.2.3

// Code generated by WechatPay APIv3 Generator based on [OpenAPI
// Generator](https://openapi-generator.tech); DO NOT EDIT.

package cn.lasons.eps.wxpay.model;

import static com.wechat.pay.java.core.util.StringUtil.toIndentedString;

import com.google.gson.annotations.SerializedName;
import com.wechat.pay.java.service.lovefeast.model.Amount;
import com.wechat.pay.java.service.partnerpayments.app.model.Detail;
import com.wechat.pay.java.service.partnerpayments.app.model.SceneInfo;
import com.wechat.pay.java.service.partnerpayments.app.model.SettleInfo;

import java.util.List;

/** PrepayRequest */
public class PrepayRequest {
  /** 公众号ID 说明:公众号ID */
  @SerializedName("appid")
  private String appid;
  /** 直连商户号 说明:直连商户号 */
  @SerializedName("mchid")
  private String mchid;
  /** 商品描述 说明:商品描述 */
  @SerializedName("description")
  private String description;
  /** 商户订单号 说明:商户订单号 */
  @SerializedName("out_trade_no")
  private String outTradeNo;
  /** 交易结束时间 说明:订单失效时间,格式为rfc3339格式 */
  @SerializedName("time_expire")
  private String timeExpire;
  /** 附加数据 说明:附加数据 */
  @SerializedName("attach")
  private String attach;
  /** 通知地址 说明:有效性:1. HTTPS;2. 不允许携带查询串。 */
  @SerializedName("notify_url")
  private String notifyUrl;
  /** 订单优惠标记 说明:商品标记,代金券或立减优惠功能的参数。 */
  @SerializedName("goods_tag")
  private String goodsTag;
  /** limitPay */
  public enum LimitPayEnum {
    @SerializedName("no_balance")
    NO_BALANCE,

    @SerializedName("no_credit")
    NO_CREDIT,

    @SerializedName("no_debit")
    NO_DEBIT,

    @SerializedName("balance_only")
    BALANCE_ONLY
  }

  @SerializedName("limit_pay")
  private List<LimitPayEnum> limitPay;
  /** 电子发票入口开放标识 说明:传入true时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效。 */
  @SerializedName("support_fapiao")
  private Boolean supportFapiao;
  /** amount */
  @SerializedName("amount")
  private Amount amount;
  /** detail */
  @SerializedName("detail")
  private Detail detail;
  /** settleInfo */
  @SerializedName("settle_info")
  private SettleInfo settleInfo;
  /** sceneInfo */
  @SerializedName("scene_info")
  private SceneInfo sceneInfo;

  public String getAppid() {
    return appid;
  }

  public void setAppid(String appid) {
    this.appid = appid;
  }

  public String getMchid() {
    return mchid;
  }

  public void setMchid(String mchid) {
    this.mchid = mchid;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public String getOutTradeNo() {
    return outTradeNo;
  }

  public void setOutTradeNo(String outTradeNo) {
    this.outTradeNo = outTradeNo;
  }

  public String getTimeExpire() {
    return timeExpire;
  }

  public void setTimeExpire(String timeExpire) {
    this.timeExpire = timeExpire;
  }

  public String getAttach() {
    return attach;
  }

  public void setAttach(String attach) {
    this.attach = attach;
  }

  public String getNotifyUrl() {
    return notifyUrl;
  }

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

  public String getGoodsTag() {
    return goodsTag;
  }

  public void setGoodsTag(String goodsTag) {
    this.goodsTag = goodsTag;
  }

  public List<LimitPayEnum> getLimitPay() {
    return limitPay;
  }

  public void setLimitPay(List<LimitPayEnum> limitPay) {
    this.limitPay = limitPay;
  }

  public Boolean getSupportFapiao() {
    return supportFapiao;
  }

  public void setSupportFapiao(Boolean supportFapiao) {
    this.supportFapiao = supportFapiao;
  }

  public Amount getAmount() {
    return amount;
  }

  public void setAmount(Amount amount) {
    this.amount = amount;
  }

  public Detail getDetail() {
    return detail;
  }

  public void setDetail(Detail detail) {
    this.detail = detail;
  }

  public SettleInfo getSettleInfo() {
    return settleInfo;
  }

  public void setSettleInfo(SettleInfo settleInfo) {
    this.settleInfo = settleInfo;
  }

  public SceneInfo getSceneInfo() {
    return sceneInfo;
  }

  public void setSceneInfo(SceneInfo sceneInfo) {
    this.sceneInfo = sceneInfo;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class PrepayRequest {\n");
    sb.append("    appid: ").append(toIndentedString(appid)).append("\n");
    sb.append("    mchid: ").append(toIndentedString(mchid)).append("\n");
    sb.append("    description: ").append(toIndentedString(description)).append("\n");
    sb.append("    outTradeNo: ").append(toIndentedString(outTradeNo)).append("\n");
    sb.append("    timeExpire: ").append(toIndentedString(timeExpire)).append("\n");
    sb.append("    attach: ").append(toIndentedString(attach)).append("\n");
    sb.append("    notifyUrl: ").append(toIndentedString(notifyUrl)).append("\n");
    sb.append("    goodsTag: ").append(toIndentedString(goodsTag)).append("\n");
    sb.append("    limitPay: ").append(toIndentedString(limitPay)).append("\n");
    sb.append("    supportFapiao: ").append(toIndentedString(supportFapiao)).append("\n");
    sb.append("    amount: ").append(toIndentedString(amount)).append("\n");
    sb.append("    detail: ").append(toIndentedString(detail)).append("\n");
    sb.append("    settleInfo: ").append(toIndentedString(settleInfo)).append("\n");
    sb.append("    sceneInfo: ").append(toIndentedString(sceneInfo)).append("\n");
    sb.append("}");
    return sb.toString();
  }
}

接下来就是参数的填写了

package cn.lasons.eps.wxpay;

import cn.lasons.eps.wxpay.model.PrepayRequest;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.lovefeast.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

@Slf4j
public class WXPayConstants {

    /*appid*/
    public static final String APP_ID = "";
    /*商户号*/
    public static final String MCH_ID = "";
    /* 商户证书序列号 */
    public static final String MCH_SERIAL_NO = "";
    /*APIv3密钥*/
    public static final String API_V3KEY = "";
    /*支付成功回调地址*/
    public static final String NOTIFY_URL = "xxxx/wxPayNotify";
    /*序列号地址*/
    public static final String PRIVATE_KEY_PATH = "apiclient_key.pem";

    private static NativePayService service;

    /**
     * 微信预支付
     */
    public static Map<String, String> nativePayV3(Integer money, String outTradeNo) {
        if (service == null) {
            Config config = new RSAAutoCertificateConfig.Builder()
                    .merchantId(MCH_ID)
                    .privateKeyFromPath(PRIVATE_KEY_PATH)
                    .merchantSerialNumber(MCH_SERIAL_NO)
                    .apiV3Key(API_V3KEY)
                    .build();

            service = new NativePayService.Builder().config(config).build();
        }

        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(money);
        request.setAmount(amount);
        request.setAppid(APP_ID);
        request.setMchid(MCH_ID);
        request.setDescription("积分充值");
        request.setNotifyUrl(NOTIFY_URL);
        request.setOutTradeNo(outTradeNo);
        PrepayResponse response = service.prepay(request);

        Map<String, String> payParameters = new HashMap<>();
        payParameters.put("code_url", response.getCodeUrl());
        return payParameters;
    }

	/**验签*/
    public static boolean signVerify(String serial, String message, String signature) {
        // 从证书管理器中获取verifier
        Verifier verifier = null;
        try {
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(ResourceUtils.getFile(WXPayConstants.PRIVATE_KEY_PATH)));
            // 获取证书管理器实例
            CertificatesManager certificatesManager = CertificatesManager.getInstance();
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant(WXPayConstants.MCH_ID, new WechatPay2Credentials(WXPayConstants.MCH_ID,
                    new PrivateKeySigner(WXPayConstants.MCH_SERIAL_NO, merchantPrivateKey)), WXPayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
            // 从证书管理器中获取verifier
            verifier = certificatesManager.getVerifier(WXPayConstants.MCH_ID);
            return verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature);
        } catch (IOException e) {
            log.error("验签发送异常", e);
        } catch (GeneralSecurityException e) {
            log.error("验签发送异常", e);
        } catch (HttpCodeException e) {
            log.error("验签发送异常", e);
        } catch (NotFoundException e) {
            log.error("验签发送异常", e);
        }
        return false;
    }

}

万事俱备,可以直接创建post请求

       @PostMapping("/wx/pay_native_v3_demo")
    public Map<String, String> nativeV3Demo(@RequestBody PayBody data) {
        return WXPayConstants.nativePayV3(1, "20231020103211111");
    }
    @PostMapping(value = "/wxPayNotify")
    public Map payCallBack(HttpServletRequest request,
                                 @RequestBody WeChatPayResultIn weChatPayResultIn) {
        Map result = new HashMap();
        result.put("code", wxPayService.wechatPayCallBack(weChatPayResultIn, request));
        return result;
    }

WeChatPayResultIn

package cn.lasons.eps.wxpay;

import lombok.Data;

@Data
public class WeChatPayResultIn {
    private String id;
    private String create_time;
    private String resource_type;
    private String event_type;
    private String summary;
    private WechatPayResourceIn resource;
}

WechatPayResourceIn 类

package cn.lasons.eps.wxpay;

import lombok.Data;

@Data
public class WechatPayResourceIn {
    private String original_type;
    private String algorithm;
    private String ciphertext;
    private String associated_data;
    private String nonce;
}

wechatPayCallBack方法如下

@Transactional
    public Map wechatPayCallBackDemo(WeChatPayResultIn weChatPayResultIn, HttpServletRequest request) {
        log.error("微信结果通知字符串:{}" + JSON.toJSONString(weChatPayResultIn));
        String nonce = weChatPayResultIn.getResource().getNonce();
        String ciphertext = weChatPayResultIn.getResource().getCiphertext();
        String associated_data = weChatPayResultIn.getResource().getAssociated_data();
        AesUtil aesUtil = new AesUtil(WXPayConstants.API_V3KEY.getBytes());
        Map result = new HashMap();
        result.put("code", "FAIL");
        BufferedReader reader = null;
        try {
            //验签
            StringBuilder signStr = new StringBuilder();
            // 请求时间戳\n
            signStr.append(request.getHeader("Wechatpay-Timestamp")).append("\n");
            // 请求随机串\n
            signStr.append(request.getHeader("Wechatpay-Nonce")).append("\n");
            reader = request.getReader();
            log.error("reader:=========>" + reader.toString());
            String str = null;
            StringBuilder builder = new StringBuilder();
            while ((str = reader.readLine()) != null) {
                builder.append(str);
            }
            log.error("请求报文主体: {}", builder);
            signStr.append(builder.toString()).append("\n");
            // 1.验签
            if (!WXPayConstants.signVerify(request.getHeader("Wechatpay-Serial"), signStr.toString(), request.getHeader("Wechatpay-Signature"))) {
                result.put("message", "sign error");
                log.error("验签失败-sign error");
                return result;
            }
            // 解密请求体
            String s = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
            log.error("解密结果");
            log.error(s);
            JSONObject jsonObject = JSONObject.parseObject(s);
            log.error("json格式化的结果");
            log.error(jsonObject.toJSONString());
            //订单号
            String orderId = jsonObject.getString("out_trade_no");
            //微信支付订单号
            String transactionId = jsonObject.getString("transaction_id");

            String tradeState = jsonObject.getString("trade_state");
            //幂等性
            if (StringUtils.equals(tradeState, "USERPAYING")) {
                //还在支付中,不做处理
                return null;
            }
            if (StringUtils.equals(tradeState, "SUCCESS")) {
                // TODO: 2023/10/20 自己的业务逻辑 
                result.put("code", "OK");
                return result;
            }
        } catch (GeneralSecurityException e) {
            log.error("微信支付回调数据处理及自己项目逻辑异常", e);
            result.put("message", e.getMessage());
        } catch (IOException e) {
            log.error("微信支付回调获取body流异常", e);
            result.put("message", e.getMessage());
        }
        return result;
    }

成功的实例如下

{
	"code_url": "weixin://wxpay/bizpayurl?pr=pZ053QLxxx"
}

看到这个就完成大半了,直接在网上找一个链接转二维码的网站,将后面的weixin:xxx复制,然后就能生成二维码,再用手机的微信扫码,就可以完成支付了。

vue方面

vue主要就是发起一个axios请求,然后将获取到的链接转换为二维码即可,操作如下:

安装链接转二维码

npm install  qrcodejs2 --save

写一个div元素去显示二维码

 <div  id="qrcode" ref="qrcode" />

发起请求

    handPay(item) {
      pay().then(res => {
        console.log('查看返回的数据', res)
        this.init(res.data.code_url)
      }).catch(console.error)
    },
    init(code) {
      document.getElementById('qrcode').innerHTML = ''
      this.qrcode = new QRCode('qrcode', {
        width: 200, // 二维码宽度
        height: 200, // 二维码高度
        text: code
      })
    },

axios请求

import request from '@/utils/request'

export function pay(data) {
  return request({
    url: '/wx/pay_native_v3',
    method: 'post',
    data: data
  })
}

这里的request是自己封装axios的,代码如下。里面的getToken可以直接换成自己的token,这里不再赘述。

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_URL, // 自己项目的地址
  withCredentials: true, // send cookies when cross-domain requests
  timeout: 10000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent
    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  response => {
    const { data } = response

    // if the custom code is not 20000, it is judged as an error.
    if (data.code != 20000) {
      Message({
        message: data.data || data.msg || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.msg || error.data,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

结语

至此,一次简单的vue+springboot对接微信支付的native方式就成功了。回头看,很多的博客都是忽略一些东西,看的云里雾里的。跑通了就自己记录下来,下次少绕点圈子。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值