学习笔记-微信支付

28 篇文章 0 订阅

微信支付

  • 支付方式

    • 付款码支付
    • JSAPI支付
    • 小程序支付
    • Native支付
      • 商户生产二维码,用户扫一扫支付
    • 刷脸支付
  • 接入

    • 获取商户号
      • https://pay.weixin.qq.com/
      • 提交资料 签署协议 获取商户号
    • 获取api密钥
    • 获取apiv3密钥
    • 申请商户api证书
    • 获取微信平台证书

项目搭建

环境准备

  • 依赖
   <!--微信支付SDK-->
    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-apache-httpclient</artifactId>
        <version>0.3.0</version>
    </dependency>
    <!--json处理器-->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>

  • wxpay.properties
# 微信支付相关参数
# 商户号
wxpay.mch-id=1558950191
# 商户API证书序列号
wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F

# 商户私钥文件
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
# APPID
wxpay.appid=wx74862e0dfcf69954
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain=https://500c-219-143-130-12.ngrok.io

# APIv2密钥
wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb

  • WxPayConfig

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

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

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址
    private String notifyDomain;

    // APIv2密钥
    private String partnerKey;

    /**
     * 获取商户的私钥文件
     * @param filename
     * @return
     */
    private PrivateKey getPrivateKey(String filename){

        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

    /**
     * 获取签名验证器
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier(){

        log.info("获取签名验证器");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 使用定时更新的签名验证器,不需要传入证书
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));

        return verifier;
    }


    /**
     * 获取http请求对象
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){

        log.info("获取httpClient");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }

  

下单

  • 统一下单api
    • native下单api
      • /v3/pay/transactions/native
// 首先需要生成订单
log.info("调用统一下单API");
//调用统一下单API
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));

// 请求body参数
Gson gson = new Gson();
Map paramsMap = new HashMap();
paramsMap.put("appid", wxPayConfig.getAppid());
paramsMap.put("mchid", wxPayConfig.getMchId());
// 订单描述
paramsMap.put("description", orderInfo.getTitle());
// 订单号
paramsMap.put("out_trade_no", orderInfo.getOrderNo());
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));

Map amountMap = new HashMap();
amountMap.put("total", orderInfo.getTotalFee());
amountMap.put("currency", "CNY");

paramsMap.put("amount", amountMap);

//将参数转换成json字符串
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}" + jsonParams);

StringEntity entity = new StringEntity(jsonParams,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");

//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpPost);

  • 处理响应
    • 获取下单后支付二维码
String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode == 200) { //处理成功
    log.info("成功, 返回结果 = " + bodyAsString);
} else if (statusCode == 204) { //处理成功,无返回Body
    log.info("成功");
} else {
    log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
    throw new IOException("request failed");
}

//响应结果
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
//二维码
codeUrl = resultMap.get("code_url");

  • 返回
    • 二维码
    • 订单号
//返回二维码
Map<String, Object> map = new HashMap<>();
map.put("codeUrl", codeUrl);
map.put("orderNo", orderInfo.getOrderNo());

支付通知

  • 内网穿透

    • ngrok
  • controller

    • 接收微信通知
 /**
     * 支付通知
     * 微信支付通过支付通知接口将用户支付成功消息通知给商户
     */
    @ApiOperation("支付通知")
    @PostMapping("/native/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response){

        Gson gson = new Gson();
        Map<String, String> map = new HashMap<>();//应答对象

        try {

            //处理通知参数
            String body = HttpUtils.readData(request);
            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
            String requestId = (String)bodyMap.get("id");
            log.info("支付通知的id ===> {}", requestId);
            //log.info("支付通知的完整数据 ===> {}", body);
            //int a = 9 / 0;

            //签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                    = new WechatPay2ValidatorForRequest(verifier, requestId, body);
            if(!wechatPay2ValidatorForRequest.validate(request)){

                log.error("通知验签失败");
                //失败应答
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "通知验签失败");
                return gson.toJson(map);
            }
            log.info("通知验签成功");

            //处理订单,需要解密
            wxPayService.processOrder(bodyMap);

            //应答超时
            //模拟接收微信端的重复通知
            TimeUnit.SECONDS.sleep(5);

            //成功应答
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);

        } catch (Exception e) {
            e.printStackTrace();
            //失败应答
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return gson.toJson(map);
        }

    }

取消订单

  • /v3/pay/transactions/out-trade-no/%s/close
  //创建远程请求对象
        String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url);
        HttpPost httpPost = new HttpPost(url);

        //组装json请求体
        Gson gson = new Gson();
        Map<String, String> paramsMap = new HashMap<>();
        paramsMap.put("mchid", wxPayConfig.getMchId());
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}", jsonParams);

        //将请求参数设置到请求对象中
        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

查询微信支付订单

  • /v3/pay/transactions/out-trade-no/%s
String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());

HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");

//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpGet);

try {
    String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
    int statusCode = response.getStatusLine().getStatusCode();//响应状态码
    if (statusCode == 200) { //处理成功
        log.info("成功, 返回结果 = " + bodyAsString);
    } else if (statusCode == 204) { //处理成功,无返回Body
        log.info("成功");
    } else {
        log.info("查单接口调用,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
        throw new IOException("request failed");
    }

    return bodyAsString;

} finally {
    response.close();
}

退款

  • /v3/refund/domestic/refunds
  //调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);

        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("out_trade_no", orderNo);//订单编号
        paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
        paramsMap.put("reason",reason);//退款原因
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址

        Map amountMap = new HashMap();
        amountMap.put("refund", refundsInfo.getRefund());//退款金额
        amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
        amountMap.put("currency", "CNY");//退款币种
        paramsMap.put("amount", amountMap);

        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");//设置请求报文格式
        httpPost.setEntity(entity);//将请求报文放入请求对象
        httpPost.setHeader("Accept", "application/json");//设置响应报文格式

        //完成签名并执行请求,并完成验签
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try {

            //解析响应结果
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);
            }

        } finally {
            response.close();
        }

查询退款订单

  • /v3/refund/domestic/refunds/%s
String url =  String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
url = wxPayConfig.getDomain().concat(url);

//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");

//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpGet);

try {
    String bodyAsString = EntityUtils.toString(response.getEntity());
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200) {
        log.info("成功, 查询退款返回结果 = " + bodyAsString);
    } else if (statusCode == 204) {
        log.info("成功");
    } else {
        throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);
    }

    return bodyAsString;

} finally {
    response.close();
}

账单查询

  • 账单查询
    • tradebill
      • /v3/bill/tradebill
    • fundflowbill
      • /v3/bill/fundflowbill
        String url = "";
        if("tradebill".equals(type)){
            url =  WxApiType.TRADE_BILLS.getType();
        }else if("fundflowbill".equals(type)){
            url =  WxApiType.FUND_FLOW_BILLS.getType();
        }else{
            throw new RuntimeException("不支持的账单类型");
        }

        url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);

        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Accept", "application/json");

        //使用wxPayClient发送请求得到响应
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《Python学习笔记》是由皮大庆编写的一本关于Python语言学习的教材。在这本书中,作者详细介绍了Python语言的基础知识、语法规则以及常用的编程技巧。 首先,作者简要介绍了Python语言的特点和优势。他提到,Python是一种易于学习和使用的编程语言,受到了广大程序员的喜爱。Python具有简洁、清晰的语法结构,使得代码可读性极高,同时也提供了丰富的库和模块,能够快速实现各种功能。 接着,作者详细讲解了Python的基本语法。他从变量、数据类型、运算符等基础知识开始,逐步介绍了条件语句、循环控制、函数、模块等高级概念。同时,作者通过大量的示例代码和实践案例,帮助读者加深对Python编程的理解和应用。 在书中,作者还特别强调了编写规范和良好的编程习惯。他从命名规范、注释风格、代码缩进等方面指导读者如何写出清晰、可读性强的Python代码。作者认为,良好的编程习惯对于提高代码质量和提高工作效率非常重要。 此外,作者还介绍了Python的常用库和模块。他提到了一些常用的库,如Numpy、Pandas、Matplotlib等。这些库在数据处理、科学计算、可视化等领域有广泛的应用,帮助读者更好地解决实际问题。 总的来说,《Python学习笔记》是一本非常实用和全面的Python学习教材。通过学习这本书,读者可以系统地学习和掌握Python编程的基础知识和高级应用技巧,为以后的编程学习和工作打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值