[Java]微信支付apiv3接入demo 复制可用(jsapi 小程序)

若干年前接过微信和支付宝,当时微信还是xml,接完以后当时就觉着微信的接口弄的烂的一批,,,。最近又有接入微信支付,相比之前好了一点吧,但是感觉微信还是弄的太散乱了。

以下内容仅为减少代码的重复劳动,并不提供接入流程、字段、配置说明,开始前请明确已经阅读过微信支付文档,了解接入流程,并获取了秘钥证书相关内容

包含的接口有,jsapi下单、小程序调起支付加签、回调数据处理、微信支付平台证书更新。功能包括数据加验签,加解密

代码中注意使用官方类库,以保证资金安全。当前为2021年8月,时间久远内容可能出现差异,注意识别

maven引入微信支付sdk

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.2.2</version>
</dependency>

aes解密方法(AesUtil)直接复制官方示例

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AesUtil {

	static final int KEY_LENGTH_BYTE = 32;
	static final int TAG_LENGTH_BIT = 128;
	private final byte[] aesKey;

	public AesUtil(byte[] key) {
		if (key.length != KEY_LENGTH_BYTE) {
			throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
		}
		this.aesKey = key;
	}

	public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
	throws GeneralSecurityException, IOException {
		try {
			Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

			SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
			GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

			cipher.init(Cipher.DECRYPT_MODE, key, spec);
			cipher.updateAAD(associatedData);

			return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
		} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
			throw new IllegalStateException(e);
		} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
			throw new IllegalArgumentException(e);
		}
	}
}

参数配置类

public class WeiXinProties {
    @Getter
    private static final String APP_ID = "微信小程序APPID";
    @Getter
    private static final String MERC_ID = "微信支付商户号";
    @Getter
    private static final String SECRET = "微信小程序安全码";
    @Getter
    private static final String SERIAL_NO = "微信支付证书编号";
    @Getter
    private static final String PRIVATE_KEY = "微信支付证书私钥,就是下载的apiclient_key.pem内容,去掉头尾换行";
    @Getter
    private static final String NOTIFY_URL = "支付结果回调地址";
    @Getter
    private static final String WEIXINPAY_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";//微信jsapi下单地址
    @Getter
    private static final String APIV3_KEY = "apiv3秘钥";
    @Getter
    private static final String API_KEY = "api秘钥,新版本v3接口好像没用";
    @Getter
    private static final String CERT_URL = "https://api.mch.weixin.qq.com/v3/certificates";//微信平台证书地址
}

createOrder为jsapi预下单方法,传入单号、金额、openid
wxAppSign返回结果为微信小程序调起支付的数据

import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;


public class WeiXinPay {
    private static String transaction = "TRANSACTION.SUCCESS";
    private static String description = "充值";
    private static String paySign = "%s\n%s\n%s\n%s\n";
    private static String signType = "RSA";
    private static String charSet = "UTF-8";
    private static String contentType = "application/json";
    private static String algorithm = "SHA256withRSA";

    private static Map<String, X509Certificate> certSet = new HashMap<>();
    private static CloseableHttpClient httpClient;
    private static PrivateKey merchantPrivateKey;
    private static AesUtil aesUtil;


    public WeiXinPay() throws IOException {
        // 加载商户私钥(privateKey:私钥字符串)
        merchantPrivateKey = PemUtil
                .loadPrivateKey(new ByteArrayInputStream(WeiXinProties.getPRIVATE_KEY().getBytes(charSet)));

        // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(WeiXinProties.getMERC_ID(), new PrivateKeySigner(WeiXinProties.getSERIAL_NO(), merchantPrivateKey)), WeiXinProties.getAPIV3_KEY().getBytes(charSet));

        // 初始化httpClient
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(WeiXinProties.getMERC_ID(), WeiXinProties.getSERIAL_NO(), merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier)).build();
        aesUtil = new AesUtil(WeiXinProties.getAPIV3_KEY().getBytes(charSet));
    }

    /**
     * 预支付下单,jsapi
     *
     * @param orderId
     * @param amt
     * @param payerOpenId
     * @return
     * @throws Exception
     */
    private String createOrder(String orderId, String amt, String payerOpenId) throws Exception {
        //请求URL
        HttpPost httpPost = new HttpPost(WeiXinProties.getWEIXINPAY_URL());
        //请求body参数
        JSONObject reqdata = new JSONObject();
        reqdata.put("appid", WeiXinProties.getAPP_ID());
        reqdata.put("mchid", WeiXinProties.getMERC_ID());
        reqdata.put("description", description);
        reqdata.put("out_trade_no", orderId);
        reqdata.put("notify_url", WeiXinProties.getNOTIFY_URL());
        JSONObject amount = new JSONObject();
        //单位为分,传数字不能传字符串,需要对amt进行处理
        amount.put("total", 1000);
        amount.put("currency", "CNY");
        reqdata.put("amount", amount);
        JSONObject payer = new JSONObject();
        payer.put("openid", payerOpenId);
        reqdata.put("payer", payer);
        StringEntity entity = new StringEntity(reqdata.toString());
        entity.setContentType(contentType);
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", contentType);
        return this.httpExecute(httpPost).getString("prepay_id");
    }


    private JSONObject httpExecute(HttpUriRequest uriRequest) throws Exception {
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(uriRequest);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                return JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) {
                throw new RuntimeException("微信支付状态204");
            } else {
                try {
                    throw new RuntimeException(JSONObject.parseObject(EntityUtils.toString(response.getEntity())).getString("message"));
                } catch (Exception e) {
                    throw new RuntimeException(StringUtils.join("微信支付状态", statusCode));
                }
            }
        } finally {
            response.close();
        }
    }

    /**
     * 小程序调起支付数据组织签名
     *
     * @param orderId
     * @param amt
     * @param payerOpenId
     * @return
     */
    public JSONObject wxAppSign(String orderId, String amt, String payerOpenId) {
        try {
            JSONObject data = new JSONObject();
            String orderPkg = this.createOrder(orderId, amt, payerOpenId);
            String appId = WeiXinProties.getAPP_ID();
            String timeSmp = String.valueOf(System.currentTimeMillis());
            String uuid = UUID.randomUUID().toString().replace("-", "");
            String pkgInfo = StringUtils.join("prepay_id=", orderPkg);
            data.put("appId", appId);
            data.put("timeStamp", timeSmp);
            data.put("nonceStr", uuid);
            data.put("package", pkgInfo);
            data.put("signType", signType);
            data.put("paySign", this.sign(String.format(paySign, appId, timeSmp, uuid, pkgInfo), merchantPrivateKey));
            return data;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 私钥签名
     *
     * @param plainText
     * @param privateKey
     * @return
     * @throws Exception
     */
    private String sign(String plainText, PrivateKey privateKey) throws Exception {
        Signature privateSignature = Signature.getInstance(algorithm);
        privateSignature.initSign(privateKey);
        privateSignature.update(plainText.getBytes(charSet));
        byte[] signature = privateSignature.sign();
        return Base64.getEncoder().encodeToString(signature);
    }

    /**
     * 验签
     *
     * @param serialNo
     * @param verStr
     * @param signStr
     * @throws Exception
     */
    public void verify(String serialNo, String verStr, String signStr) throws Exception {
        Certificate cerCert = certSet.get(serialNo);
        if (null == cerCert) {
            this.freshCert();
            cerCert = certSet.get(serialNo);
        }
        PublicKey publicKey = cerCert.getPublicKey();
        byte[] signed = Base64.getDecoder().decode(signStr);
        Signature sign = Signature.getInstance(algorithm);
        sign.initVerify(publicKey);
        sign.update(verStr.getBytes(charSet));
        if (!sign.verify(signed)) {
            throw new RuntimeException("签名验证失败");
        }
    }

    /**
     * 获取微信支付平台证书
     *
     * @throws Exception
     */
    private void freshCert() throws Exception {
        HttpGet httpGet = new HttpGet(WeiXinProties.getCERT_URL());
        httpGet.setHeader("Accept", contentType);
        httpGet.setHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");
        JSONObject json = this.httpExecute(httpGet);
        for (Object o : json.getJSONArray("data")) {
            JSONObject certificate = JSONObject.parseObject(o.toString());
            JSONObject encryptCertificate = certificate.getJSONObject("encrypt_certificate");
            String cert = aesUtil.decryptToString(encryptCertificate.getString("associated_data").getBytes(charSet), encryptCertificate.getString("nonce").getBytes(charSet), encryptCertificate.getString("ciphertext"));
            X509Certificate weixinCert = PemUtil.loadCertificate(new ByteArrayInputStream(cert.getBytes(charSet)));
            certSet.put(certificate.getString("serial_no"), weixinCert);
        }
    }


    public void notify(JSONObject data) throws Exception {
        if (transaction.equals(data.get("event_type"))) {
            JSONObject resource = data.getJSONObject("resource");
            String notifyData = aesUtil.decryptToString(resource.getString("associated_data").getBytes("UTF-8"), resource.getString("nonce").getBytes("UTF-8"), resource.getString("ciphertext"));
            JSONObject notifyJsonData = JSONObject.parseObject(notifyData);
            notifyJsonData.getString("mchid");
            notifyJsonData.getString("appid");
            notifyJsonData.getString("out_trade_no");
            notifyJsonData.getString("trade_state");
            notifyJsonData.getString("success_time");
            notifyJsonData.getJSONObject("payer").getString("openid");
            notifyJsonData.getJSONObject("amount");
        }
    }
}

接收回调信息,验签解密

			String notifySign = "%s\n%s\n%s\n";
            Enumeration<String> headerNames = request.getHeaderNames();
            String body = request.getBodyData();//获取body数据,需要自实现
            JSONObject head = new JSONObject();
            while (headerNames.hasMoreElements()) {
                String key = headerNames.nextElement();
                String value = request.getHeader(key);
                head.put(key, value);
            }
            log.info("微信通知头部" + head);
            log.info("微信通知body" + body);
            weiXinPay.verify(head.getString("wechatpay-serial"), String.format(notifySign, head.getString("wechatpay-timestamp"), head.getString("wechatpay-nonce"), body), head.getString("wechatpay-signature"));
            weiXinPay.notify(JSONObject.parseObject(body));
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值