首页 > 其他分享 >微信支付

微信支付

时间:2022-12-06 16:58:44浏览次数:39  
标签:info log 微信 支付 new import com String

加密

加密分为对称性和非对称性

对称性的意思就是加密和解密的密钥是同一个

加密分组模式:将明文分组加密

非对称加密是用公钥进行加密(可公开),使用私钥进行解密

使用公钥进行加密后只能使用私钥进行解密,反过来,私钥加密后也只能用公钥进行解密

RSA加密算法:最著名的非对称加密

两者的对比

对称加密:

​ 优点:运算速度块

​ 缺点:密钥需要信息交换的双方共享,一旦被窃取,消息会被破解

非对称加密

​ 优点:私钥严格保密,公钥任意分发,黑客获取公钥无法破解密文

​ 缺点:运算速度非常慢

开发中:我们常常使用非对称传递密钥,使用对成进行加密

身份认证

使用公钥加密,私钥解密是为了安全性

使用私钥加密,公钥解密是为了做身份认证

摘要算法和数据的完整性

Z->H

任意长度的经过加密后得到固定长度的密文(摘要)

1:不可逆:只有算法,没有密钥,不能解密

2.难题友好性:想要破解,只能暴力枚举

3.发散性:只要对原文进行一点点改动,摘要就会发生剧烈变化

4.抗碰撞性:原文不同,计算后的摘要也要不同

常见摘要算法:MD5、SHA1、SHA2

解决黑客获取后对原文进行修改,并生成新的摘要:

解决办法:现将原文加密成摘要,然后发送方使用私钥将摘要加密(加密后的结果,我们称之为签名),接收方取下数字签名,用公钥进行解密,得到摘要,接受方使用一样的加密算法对原文进行加密,对比两者是否一致,如果一致,证明没有被篡改

数字证书

案例:黑客将自己的公钥给你,告诉你这是对方的公钥,然后黑客使用自己的私钥给你发送,你用黑客给你的公钥可以进行解密,但是你以为自己一直是在和对方进行沟通

问题:公钥的信任,黑客可以伪造公钥,怎么判断公钥是真实的?

数字证书:

image-20221201164844837

image-20221201165858954

image-20221201170037017

因为你拿到对方的文件后对对数字签名进行解析,里面有CA给对方的认证,黑客无法篡改,也无法伪造

https协议 数字证书

image-20221201170523538

创建项目时学习到的新东西

使用mypatis-plu时,我们会将xml文件放在mapper下

image-20221202091802269

为了打包的时候将xml一起打包

需要配置pom

image-20221202091853099

微信支付APIv3搭建过程

server:
  port: 8090 #端口

spring:
  application:
    name: payment-demo #项目名称
  #设置时间格式
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/payment_demo?serverTimezone=GMT%2B8&useSSL=true&characterEncoding=utf-8
    username: root
    password: 123456
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #日志打印
  mapper-locations: classpath:com/yhd.paymentdemo/mapper/xml/*.xml  #配置文件

当我们准备好所有资料后

创建一个文件写入需要的资料

# 微信支付相关参数
# 商户号
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://77ea-221-239-177-21.ngrok.io

# APIv2密钥
wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb

创建一个配置类用来接受需要的参数

package com.yhd.paymentdemo.config;


import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@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;


}

测试配置文件是否被读取

package com.yhd.paymentdemo.controller;

import com.yhd.paymentdemo.config.WxPayConfig;
import com.yhd.paymentdemo.vo.R;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 *
 **/
@Api(tags = "测试控制器")
@RestController
@RequestMapping("/api/test")
public class TestController {
    @Resource
    private WxPayConfig wxPayConfig;

    @GetMapping("")
    public R getWxPayConfig(){
        String mchId = wxPayConfig.getMchId();
        return R.ok().data("mchId",mchId);
    }
}

小技巧-将自定义配置文件变为应用文件

image-20221202102003751

image-20221202102027063

image-20221202102102376

image-20221202102136179

配置完后,发现变成小叶子,而且文件中的字也变了颜色

image-20221202102216243

但是发现还是不能点击跳转

所以我们需要添加依赖

<!--        生成自定义配置的元数据信息-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

更新后发现点击会跳转到java类的属性上,方便我们的开发

开始开发微信支付

加载商户私钥

我们首先将私钥文件复制到项目中

image-20221202103320491

去微信支付官网查看api,需要引入微信支付的SDK

<!--        微信支付SDK-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.3.0</version>
        </dependency>

微信配置类中创建该方法

**
  * 获取商户私钥文件
  * @param fileName
  * @return
  */
public PrivateKey getPrivateKey(String fileName){
    try {
        return PemUtil.loadPrivateKey(new FileInputStream(fileName));
    } catch (FileNotFoundException e) {
        throw new RuntimeException("私钥文件不存在",e);
    }
}

测试

package com.yhd.paymentdemo.config;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

import java.security.PrivateKey;

import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest //果果测试中用到了Bean需要加该注解
class WxPayConfigTest {
    @Resource
    private WxPayConfig wxPayConfig;
    @Test
    void getPrivateKey() {
        String privateKeyPath = wxPayConfig.getPrivateKeyPath();
        PrivateKey privateKey = wxPayConfig.getPrivateKey(privateKeyPath);
        System.out.println(privateKey);
    }
}

获取验签器和HttpClient

package com.yhd.paymentdemo.config;


import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
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 lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;

@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请求对象
     * @return
     */
    @Bean
  public CloseableHttpClient getWxPayClient(){
      //获取商户私钥
      PrivateKey privateKey = getPrivateKey(privateKeyPath);
      //使用定时更新的签名认证器,不需要传入证书
      WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
              .withMerchant(mchId, mchSerialNo, privateKey)
              .withValidator(new WechatPay2Validator(getVerifier()));
      // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

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


}

因为使用json进行交流

所以引进gson

<!--        json处理器-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

简单的下单业务(未存数据库)

package com.yhd.paymentdemo.service.impl;

import com.google.gson.Gson;
import com.yhd.paymentdemo.config.WxPayConfig;
import com.yhd.paymentdemo.entity.OrderInfo;
import com.yhd.paymentdemo.enums.OrderStatus;
import com.yhd.paymentdemo.enums.wxpay.WxApiType;
import com.yhd.paymentdemo.enums.wxpay.WxNotifyType;
import com.yhd.paymentdemo.service.WxPayService;
import com.yhd.paymentdemo.util.OrderNoUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 *
 **/
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService{
    @Resource
    private WxPayConfig wxPayConfig;

    @Resource
    private CloseableHttpClient wxPayClient;

    /**
     * 创建订单,调用native接口
     * @param productId
     * @return code_url 和 订单号
     */
    @Override
    public Map<String, Object> nativePay(Long productId) throws Exception{
        log.info("生成订单");
        //生成订单
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setTitle("test");
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//订单号
        orderInfo.setProductId(productId);
        orderInfo.setTotalFee(1);//单位是分
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
        //TODO 存入数据库
        //调用统一下单api
        log.info("调用统一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 jsonMap = gson.toJson(paramsMap);
        log.info("请求参数:"+paramsMap);
        StringEntity entity = new StringEntity(jsonMap,"utf-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");

            //完成签名并执行请求
            CloseableHttpResponse response = wxPayClient.execute(httpPost);
            HashMap<String, Object> map = new HashMap<>();

            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+ ", 返回结果 = " + EntityUtils.toString(response.getEntity()));
                    throw new IOException("request failed");
                }
                //响应结果
                HashMap<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                //二维码
                String code_url = resultMap.get("code_url");

                map.put("codeUrl",code_url);
                map.put("orderNo",orderInfo.getOrderNo());
                return map;
            } finally {
                response.close();
            }


    }
}

下单的控制层

package com.yhd.paymentdemo.controller;

import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.yhd.paymentdemo.service.WxPayService;
import com.yhd.paymentdemo.vo.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付APIv3")
@Slf4j
public class WxPayController {

    @Resource
    private WxPayService wxPayService;

    @Resource
    private Verifier verifier;

    /**
     * Native下单
     * @param productId
     * @return
     * @throws Exception
     */
    @ApiOperation("调用统一下单API,生成支付二维码")
    @PostMapping("/native/{productId}")
    public R nativePay(@PathVariable Long productId) throws Exception {

        log.info("发起支付请求 v3");

        //返回支付二维码连接和订单号
        Map<String, Object> map = wxPayService.nativePay(productId);

        return R.ok().setData(map);
    }


}
  

微信如和生成签名串

image-20221203143855055

生成订单模块

之前我们是写死了数据,现在我们需要获取数据并且存单数据库中

package com.yhd.paymentdemo.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yhd.paymentdemo.entity.OrderInfo;
import com.yhd.paymentdemo.entity.Product;
import com.yhd.paymentdemo.enums.OrderStatus;
import com.yhd.paymentdemo.mapper.OrderInfoMapper;
import com.yhd.paymentdemo.mapper.ProductMapper;
import com.yhd.paymentdemo.service.OrderInfoService;
import com.yhd.paymentdemo.util.OrderNoUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {
    @Resource
    private ProductMapper productMapper;

    /**
     * 创建订单
     * @param productId
     * @return
     */
    @Override
    public OrderInfo createOrderByProductId(Long productId) {
        //查找已经存在但是未支付的订单;
       OrderInfo orderInfo= this.getNoPayOrderByProductId(productId);
       if(orderInfo!=null){
           return orderInfo;
       }
        //获取商品信息
        Product product=productMapper.selectById(productId);
        //生成订单
        orderInfo=new OrderInfo();//出现错误,没创建对象直接用null赋值
        orderInfo.setTitle(product.getTitle());
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//订单号
        orderInfo.setProductId(productId);
        orderInfo.setTotalFee(product.getPrice());//单位是分
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());

        //存入到数据库
        baseMapper.insert(orderInfo);
        return orderInfo;
    }

    /**
     * 存储订单二维码
     * @param orderNo
     * @param codeUrl
     */
    @Override
    public void saveCodeUrl(String orderNo, String codeUrl) {

        QueryWrapper<OrderInfo> queryWapper = new QueryWrapper<>();
        queryWapper.eq("order_no",orderNo);
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setCodeUrl(codeUrl);
        baseMapper.update(orderInfo,queryWapper);
    }

    /**
     * 获取已经存在但是未支付的订单
     * @param productId
     * @return
     */
    private OrderInfo getNoPayOrderByProductId(Long productId) {

        QueryWrapper<OrderInfo> orderInfoQueryWrapper = new QueryWrapper<>();
        orderInfoQueryWrapper.eq("product_id",productId)
                .eq("order_status",OrderStatus.NOTPAY.getType());
        OrderInfo orderInfo = baseMapper.selectOne(orderInfoQueryWrapper);
        return orderInfo;
    }

}

查询订单模块

/**
 * 查询订单列表,并倒序查询
 * @return
 */
@Override
public List<OrderInfo> listOrderByCreateTimeDesc() {
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.orderByDesc("create_time");
    return baseMapper.selectList(queryWrapper);
}
/**
 * 订单控制器
 **/
@RestController
@Slf4j
@Api(tags = "商品订单管理")
@RequestMapping("/api/order-info")
public class OrderInfoController {
    @Resource
    private OrderInfoService orderInfoService;
    @GetMapping("/list")
    public R list(){
        List<OrderInfo> list=orderInfoService.listOrderByCreateTimeDesc();
        return R.ok().data("list",list);
    }
}

内网穿透

使用工具进行内网穿透,达到外网访问本地网络

使用工具进行内网穿透后

需要修改:本次使用工具natapp 启动指令:natapp -authtoken=上面查看到的 authtoken

image-20221206162452186

支付通知(微信平台发送)

@PostMapping("/native/notify")
public String nativeNotify(HttpServletRequest request,HttpServletResponse response){
    Gson gson = new Gson();
    Map<String,String> map=new HashMap<>();
    //处理通知参数
    String body = HttpUtils.readData(request);
    Map<String,Object> bodMap = gson.fromJson(body, HashMap.class);
    log.info("支付通知的id==>{}",bodMap.get("id"));
    log.info("支付通知的完整数据==>{}",body);

    //TODO :签名的验签
    //TODO :处理订单

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

image-20221205115901177

在之前发送给微信支付借口了包含了通知返回的地址

支付通知-验签(验签微信请求的内容)

将响应验签的类进行修改

package com.yhd.paymentdemo.util;


import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;

/**
 * @author xy-peng
 */
public class WechatPay2ValidatorForRequest {

    protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
    /**
     * 应答超时时间,单位为分钟
     */
    protected static final long RESPONSE_EXPIRED_MINUTES = 5;
    protected final Verifier verifier;
    protected final String requestId;
    protected final String body;


    public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
        this.verifier = verifier;
        this.requestId = requestId;
        this.body = body;
    }

    protected static IllegalArgumentException parameterError(String message, Object... args) {
        message = String.format(message, args);
        return new IllegalArgumentException("parameter error: " + message);
    }

    protected static IllegalArgumentException verifyFail(String message, Object... args) {
        message = String.format(message, args);
        return new IllegalArgumentException("signature verify fail: " + message);
    }

    public final boolean validate(HttpServletRequest request) throws IOException {
        try {
            //处理请求参数
            validateParameters(request);

            //构造验签名串
            String message = buildMessage(request);
            //平台证书签名串
            String serial = request.getHeader(WECHAT_PAY_SERIAL);
            //平台证书序列号
            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);

            //验签
            if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
                throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
                        serial, message, signature, requestId);
            }
        } catch (IllegalArgumentException e) {
            log.warn(e.getMessage());
            return false;
        }

        return true;
    }

    protected final void validateParameters(HttpServletRequest request) {

        // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
        String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};

        String header = null;
        for (String headerName : headers) {
            header = request.getHeader(headerName);
            if (header == null) {
                throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
            }
        }

        //判断请求是否过期
        String timestampStr = header;
        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
            // 拒绝过期请求
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
                throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
        }
    }

    protected final String buildMessage(HttpServletRequest request) throws IOException {
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        return timestamp + "\n"
                + nonce + "\n"
                + body + "\n";
    }

    protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
    }

}

image-20221205140833114

支付通知-报文解密

@Override
public void processOrder(Map<String, Object> bodMap) throws GeneralSecurityException {
    log.info("处理订单");
    String plainText=this.decryptFromResource(bodMap);
}

/**
 * 对称解密
 * @param bodMap
 * @return
 */
private String decryptFromResource(Map<String, Object> bodMap) throws GeneralSecurityException {
    log.info("密文解密");
    //通知数据
    Map<String,String> resourceMap=(Map<String,String>)bodMap.get("resource");
    //获取数据密文
    String ciphertext = resourceMap.get("ciphertext");
    //获取随机串
    String nonce = resourceMap.get("nonce");
    //获取附加数据
    String associated_data = resourceMap.get("associated_data");
    log.info("密文 ===> {}",ciphertext);
    AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
    String plainText=aesUtil.decryptToString(associated_data.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),
            ciphertext);
   log.info("明文 ===> {}",plainText);
    return plainText;
}

支付通知-处理订单状态&记录支付日志

@Override
public void processOrder(Map<String, Object> bodMap) throws GeneralSecurityException {
    log.info("处理订单");
    //解密报文
    String plainText=this.decryptFromResource(bodMap);
    //将明文转为map
    Gson gson = new Gson();
    HashMap plaintTextMap = gson.fromJson(plainText, HashMap.class);
    String orderNo = (String)plaintTextMap.get("out_trade_no");
    //处理重复的通知
    //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
    String orderStatus=orderInfoService.getOrderStatus(orderNo);
    if(!OrderStatus.NOTPAY.getType().equals(orderStatus)){
        return;
    }
    //更新订单状态
    orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.SUCCESS);

    //记录支付日志
    paymentInfoService.createPaymentInfo(plainText);

}
/**
 * 根据订单号更新订单状态
 * @param orderNo
 * @param orderStatus
 */
@Override
public void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus) {
    log.info("更新订单状态===>{}",orderStatus.getType());
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("order_no",orderNo);
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderStatus(orderStatus.getType());
    baseMapper.update(orderInfo,queryWrapper);
}
package com.yhd.paymentdemo.service.impl;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.yhd.paymentdemo.entity.PaymentInfo;
import com.yhd.paymentdemo.enums.PayType;
import com.yhd.paymentdemo.mapper.PaymentInfoMapper;
import com.yhd.paymentdemo.service.PaymentInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

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

@Service
@Slf4j
public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, PaymentInfo> implements PaymentInfoService {

    @Override
    public void createPaymentInfo(String plainText) {
        log.info("记录支付日志");
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        //商户订单号
        String orderNo = (String) plainTextMap.get("out_trade_no");
        //微信订单号
        String transactionId = (String) plainTextMap.get("transaction_id");
        //支付渠道,这里是native
        String tradeType = (String) plainTextMap.get("trade_type");
        //交易状态
        String tradeState = (String) plainTextMap.get("trade_state");
        //订单金额
        Map<String,Object> amount=(HashMap) plainTextMap.get("amount");
        //用户支付金额
        Integer payerTotal=((Double)amount.get("payer_total")).intValue();

        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setPaymentType(PayType.WXPAY.getType());
        paymentInfo.setTransactionId(transactionId);
        paymentInfo.setTradeType(tradeType);
        paymentInfo.setTradeState(tradeType);
        paymentInfo.setPayerTotal(payerTotal);
        paymentInfo.setContent(plainText);
        baseMapper.insert(paymentInfo);
    }
}

支付通知-解决重复通知

image-20221205171814074

支付通知-数据锁

@Override
public void processOrder(Map<String, Object> bodMap) throws GeneralSecurityException {
    log.info("处理订单");
    //解密报文
    String plainText=this.decryptFromResource(bodMap);
    //将明文转为map
    Gson gson = new Gson();
    HashMap plaintTextMap = gson.fromJson(plainText, HashMap.class);
    String orderNo = (String)plaintTextMap.get("out_trade_no");

    /*
       在对业务数据进行状态检查和处理之前,
         要采用数据锁进行并发控制,
         以避免函数重入造成的数据混乱
     */
    //尝试获取锁,成功则立即返回true,获取失败则立即返回false,不必一直等待锁的释放
    if(lock.tryLock()){
        try {
            //处理重复的通知
            //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
            String orderStatus=orderInfoService.getOrderStatus(orderNo);
            if(!OrderStatus.NOTPAY.getType().equals(orderStatus)){
                return;
            }

            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.SUCCESS);

            //记录支付日志
            paymentInfoService.createPaymentInfo(plainText);
        } finally {
                //主动释放锁
            lock.unlock();
        }

    }

image-20221206101246939

取消订单

/**
 *用户取消订单
 * @param orderNo
 */
@Override
public void cancelOrder(String orderNo) throws Exception {
    //调用微信支付的关单接口
    this.closeOrder(orderNo);
    //更新商户端的订单状态
    orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CANCEL);
}

/**
 * 调用关闭订单接口
 * @param orderNo
 */
private void closeOrder(String orderNo) throws Exception {
    log.info("关单接口的调用,订单号===>{}",orderNo);
    //创建远程请求的对象
    String url=String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(),orderNo);
    url=wxPayConfig.getDomain().concat(url);
    HttpPost httpPost = new HttpPost(url);
    //组装json请i躯体
    Gson gson = new Gson();
    HashMap<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);
    try {
        int statusCode = response.getStatusLine().getStatusCode();//响应状态码
        if (statusCode == 200) { //处理成功
            log.info("成功200" );
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功204");
        } else {
            log.info("失败,响应码 = " + statusCode);
            throw new IOException("request failed");
        }
    } finally {
        response.close();
    }

}

核实订单状态

该功能是在当我们未能接受到微信平台的支付成功回调通知时,我们主动查询该订单的支付状态,并对状态进行修改

启动定时任务

主启动类添加注解

@EnableScheduling
package com.yhd.paymentdemo.task;

import com.yhd.paymentdemo.entity.OrderInfo;
import com.yhd.paymentdemo.service.OrderInfoService;
import com.yhd.paymentdemo.service.WxPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 *
 **/
@Component
@Slf4j
public class WxPayTask {
    @Resource
    private OrderInfoService orderInfoService;
    @Resource
    private WxPayService wxPayService;
    /**
     * 秒 分 时 日 月 周
     **:每秒都执行
     * 1-3:从第1秒开始执行,到第3秒结束执行
     * 0/3:从第0秒开始,每隔三秒执行1次
     * 1,2,3:指定的第一秒,第2秒第3秒执行
     * ?:不指定
     * 日和周互斥不能同时指定,指定其中一个,则另一个设置为?
     *
     */
    @Scheduled(cron="0/3 * * * * ?")
    public void task1(){
        log.info("task1 被执行...");
    }

    /**
     * 从第0秒开始每隔30秒执行一次,查询创建超过5分钟,并且未支付的订单
     */
    @Scheduled(cron="0/3 * * * * ?")
    public void orderConfirm() throws Exception {
        log.info("orderConfirm 被执行...");
        List<OrderInfo>orderInfoList= orderInfoService.getNoPayOrdeByDuration(5);
        for (OrderInfo orderInfo : orderInfoList) {
            String orderNo=orderInfo.getOrderNo();
            log.warn("超时订单===》{}",orderNo);

            //核实订单状态,调用微信支付查单接口
            wxPayService.checkOrderStatus(orderNo);
        }
    }
}
/**
 * 查询创建超过minutes分钟并且未支付的订单
 * @param minutes
 * @return
 */
@Override
public List<OrderInfo> getNoPayOrdeByDuration(int minutes) {
    Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
    log.info("创建时间===>{}",instant);
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());
    queryWrapper.le("create_time",instant);
    List<OrderInfo> orderInfoList = baseMapper.selectList(queryWrapper);
    return orderInfoList;
}
/**
 * 根据订单号查询微信支付查单接口,核实订单状态
 * 如果订单已支付,则更新商户端订单状态
 * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
 * @param orderNo
 */
@Override
public void checkOrderStatus(String orderNo) throws Exception {
    log.warn("根据订单号核实订单状态===》{}",orderNo);
    //调用微信支付查单接口
    String result = this.queryOrder(orderNo);
    Gson gson = new Gson();
    Map resultMap = gson.fromJson(result, HashMap.class);
    //获取微信支付端的订单状态
    Object tradeState = resultMap.get("trade_state");
    if(WxTradeState.SUCCESS.getType().equals(tradeState)){
        log.warn("核实订单已支付===>{}",orderNo);
        //如果确认订单已经支付则更新本地订单状态
        orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.SUCCESS);
        //记录支付日志
        paymentInfoService.createPaymentInfo(result);
    }
    if(WxTradeState.NOTPAY.getType().equals(tradeState)){
        log.warn("核实订单未支付===>{}",orderNo);
        //如果订单未支付,则调用关单接口
        this.closeOrder(orderNo);
        orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CLOSED);
    }
}

image-20221206115849983

退款流程-申请退款

@ApiOperation("申请退款")
@PostMapping("/refunds/{orderNo}/{reason}")
public R refunds(@PathVariable String orderNo,@PathVariable String reason) throws Exception {
    log.info("申请退款");
    wxPayService.refund(orderNo,reason);
    return R.ok();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void refund(String orderNo, String reason) throws Exception {
    log.info("创建退款单记录");
    RefundInfo refundInfo=refundInfoService.createRefundByOrderNo(orderNo,reason);
    log.info("调用退款API");
    String url=wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
    HttpPost httpPost = new HttpPost(url);
    Gson gson = new Gson();
    Map paramsMap = new HashMap();
    paramsMap.put("out_trade_no",orderNo);//订单编号
    paramsMap.put("out_refund_no",refundInfo.getRefundNo());//退款编号
    paramsMap.put("reason",reason);//退款原因
    paramsMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));
    Map amountMap = new HashMap<>();
    amountMap.put("refund",refundInfo.getRefund());//退款金额
    amountMap.put("total",refundInfo.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("成功200" );
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功204");
        } else {
            log.info("退款异常,响应码 = " + statusCode+",退款返回结果 = "+bodyAsString);
            throw new IOException("request failed");
        }
        //更新订单状态
        orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.REFUND_PROCESSING);
        //更新退款单
        refundInfoService.updateRefund(bodyAsString);
    } finally {
        response.close();
    }

}
@Override
public RefundInfo createRefundByOrderNo(String orderNo, String reason) {
    //根据订单号获取订单信息
   OrderInfo orderInfo= orderInfoService.getOrderByOrderNo(orderNo);
   //根据订单号生成退款订单
    RefundInfo refundInfo = new RefundInfo();
    refundInfo.setOrderNo(orderNo);
    refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号(随机生成,可以使用uuid代替)
    refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)
    refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)
    refundInfo.setReason(reason);//退款原因
    //保存退款订单
    baseMapper.insert(refundInfo);
    return refundInfo;
}
/**
 * 根据订单号更新订单状态
 * @param orderNo
 * @param orderStatus
 */
@Override
public void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus) {
    log.info("更新订单状态===>{}",orderStatus.getType());
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("order_no",orderNo);
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderStatus(orderStatus.getType());
    baseMapper.update(orderInfo,queryWrapper);
}
@Override
public void updateRefund(String bodyAsString) {
    //将json转为map
    Gson gson = new Gson();
    Map<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
    //根据退款单编号修改退款单
    QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("refund_no",resultMap.get("out_refund_no"));
    //设置修改的字段
    RefundInfo refundInfo = new RefundInfo();
    refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号

    //查询退款和申请退款中的返回参数
    if(resultMap.get("status")!=null){
        refundInfo.setRefundStatus(resultMap.get("status"));//退款状态
        refundInfo.setContentReturn(bodyAsString);
    }
    //退款回调中的回调参数
    if(resultMap.get("refund_status")!=null){
        refundInfo.setRefundStatus(resultMap.get("refund_status"));//退款状态
    }
    refundInfo.setContentNotify(bodyAsString);//将全部响应结果存入数据库的content字段
    //更新退款单
    baseMapper.update(refundInfo,queryWrapper);

}

退款-查询退款

@ApiOperation("查询退款,测试用")
@GetMapping("/query-refund/{refundNo}")
public R queryRefund(@PathVariable String refundNo) throws Exception {
    log.info("查询退款");
    String result=wxPayService.querRefund(refundNo);
    return R.ok().setMessage("查询成功").data("result",result);
}
Override
public String querRefund(String refundNo) throws Exception{
    String url=String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(),refundNo);
    url=wxPayConfig.getDomain().concat(url);
    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("成功200" );
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功204");
        } else {
            log.info("退款异常,响应码 = " + statusCode+",退款返回结果 = "+bodyAsString);
            throw new IOException("request failed");
        }
      return bodyAsString;
    } finally {
        response.close();
    }
}

退款-退款通知

@ApiOperation("退款通知")
@PostMapping("/refunds/notify")
public String refundsNotify(HttpServletRequest request,HttpServletResponse response){
    log.info("退款通知执行");
    Gson gson = new Gson();
    Map<String,String> map=new HashMap<>();//应答对象
    try {//快捷键CTRL+ALT+T
        //处理通知参数
        String body = HttpUtils.readData(request);
        Map<String,Object> bodyMap = gson.fromJson(body, HashMap.class);
        String requestId=(String)bodyMap.get("id");
        log.info("退款通知的id==>{}",bodyMap.get("id"));
        log.info("退款通知的完整数据==>{}",body);

        //TODO :签名的验签
        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.processRefund(bodyMap);

        //成功应答
        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);
    }
}
@Override
public void processRefund(Map<String, Object> bodyMap) throws GeneralSecurityException {
    log.info("退款单");
    //解密报文
    String plainText=this.decryptFromResource(bodyMap);
    //将明文转为map
    Gson gson = new Gson();
    HashMap plaintTextMap = gson.fromJson(plainText, HashMap.class);
    String orderNo = (String)plaintTextMap.get("out_trade_no");

    /*
       在对业务数据进行状态检查和处理之前,
         要采用数据锁进行并发控制,
         以避免函数重入造成的数据混乱
     */
    //尝试获取锁,成功则立即返回true,获取失败则立即返回false,不必一直等待锁的释放
    if(lock.tryLock()){
        try {
            //处理重复的通知
            //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
            String orderStatus=orderInfoService.getOrderStatus(orderNo);
            if(!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)){
                return;
            }

            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.REFUND_SUCCESS);

            //记录支付日志
           refundInfoService.updateRefund(plainText);
        } finally {
            //主动释放锁
            lock.unlock();
        }

    }
}

账单-获取账单

@ApiOperation("获取账单url:测试用")
@GetMapping("/quearybill/{billDate}/{type}")
public R quearyBill(@PathVariable String billDate,
                      @PathVariable String type) throws Exception {
    log.info("获取账单url");
    String downloadUrl=wxPayService.queryBill(billDate,type);
    return R.ok().setMessage("获取账单url成功").data("downloadUrl",downloadUrl);
}
/**
 * 查询账单
 * @param billDate
 * @param type
 * @return
 * @throws Exception
 */
@Override
public String queryBill(String billDate, String type)throws Exception{
    log.info("申请账单接口调用 {}",billDate);
    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");
    CloseableHttpResponse response = wxPayClient.execute(httpGet);
    HashMap<String, Object> map = new HashMap<>();

    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+ ", 返回结果 = " + EntityUtils.toString(response.getEntity()));
            throw new RuntimeException("申请账单异常,响应码="+statusCode+",申请账单返回数据"+bodyAsString);
        }
        Gson gson = new Gson();
        Map<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
        return resultMap.get("download_url");
    } finally {
        response.close();
    }
}

账单-下载账单

@ApiOperation("下载账单")
@GetMapping("/downloadbill/{billDate}/{type}")
public R downloadBill(@PathVariable String billDate,
                      @PathVariable String type) throws Exception {
    log.info("下载账单");
    String result=wxPayService.downloadBill(billDate,type);
    return R.ok().data("result",result);
}
/**
 * 下载账单
 * @param billDate
 * @param type
 * @return
 */
@Override
public String downloadBill(String billDate, String type) throws Exception {
    log.info("下载账单");
    String url = this.queryBill(billDate, type);
    HttpGet httpGet = new HttpGet(url);
    httpGet.setHeader("Accept","application/json");

    //使用wxPayClient发送请求得到响应
    CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);
    try {
        String bodyAsString = EntityUtils.toString(response.getEntity());
        int statusCode = response.getStatusLine().getStatusCode();//响应状态码
        if (statusCode == 200) { //处理成功
            log.info("成功200" );
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功204");
        } else {
            log.info("失败,响应码 = " + statusCode);
            throw new RuntimeException("下载账单异常,响应码:"+statusCode+",响应内容:"+bodyAsString);
        }
        return bodyAsString;
    } finally {
        response.close();
    }
}

整套流程下来对微信支付有了一个初步的了解,还需要看更多的api

标签:info,log,微信,支付,new,import,com,String
From: https://www.cnblogs.com/yhdxx/p/16955754.html

相关文章

  • 微信网页开发禁用分享
    首先引入wiexinjs<scriptsrc="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"type="text/javascript"></script>其次functiononBridgeReady(){Weixin......
  • 技术分享| anyRTC音视频与微信小程序互通实践
    随着网络架构的变迁、媒体技术发展、音视频场景迭代,基于流媒体的技术也是推陈出新。WebRTC渐渐的成为了音视频互动场景的主流,而微信在6.5.21版本通过小程序开放了实时音视频......
  • 钉钉小程序与企业微信小程序快速入门
    最近开发了一款关于钉钉小程序与企业微信小程序企业办公工具,api跟微信小程序差不多,但应用载体不同,或多或少有些异同,由于临时学习开发,简单记录一下:钉钉小程序快速入门: ......
  • 技术分享| anyRTC音视频与微信小程序互通实践
    随着网络架构的变迁、媒体技术发展、音视频场景迭代,基于流媒体的技术也是推陈出新。WebRTC渐渐的成为了音视频互动场景的主流,而微信在6.5.21版本通过小程序开放了实时音视......
  • 微信小程序写入缓存再页面跳转,部分机型异常处理
    先看下发现异常的代码:uni.setStorage({ key:"tmp_registerPageInfo", data:_registerPageInfo})uni.navigateTo({ url:"/attestationPackage/pages/bankCard/mai......
  • 第三方集成 | 支付宝支付
    前端调用 this.payOrder.payType===‘alipay’{aliPayApi.tradePagePay(this.payOrder.productId).then((response)=>{document.write(response.data.formSt......
  • 支付宝继承支付
    支付宝集成的一些知识点 应用公钥(publickey)需提供给支付宝账号管理者上传到支付宝开放平台。应用私钥(privatekey)由开发者自己保存,需填写到代码中供签名时使用。生成......
  • 个人微信开发API,微信机器人
    微信个人号二次开发,基于API开发可以有很多功能模块各种知名SCRM系统、客服平台都是根据此API二次开发的。好友管理:添加好友、删除好友、修改备注、创建标签、获取好友列......
  • 微信小程序 调用微信物流下单 {"errcode":9300526,"errmsg":" input arg error, pleas
    官方:物流下单logistics.addOrderhttps://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.addOrder.html 上面的错误......
  • 处理来自微信的文本消息
    官方文档在这里。我们的公众号服务器可以接收来自微信服务器的普通消息,包括:文本消息图片消息语音消息小视频消息地理位置消息链接消息这里以文本消息为例,介绍如......