当前位置:   article > 正文

JAVA接入小程序微信支付_java 小程序支付

java 小程序支付

一、准备所需账号以及配置信息

1、在 微信公众平台 注册应用,并保存好appId和appSecret
2、在微信支付商户平台 注册一个商户,保存好mchId(商户id)、api_key(支付密钥)、以及商户证书序列号。还需要将支付商户密钥文件下载放到项目resources目录中

项目结构

在这里插入图片描述
(结构中包含的其他内容与支付无关)

二、开发环境

1、导入jar包

        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.10</version>
        </dependency>
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.26</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2、在yaml文件中配置参数

wechatpay:
  #应用编号 正式号id
  appId: XXXXXX
  # 小程序密钥
  appSecret: XXXXXX
  #商户号
  mchId: XXXXXX
  # APIv3密钥(在微信支付平台中自己设置)
  apiV3Key: XXXXXX
  
  # 支付成功后微信官方会自动调用我们设定的接口:域名/接口名
  # 支付通知回调, 本地测试内网穿透地址(仅用于做本地测试)
  #  notifyUrl: http://84cd47.natappfree.cc/wechatPay/notify-pay
  # 支付通知回调, 线上域名地址(正常使用服务器配置的域名即可),小程序上线后前后端访问数据时只能用https不能用http,因此还需要为域名配置SSL证书,可根据使用的服务器查看如何配置
  notifyUrl: XXXXXX
  
  # 密钥路径,resources根目录下
  keyPemPath: src/main/resources/wechatPay/apiclient_key.pem
  certPath: src/main/resources/wechatPay/apiclient_cert.pem
  certP12Path: src/main/resources/wechatPay/apiclient_cert.p12
  # 商户证书序列号(可在微信支付平台商户信息中查看)
  serialNo: XXXXXXXXXX
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3、将参数注入到配置类中

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

/**
 * 微信支付配置类
 */
@Component
@Data
@ConfigurationProperties(prefix = "wechatpay")
public class WechatPayConfig {
    /**
     * 小程序appId
     */
    private String appId;
    /**
     * 小程序密钥
     */
    private String appSecret;
    /**
     * 商户号(微信支付平台设置)
     */
    private String mchId;
    /**
     * APIv3密钥(微信支付平台设置)
     */
    private String apiV3Key;
    /**
     * 支付成功回调地址
     */
    private String notifyUrl;
    /**
     * 商户证书序列号
     */
    private String serialNo;
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

4、支付工具类(目前只有支付,没有退款和查询)

import com.alibaba.fastjson2.JSON;
import com.project.config.WechatPayConfig;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Service
public class WxPayUtil {
    private final WechatPayConfig wechatPayConfig;

    @Autowired
    public WxPayUtil(WechatPayConfig wechatPayConfig) {
        this.wechatPayConfig = wechatPayConfig;
    }

    public  <T> T getNotificationParser(HttpServletRequest request, Map<String, Object> body, Class<T> clazz) throws Exception {
        String privateKey = loadKeyByResource("wechatPay/apiclient_key.pem");
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(request.getHeader("Wechatpay-Serial"))
                .nonce(request.getHeader("Wechatpay-Nonce"))
                .signature(request.getHeader("Wechatpay-Signature"))
                .timestamp(request.getHeader("Wechatpay-Timestamp"))
                .signType(request.getHeader("Wechatpay-Signature-Type"))
                .body(JSON.toJSONString(body))
                .build();
        NotificationConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(wechatPayConfig.getMchId())
                .privateKey(privateKey)
                .merchantSerialNumber(wechatPayConfig.getSerialNo())
                .apiV3Key(wechatPayConfig.getApiV3Key())
                .build();

        NotificationParser parser = new NotificationParser(config);
        return parser.parse(requestParam, clazz);
    }

    /**
     * 通过文件路径获取文件内容
     * ClassPathResource可以在jar包中运行,但不能使用其中getFile().getPath()
     * @param path          文件路径
     * @return              文件内容
     * @throws Exception    报错信息
     */
    public static String loadKeyByResource(String path) throws Exception {
        ClassPathResource resource = new ClassPathResource(path);
        byte[] byteArray = FileCopyUtils.copyToByteArray(resource.getInputStream());
        return new String(byteArray, StandardCharsets.UTF_8);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

5、service和impl

//service
public interface WechatPayService {
}

//impl
import com.project.config.WechatPayConfig;
import com.project.service.WechatPayService;
import com.project.utils.WxPayUtil;
import com.project.vo.WechatPayVo;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.refund.RefundService;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

@Service
public class WechatPayServiceImpl implements WechatPayService {
    public static JsapiServiceExtension jsapiServiceExtension;
    public static RefundService refundService;
    private final WechatPayConfig wechatPayConfig;

    public static final String CNY = "CNY";

    public WechatPayServiceImpl(WechatPayConfig wechatPayConfig) throws Exception {
        this.wechatPayConfig = wechatPayConfig;
        this.init();
    }

    @PostConstruct
    public void init() throws Exception {
        String privateKey = WxPayUtil.loadKeyByResource("wechatPay/apiclient_key.pem");
        // 初始化商户配置
        Config config =
                new RSAAutoCertificateConfig.Builder()
                        .merchantId(wechatPayConfig.getMchId())
                        .privateKey(privateKey)
                        .merchantSerialNumber(wechatPayConfig.getSerialNo())
                        .apiV3Key(wechatPayConfig.getApiV3Key())
                        .build();
        // 初始化服务
        jsapiServiceExtension =
                new JsapiServiceExtension.Builder()
                        .config(config)
                        .signType("RSA") // 不填默认为RSA
                        .build();
        refundService = new RefundService.Builder().config(config).build();
    }

    /**
     * JSAPI支付下单,并返回JSAPI调起支付数据
     * <p>
     * //     * @param details    订单描述
     * //     * @param outTradeNo id
     * //     * @param money      金额
     * //     * @param openId     用户openid
     * //     * @param orderBean  购买商品 goods、充值 charge+
     *
     * @return PrepayWithRequestPaymentResponse 支付信息
     */
    public PrepayWithRequestPaymentResponse prepayWithRequestPayment(WechatPayVo wechatPayVo,String outTradeNo) {
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        //支付金额
        amount.setTotal((int) wechatPayVo.getPayMoney());
        amount.setCurrency(CNY);
        // 设置支付成功后的回调
        request.setNotifyUrl(wechatPayConfig.getNotifyUrl());
        request.setAmount(amount);
        //支付项目的名称
        request.setAttach(wechatPayVo.getName());
        request.setAppid(wechatPayConfig.getAppId());
        request.setMchid(wechatPayConfig.getMchId());
        //自定义设置支付成功后的商户单号32位字符
        request.setOutTradeNo(outTradeNo);
        //订单描述
        request.setDescription(wechatPayVo.getDetails());
        Payer payer = new Payer();
        //前端传递的openId
        payer.setOpenid(wechatPayVo.getOpenId());
        request.setPayer(payer);
        // 调用接口
        return jsapiServiceExtension.prepayWithRequestPayment(request);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

6、自定义工具类(用于获取用户支付唯一标识的openId)

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.project.vo.IdCardInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;

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

@Slf4j
public class UtilClass {
    /**
     * 获取openid和session_key
     *
     * @param code      由前端传值
     * @param appId
     * @param appSecret
     * @return
     */
    public static JSONObject getOpenIdAndSessionKey(String code, String appId, String appSecret) {
        JSONObject jsonObject = null;
        try {
            // 请求微信接口获取openid和session_key
            String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId +
                    "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code";

            HttpClient httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(url);
            HttpResponse response = httpClient.execute(httpGet);
            String jsonString = EntityUtils.toString(response.getEntity());

            jsonObject = JSONObject.parseObject(jsonString);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jsonObject;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

前端调起支付传递的对象参数

import lombok.Data;

/**
 * 微信支付vo
 */
@Data
public class WechatPayVo {
    /**
     * 用户id
     */
    private long userId;
    /**
     * 用户唯一标识
     */
    private String openId;
    /**
     * 订单描述
     */
    private String details;
    /**
     * 订单名称
     */
    private String name;
    /**
     * 支付金额
     */
    private double payMoney;
    /**
     * 支付时间(在支付成功的回调接口中获取数据后再赋值)
     */
    private String payTime;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

**注意:微信官方调用自定义的回调接口时会传递一个JSON对象,里面包含基础的支付时间、订单描述、官方的提供的交易订单号、支付成功等信息;但需要通过解密得到,因此还需要一个解密的工具类 AesUtil **

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;

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

/**
 * 解密支付回调工具类
 */
public class AesUtil {
    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;
    private final byte[] aesKey;

    /**
     * 小程序支付密钥
     */
    private final String APIv3Key = "";

    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);
        }
    }

    /**
     * 解密支付回调返回的resource对象
     * 包含支付的所有信息
     */
    public JSONObject decryptResource(JSONObject jsonObject) {
        String json = jsonObject.toString();
        //解密resource对象
        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;
        try {
            decryptData = new AesUtil(this.APIv3Key.getBytes(StandardCharsets.UTF_8))
                    .decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        } catch (GeneralSecurityException | IOException e) {
            throw new RuntimeException(e);
        }
        return JSONObject.parseObject(decryptData, JSONObject.class);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

7、controller层

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.project.utils.UtilClass;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.project.config.WechatPayConfig;
import com.project.service.impl.WechatPayServiceImpl;
import com.project.utils.AesUtil;
import com.project.vo.WechatPayVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.UUID;

@RestController
@Api(tags = "微信支付接口")
@RequestMapping("wechatPay")
public class WechatPayController {
    @Resource
    private WechatPayServiceImpl wechatPayServiceImpl;
    @Resource
    private WechatPayConfig wechatPayConfig;

	//前端首先调用此接口获取到用户唯一标识openId,用于后续调起支付的传参
    @ApiOperation("获取openid和session_key")
    @GetMapping("getOpenIdAndSessionKey")
    public com.alibaba.fastjson.JSONObject getOpenIdAndSessionKey(String code) {
        return UtilClass.getOpenIdAndSessionKey(code, wechatPayConfig.getAppId(), wechatPayConfig.getAppSecret());
    }


    @ApiOperation("调起支付")
    @PostMapping("pay")
    public synchronized PrepayWithRequestPaymentResponse prepayWithRequestPayment(@RequestBody WechatPayVo wechatPayVo) {
        String outTradeNo = UUID.randomUUID().toString().replaceAll("-", "");
        return wechatPayServiceImpl.prepayWithRequestPayment(wechatPayVo, outTradeNo);
    }

	//支付成功后微信会自动调用此接口
    @ApiOperation("支付成功的回调")
    @PostMapping("notify-pay")
    public Object callBackWeiXinPay(@RequestBody JSONObject jsonObject) {
        try {
            String key = wechatPayConfig.getApiV3Key();
            String json = jsonObject.toString();
            //解密 jsonObject 对象
            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 = new AesUtil(key.getBytes(StandardCharsets.UTF_8)).decryptToString
                    (associated_data.getBytes(StandardCharsets.UTF_8),
                            nonce.getBytes(StandardCharsets.UTF_8),
                            ciphertext);
            JSONObject decryptDataObj = JSONObject.parseObject(decryptData, JSONObject.class);
            if ("支付成功".equals(decryptDataObj.get("trade_state_desc"))) {
                //支付成功后的业务操作

            }
        } catch (GeneralSecurityException | IOException e) {
            throw new RuntimeException(e);
        }
        return new Object();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

最后在本地测试时配置的内网穿透域名可通过 NATAPP内网穿透工具 设置,可在NATAPP官网学习使用
简单步骤:
1、注册登录
2、点击购买隧道——>免费隧道
在这里插入图片描述
3、根据教程下载以下文件并做简单配置后双击 natapp.exe在这里插入图片描述在这里插入图片描述
运行完成后如下,此时红圈内则是内网穿透的域名,拼接上我们的支付回调接口后 http://84cd47.natappfree.cc/wechatPay/notify-pay 即可使用
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/467382
推荐阅读
相关标签
  

闽ICP备14008679号