JAVA + JSAPIv3

maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

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

<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.22</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.0</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
</dependency>

建立一个存放微信开放平台和商户信息的类 WechatPayProperties

wx:
  pay:
    appId: ***** # 应用ID
    appSecret: ***** #开发者的AppSecret
    merchantId: *****  #商户id
    merchantSerialNumber: *****  #商户证书序列
    apiV3Key: ***** #V3密钥
    privateKeyPath: G:\\*****\\apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径
    privateCertPath: G:\\*****\\apiclient_cert.pem #apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径
    privateCertificatePath: G:\\*****\\wechatpay_******.pem
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "wx.pay")
@ToString
public class WechatPayProperties {
    /**
     * APPID
     */
    private String appId;

    /*
    * appSecret
    * */
    private String appSecret;
  
    /**
     * 商户号
     */
    private String merchantId;

    /**
     * 商户API证书序列号
     */
    private String merchantSerialNumber;

    /**
     * APIv3密钥
     */
    private String apiV3Key;

    /**
     * apiclient_key.pem证书文件
     */
    private String privateKeyPath;

    /**
     * apiclient_cert.pem证书文件
     */
    private String privateCertPath;

    /*
    * privateCertificatePath
    * */
    private String privateCertificatePath;
}


微信支付通用工具类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.UUID;

public class WxPayUtils {

    private static Logger logger = LoggerFactory.getLogger(WxPayUtils.class);

    /**
     * 生成支付所需的参数
     *
     * @param appId 公众号的APP_ID
     * @param prepay_id 下单接口返回的参数 预支付交易会话标识
     * @param privateKeyPath 私钥地址
     * @return JSONObject
     * @throws Exception e
     */
    public static JSONObject getTokenWeiXin(String appId, String prepay_id, String privateKeyPath) {
        // 获取随机字符串
        String nonceStr = getNonceStr();
        // 获取微信小程序支付package
        String packagestr = "prepay_id=" + prepay_id;
        long timestamp = System.currentTimeMillis() / 1000;
        //签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值
        String message = buildMessageTwo(appId, timestamp, nonceStr, packagestr);
        //获取对应的签名
        String signature = sign(message.getBytes(StandardCharsets.UTF_8), privateKeyPath);
        // 组装返回
        JSONObject json = new JSONObject(new JSONObject());
        json.put("appId", appId);
        json.put("timeStamp", String.valueOf(timestamp));
        json.put("nonceStr", nonceStr);
        json.put("package", packagestr);
        json.put("signType", "RSA");
        json.put("paySign", signature);
        return json;
    }

    /**
     * @return
     * @Author 神传子
     * @MethodName decryptToString
     * @Description //TODO 解密支付回调参数
     * @Date 11:27 2023/5/23
     * @Param aesKey ApiV3Key
     * @Param associatedData 返回参数的【resource.associated_data】
     * @Param nonce 返回参数的【resource.ciphertext】
     * @Param ciphertext 返回参数的【resource.nonce】
     */
    public static String decryptToString(byte[] aesKey, byte[] associatedData, byte[] nonce, String ciphertext)
            throws GeneralSecurityException, IOException {
        final int KEY_LENGTH_BYTE = 32;
        final int TAG_LENGTH_BIT = 128;

        if (aesKey.length != KEY_LENGTH_BYTE){
            logger.info("无效的ApiV3Key,长度必须为32个字节");
            return "";
        }
        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 static JSONObject doGet(String url) {
        //获取DefaultHttpClient请求
        HttpClient client = HttpClientBuilder.create().build();

        HttpGet get = new HttpGet(url);
        JSONObject response = null;
        try {
            HttpResponse res = client.execute(get);
            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                HttpEntity entity = res.getEntity();
                // 返回json格式
                String result = EntityUtils.toString(res.getEntity());
                response = JSON.parseObject(result);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return response;
    }

    //    获取随机字符串
    public static String getNonceStr() {
        return UUID.randomUUID().toString()
                .replaceAll("-", "")
                .substring(0, 32);
    }

    /**
     * 随机字符串 订单号
     *
     * @return
     */
    public static String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    // 签名连接成一串
    private static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) {
        return appId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + packag + "\n";
    }


    /**
     * 生成签名
     *
     * @param message message
     * @return String
     */
    private static String sign(byte[] message, String privateKeyPath) {
        PrivateKey merchantPrivateKey = null;
        X509Certificate wechatPayCertificate = null;
        try {
            merchantPrivateKey = PemUtil.loadPrivateKey(
                    new FileInputStream(privateKeyPath));
            /*wechatPayCertificate = PemUtil.loadCertificate(
                    new FileInputStream(wechatPayProperties.getPrivateCertificatePath()));*/

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        Signature sign = null;
        try {
            sign = Signature.getInstance("SHA256withRSA");
            //这里需要一个PrivateKey类型的参数,就是商户的私钥。
            sign.initSign(merchantPrivateKey);
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
}

SslUtils 忽略安全证书请求

import javax.net.ssl.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class SslUtils {
    private static void trustAllHttpsCertificates() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[1];
        TrustManager tm = new miTM();
        trustAllCerts[0] = tm;
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }

    static class miTM implements TrustManager, X509TrustManager {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(X509Certificate[] certs) {
            return true;
        }

        @Override
        public void checkServerTrusted(X509Certificate[] certs, String authType)
                throws CertificateException {
            return;
        }

        @Override
        public void checkClientTrusted(X509Certificate[] certs, String authType)
                throws CertificateException {
            return;
        }
    }

    /**
     * 忽略HTTPS请求的SSL证书,必须在openConnection之前调用
     *
     * @throws Exception
     */
    public static void ignoreSsl() throws Exception {
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String urlHostName, SSLSession session) {
                System.out.println("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost());
                return true;
            }
        };
        trustAllHttpsCertificates();
        HttpsURLConnection.setDefaultHostnameVerifier(hv);
    }
}


前置引入

@Resource
CloseableHttpClient httpClient;

@Resource
WechatPayProperties wechatPayProperties;

获取 openId

public JSONObject getOpenId(JSONObject jsonObject) {
    log.info("========== getAppId 传入参数 ==========");
    log.info(jsonObject);
    log.info("======================================");
    
    String url = "https://api.weixin.qq.com/sns/oauth2/access_token";
    //前端回调拿到的 code
    String code = jsonObject.getString("code");

    final String APP_ID = wechatPayProperties.getAppId();
    final String SECRET = wechatPayProperties.getAppSecret();

    //获取openId
    url += "?appid=" + APP_ID;
    url += "&secret=" + SECRET;
    url += "&grant_type=" + "authorization_code";
    url += "&code=" + code;
    log.info("【获取openid的路径】" + url);
    JSONObject openIdInfo = WxPayUtils.doGet(url);
    log.info("【调用获取openid的主体信息】" + openIdInfo);

    if (openIdInfo.toJSONString().indexOf("openid") != -1) {
        //openIdInfo会包含openId
        return appletApiExecSuccess(openIdInfo, "获取openId成功");
    } else {
        return appletApiExecErr("获取openid失败");
    }
}


发起支付,返回的是前端发起支付的参数

/**
 * @return com.alibaba.fastjson.JSONObject
 * @Author 神传子
 * @MethodName payment
 * @Description //TODO 
 * @Date 9:39 2023/3/31
 * @Param [jsonObject]
 */
public JSONObject payment(JSONObject inObj) {
    log.info("========== getAppId 传入参数 ==========");
    log.info(inObj);
    log.info("======================================");

    // 构建POST请求
    HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
    httpPost.addHeader("Accept", "application/json");
    httpPost.addHeader("Content-type", "application/json; charset=utf-8");
    // 使用JSON库,构建请求参数对象
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectMapper objectMapper = new ObjectMapper();
    ObjectNode rootNode = objectMapper.createObjectNode();
    JSONObject attachObj = new JSONObject();

    rootNode
            // 直连商户号
            .put("mchid", wechatPayProperties.getMerchantId())
            // 应用ID
            .put("appid", wechatPayProperties.getAppId())
            // 商品描述
            .put("description", "测试支付")
            // 商户订单号
            .put("out_trade_no", WxPayUtils.generateNonceStr())
            // 通知地址
            .put("notify_url", "http://******/zzapi/his/wx/notify/success")
            .put("attach", attachObj.toJSONString());

    // 订单金额对象
    rootNode.putObject("amount")
            // 总金额,单位为分
            .put("total", 1);

    //支付者
    rootNode.putObject("payer")
            //用户标识
            .put("openid", inObj.getString("openId"));

    try {
        SslUtils.ignoreSsl();
    } catch (Exception e) {
        e.printStackTrace();
    }

    try {
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        // 执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        // 获取响应
        JSONObject prepay_id_obj = JSON.parseObject(EntityUtils.toString(response.getEntity()));
        System.out.println("【下单返回参数】" + prepay_id_obj);
        if (prepay_id_obj.get("prepay_id") == null) {
            return appletApiExecErr(prepay_id_obj.getString("message"));
        }
        //返回签名
        String prepay_id = prepay_id_obj.getString("prepay_id");

        JSONObject tokenWeiXin = WxPayUtils.getTokenWeiXin(wechatPayProperties.getAppId(), prepay_id, wechatPayProperties.getPrivateKeyPath());
        return appletApiExecSuccess(tokenWeiXin, "获取支付信息成功");
    } catch (IOException e) {
        e.printStackTrace();
        return appletApiExecErr("获取支付信息失败");
    }
}

微信支付完成回调

public JSONObject notifySuccess(JSONObject inObj) {

    log.info("========== 微信支付回调参数 ==========");
    log.info(inObj);
    log.info("======================================");

    String method = Thread.currentThread().getStackTrace()[1].getMethodName();

    try {
        String key = wechatPayProperties.getApiV3Key();
        String json = inObj.toString();
        String associated_data = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.associated_data");
        String ciphertext = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.ciphertext");
        String nonce = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.nonce");

        String decryptData = WxPayUtils.decryptToString(
                key.getBytes(StandardCharsets.UTF_8),
                associated_data.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);

        //验签成功
        JSONObject decryptDataObj = JSONObject.parseObject(decryptData, JSONObject.class);
        System.out.println("【decryptDataObj】" + decryptDataObj);

        //返回自定义参数
        String attach = decryptDataObj.getString("attach");
        JSONObject attachObj = JSONObject.parseObject(attach);
        System.out.println(attachObj);

        //decryptDataObj 为解码后的obj,其内容如下。之后便是验签成功后的业务处理
    } catch (Exception e) {
        log.info("{} ,parms{}, 异常:", method, inObj.toJSONString(), e);
    }

    JSONObject outObj = new JSONObject();
    outObj.put("code", "SUCCESS");
    outObj.put("message", "成功");
    return outObj;
}


退费

public JSONObject refund(JSONObject inObj) {
    String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
    // 构建POST请求
    HttpPost httpPost = new HttpPost(url);
    httpPost.addHeader("Accept", "application/json");
    httpPost.addHeader("Content-type", "application/json; charset=utf-8");
    // 使用JSON库,构建请求参数对象
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectMapper objectMapper = new ObjectMapper();
    ObjectNode rootNode = objectMapper.createObjectNode();


    rootNode
            // 商户订单号
            .put("transaction_id", "****************")
            // 商户退款单号
            .put("out_refund_no", "123457");

    // 订单金额对象
    rootNode.putObject("amount")
            // 退款金额 单位:分
            .put("refund", 1)
            // 原订单金额 单位:分
            .put("total", 1)
            // 退款币种
            .put("currency", "CNY");

    try {
        SslUtils.ignoreSsl();
    } catch (Exception e) {
        e.printStackTrace();
    }
    try {
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        // 执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        // 获取响应
        JSONObject refundObj = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
        System.out.println(refundObj);
        if (refundObj.get("transaction_id") != null) {
            return appletApiExecSuccess(refundObj, "发起退款成功");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return appletApiExecErr("发起退款失败");
}

uniapp前端实现:uniapp + JSAPIv3

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值