微信小程序支付(4)支付后端代码

本文介绍了如何使用Jeecg Boot模块中的MpPaymentController来处理小程序微信支付的相关操作,包括统一下单、查询订单、关闭订单及申请交易账单,展示了如何配置API密钥和调用微信支付API接口。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

jeecg-boot-master\jeecg-boot\jeecg-boot-module-mp\src\main\java\org\jeecg\modules\mp\cost\controller\MpPaymentController.java

package org.jeecg.modules.mp.cost.controller;

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 io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.http.client.HttpClient;
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.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.aspectj.lang.annotation.Before;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.config.GlobalKeys;
import org.jeecg.modules.mp.util.WxPayUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

/**
 * <p>
 * 小程序微信支付
 * </p>
 *
 * @Author DaQiang
 * @since 2021-3-15
 */
@Api(tags = "小程序微信支付")
@Slf4j
@RestController
@RequestMapping("/cost/payment")
public class MpPaymentController {

    /**
     * 系统业务参数配置类
     */
    @Autowired
    private GlobalKeys globalKeys;

    /**
     * AppID(小程序ID)
     */
    String appId;

    /**
     * 商户号
     */
    String mchId;

    /**
     * 商户证书序列号
     */
    String mchSerialNo;

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

    /**
     * 获取发起请求时的系统当前时间戳,即格林威治时间1970年01月01日00时00分00秒
     * (北京时间1970年01月01日08时00分00秒)起至现在的总秒数,作为请求时间戳。
     * 微信支付会拒绝处理很久之前发起的请求,请商户保持自身系统的时间准确。
     */
    long timeStamp;

    /**
     * 请求随机串
     */
    String nonceStr;

    /**
     * 商户私钥
     */
    PrivateKey merchantPrivateKey;

    /**
     * 平台证书
     */
    AutoUpdateCertificatesVerifier verifier;

    /**
     * HttpClient
     */
    HttpClient httpClient;

    /**
     * HttpUrl
     */
    HttpUrl httpurl;

    /**
     * 构造器
     *
     * @throws IOException
     */
    public MpPaymentController(GlobalKeys globalKeys) throws IOException {
        // 系统业务参数配置类
        this.globalKeys = globalKeys;

        // AppID(小程序ID)
        this.appId = this.globalKeys.getAppId();

        // 商户号
        this.mchId = this.globalKeys.getMchId();

        // 商户证书序列号
        this.mchSerialNo = this.globalKeys.getMchSerialNo();

        // APIv3密钥
        this.apiV3Key = this.globalKeys.getApiV3Key();

        // 系统当前时间戳
        this.timeStamp = System.currentTimeMillis() / 1000;

        // 请求随机串
        this.nonceStr = WxPayUtil.createNonceStr();

        // 初始化HttpUrl
        this.httpurl = HttpUrl.parse("https://api.mch.weixin.qq.com/v3/certificates");

        // 创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
        this.setup();
    }

    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    /**
     * 创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
     *
     * @throws IOException
     */
    @Before("")
    public void setup() throws IOException {

        // 加载商户私钥(privateKey:私钥字符串)
        // merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));

        // 加载商户私钥
        merchantPrivateKey = getPrivateKey("apiclient_key.pem");

        // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
        verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));

        // 初始化httpClient
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier)).build();

    }

    /**
     * 小程序支付统一下单
     *
     * @throws Exception
     */
    @AutoLog(value = "小程序支付统一下单")
    @ApiOperation(value = "小程序支付统一下单", notes = "小程序支付统一下单")
    @PostMapping(value = "/createOrder")
    public Result<JSONObject> createOrder(@RequestBody Map paramMap) throws IOException {
        Result<JSONObject> result = new Result<JSONObject>();
        JSONObject obj = new JSONObject();
        Map<String, Object> map = new HashMap<>();

        // 请求URL
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");

        // total:订单总金额,单位为分
        Integer totalFee = (Integer) paramMap.get("totalFee");

        // description:商品描述
        String description = (String) paramMap.get("description");

        // notify_url:支付回调通知URL,该地址必须为直接可访问的URL,不允许携带查询串。
        String notifyUrl = (String) paramMap.get("notifyUrl");

        // 用户唯一标识OpenID
        // openid是微信用户在appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户。
        String openid = (String) paramMap.get("openid");

        // out_trade_no:商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。
        String outTradeNo = WxPayUtil.createOutTradeNo();

        // 请求body参数
        String reqdata = "{"
                + "\"amount\": {"
                + "\"total\": " + totalFee + ","
                + "\"currency\": \"CNY\""
                + "},"
                + "\"mchid\": \"" + mchId + "\","
                + "\"description\": \"" + description + "\","
                + "\"notify_url\": \"" + notifyUrl + "\","
                + "\"payer\": {"
                + "\"openid\": \"" + openid + "\"" + "},"
                + "\"out_trade_no\": \"" + outTradeNo + "\","
                + "\"goods_tag\": \"用水户水费\","
                + "\"appid\": \"" + appId + "\"" + "}";

        StringEntity entity = new StringEntity(reqdata);
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));

                // 预支付交易会话标识(prepay_id)
                // 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
                String prepayIdEntity = EntityUtils.toString(response.getEntity());
                JSONObject jsonResult = JSONObject.parseObject(prepayIdEntity);
                String prepayId = (String) jsonResult.get("prepay_id");

                // 签名生成
                obj = getToken("GET", httpurl, "prepay_id=" + prepayId);

            } else if (statusCode == 204) {
                System.out.println("success");
            } else {
                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } finally {
            response.close();
        }

        result.setResult(obj);
        result.setSuccess(true);
        result.setCode(200);
        return result;
    }

    /**
     * 签名生成
     *
     * @param method
     * @param url
     * @param body
     * @return
     */
    JSONObject getToken(String method, HttpUrl url, String body) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        // 返回JSON对象
        JSONObject obj = new JSONObject();

        // AppID(小程序ID)
        obj.put("appId", appId);

        // 商户号
        obj.put("mchId", mchId);

        // 该接口V3版本仅支持RSA
        obj.put("signType", "RSA");

        // 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=***。
        obj.put("package", body);

        // 发起请求时的系统当前时间戳
        obj.put("timeStamp", String.valueOf(timeStamp));

        // 请求随机串
        obj.put("nonceStr", nonceStr);

        // 构造签名串
        String message = buildMessage(method, url, body);

        // 签名
        String signature = sign(message.getBytes("utf-8"));
        /**signature = "mchid=\"" + mchId + "\","
         + "nonce_str=\"" + nonceStr + "\","
         + "timestamp=\"" + timestamp + "\","
         + "serial_no=\"" + mchSerialNo + "\","
         + "signature=\"" + signature + "\"";*/
        obj.put("paySign", signature);
        return obj;
    }

    /**
     * 签名
     *
     * @param message
     * @return
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws NoSuchAlgorithmException
     */
    String sign(byte[] message) throws UnsupportedEncodingException, InvalidKeyException, SignatureException, NoSuchAlgorithmException {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(merchantPrivateKey);
        sign.update(message);

        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     * 构造签名串
     *
     * @param method
     * @param url
     * @param body
     * @return
     */
    String buildMessage(String method, HttpUrl url, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        // method + "\n" + canonicalUrl + "\n" +
        return appId + "\n"
                + timeStamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * 查询订单
     *
     * @throws Exception
     */
    @AutoLog(value = "查询订单")
    @ApiOperation(value = "查询订单", notes = "查询订单")
    @GetMapping(value = "/queryOrder")
    public void QueryOrder() throws Exception {

        // 请求URL
        URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/id/4200000745202011093730578574");
        uriBuilder.setParameter("mchid", mchId);

        // 完成签名并执行请求
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet);

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) {
                System.out.println("success");
            } else {
                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } finally {
            response.close();
        }
    }

    /**
     * 关闭订单
     *
     * @throws Exception
     */
    @AutoLog(value = "关闭订单")
    @ApiOperation(value = "关闭订单", notes = "关闭订单")
    @GetMapping(value = "/closeOrder")
    public void CloseOrder() throws Exception {

        //请求URL
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/sdkphp12345678920201028112429/close");
        //请求body参数
        String reqdata = "{\"mchid\": \"" + mchId + "\"}";

        StringEntity entity = new StringEntity(reqdata);
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) {
                System.out.println("success");
            } else {
                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } finally {
            response.close();
        }
    }

    /**
     * 申请交易账单
     *
     * @throws Exception
     */
    @AutoLog(value = "申请交易账单")
    @ApiOperation(value = "申请交易账单", notes = "申请交易账单")
    @GetMapping(value = "/tradeBill")
    public void TradeBill() throws Exception {

        //请求URL
        URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill");
        uriBuilder.setParameter("bill_date", "2020-11-09");
        uriBuilder.setParameter("bill_type", "ALL");

        //完成签名并执行请求
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet);

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) {
                System.out.println("success");
            } else {
                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } finally {
            response.close();
        }
    }

    /**
     * 下载账单
     *
     * @param download_url
     * @throws Exception
     */
    @AutoLog(value = "下载账单")
    @ApiOperation(value = "下载账单", notes = "下载账单")
    @GetMapping(value = "/downloadUrl")
    public void DownloadUrl(String download_url) throws Exception {

        PrivateKey privateKey = getPrivateKey("privateKey");

        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.toString().getBytes("utf-8")));

        //初始化httpClient
        //该接口无需进行签名验证、通过withValidator((response) -> true)实现
        httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator((response) -> true).build();

        //请求URL
        //账单文件的下载地址的有效时间为30s
        URIBuilder uriBuilder = new URIBuilder(download_url);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");

        //执行请求
        CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) {
                System.out.println("success");
            } else {
                System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } finally {
            response.close();
        }
    }

}

jeecg-boot-master\jeecg-boot\jeecg-boot-module-mp\src\main\java\org\jeecg\modules\mp\util\WxPayUtil.java

package org.jeecg.modules.mp.util;

import org.apache.commons.lang3.time.FastDateFormat;

import java.sql.Timestamp;
import java.util.Date;
import java.util.Random;
import java.util.UUID;

/**
 * <p>
 * 微信小程序支付工具类
 * </p>
 *
 * @Author DaQiang
 * @since 2021-3-15
 */
public class WxPayUtil {

    /**
     * 生成商户系统内部订单号
     *
     * @return
     */
    public static String createOutTradeNo() {
        return FastDateFormat.getInstance("yyyyMMddHHmmssSSS").format(new Date()) + UUID.randomUUID().toString().hashCode();
    }

    /**
     * 生成时间戳
     *
     * @return
     */
    public static String createTimestamp() {
        return new Timestamp(System.currentTimeMillis()).toString();
    }

    /**
     * 生成随机数
     *
     * @return
     */
    public static String createNonceStr() {
        // return UUID.randomUUID().toString();
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        String res = "";
        for (int i = 0; i < 32; i++) {
            Random rd = new Random();
            res += chars.charAt(rd.nextInt(chars.length() - 1));
        }
        return res;
    }

}

jeecg-boot-master\jeecg-boot\jeecg-boot-base-common\src\main\java\org\jeecg\config\GlobalKeys.java

package org.jeecg.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * @Description: 系统业务参数配置类
 * @author: DaQiang
 * @date: 2020年09月29日 11:11
 */
@Component
@ConfigurationProperties(prefix = "sys")
@PropertySource("classpath:systemParam.properties")
@Data
public class GlobalKeys {
    private String rootAdcd;
    private String rootAdnm;
    private String rootChancd;
    private String rootChannm;
    private String rootDeCd;
    private String rootDeNm;
    private String rootCd;
    private String rootNm;

    // 微信小程序
    // AppID(小程序ID)
    private String appId;
    // AppSecret(小程序密钥)
    private String secret;

    // 微信支付
    // 商户号
    private String mchId;
    // 商户证书序列号
    private String mchSerialNo;
    // API密钥
    private String apiKey;
    // APIv3密钥
    private String apiV3Key;
}

jeecg-boot-master\jeecg-boot\jeecg-boot-base-common\src\main\resources\systemParam.properties

sys.rootAdcd=1408
sys.rootAdnm=**市

sys.rootChancd=00
sys.rootChannm=****服务中心

sys.rootDeCd=0003
sys.rootDeNm=财务科

sys.rootCd=00
sys.rootNm=****灌区

# 微信小程序
# AppID(小程序ID)
sys.appId=*************替换为自己的**************
# AppSecret(小程序密钥)
sys.secret=*************替换为自己的**************

# 微信支付
# 商户号
sys.mchId=*************替换为自己的**************
# 商户证书序列号
sys.mchSerialNo=*************替换为自己的**************
# API密钥
sys.apiKey=*************替换为自己的**************
# APIv3密钥
sys.apiV3Key=*************替换为自己的**************

jeecg-boot-master\jeecg-boot\apiclient_key.pem

-----BEGIN PRIVATE KEY-----
***************************************
***************************************
***************************************
*************替换为自己的**************
***************************************
***************************************
***************************************
-----END PRIVATE KEY-----

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值