从零玩转系列之微信支付实战PC端支付微信回调接口搭建
一、前言
halo各位大佬很久没更新了最近在搞微信支付,因商户号审核了我半个月和小程序认证也找了资料并且将商户号和小程序进行关联,至此微信支付Native支付完成.此篇文章过长我将分几个阶段的文章发布(项目源码都有,小程序和PC端)
在此之前已经更新了微信支付开篇、微信支付安全、微信实战基础框架搭建、本次更新为微信支付实战PC端接口搭建,实战篇分为几个章节因为代码量确实有点多哈.
- 第一章从零玩转系列之微信支付开篇
 - 第二章从零玩转系列之微信支付安全
 - 第三章从零玩转系列之微信支付实战基础框架搭建
 - 第四章从零玩转系列之微信支付实战PC端支付下单接口搭建
 - 第五章从零玩转系列之微信支付实战PC端支付微信回调接口搭建
 
本次项目使用技术栈
后端: SpringBoot3.1.x、Mysql8.0、MybatisPlus
前端: Vue3、Vite、ElementPlus
小程序: Uniapp、Uview
问题微信添加: BN_Tang
备注: 微信支付
一、Native模式回调
当用户支付完成时候微信会下发一个回调到我们系统当中
该链接是通过基础下单接口中的请求参数notify_url来设置的,要求必须为https地址。请确保回调URL是外部可正常访问的,且不能携带后缀参数,否则可能导致商户无法接收到微信的回调通知信息。回调URL示例: “https://xxxxxx.com/api/wx-pay/native/notify”
通知规则
用户支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理该消息,并返回应答。
对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。(通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)
OK 我们在下单的时候设置了回调必须是HTTPS的SSL证书的
搭建本地调试 到时候上线的时候就替换域名即可
同学们可以使用免费的内网穿透,使用方式官方文档很详细仔细看我这就不讲解.
- https://www.ngrok.cc/   Sunny-Ngrok
- 提供免费内网穿透服务,免费服务器支持绑定自定义域名
 - 管理内网服务器,内网web进行演示
 - 快速开发微信程序和第三方支付平台调试
 - 本地WEB外网访问、本地开发微信、TCP端口转发
 - 本站新增FRP服务器,基于 FRP 实现https、udp转发
 - 无需任何配置,下载客户端之后直接一条命令让外网访问您的内网不再是距离
 
 
目前博主使用的是花生壳 收费也就6块钱 给了两个SSL的域名速度还可以
- https://hsk.oray.com/ 花生壳 so easy to happy的东西
 - 无需依赖公网IP、无需配置路由器,花生壳支持在客户端上
 - 添加端口映射,快速将内网服务发布到外网
 

开启内网穿透代理地址到本地 127.0.0.1:9080
修改 wxpay.properties 当中  wxpay.notify-domain 参数为你的内网穿透地址

支付通知
通知报文
支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情。
(注:由于涉及到回调加密和解密,商户必须先设置好apiv3秘钥后才能解密回调通知,apiv3秘钥设置文档指引详见APIv3秘钥设置指引)
上面的为商户APIV3的密钥之前我们已经设置好了还未设置的请参考开篇->获取APIv3秘钥(后续都是使用这个秘钥)
通知签名
加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考 《微信支付API v3签名验证》。
官方话语我就不说了感兴趣的去看文档详细的
总结一下回调需要干的事情
1.签名验证
处理签名验证
构造验签名串
首先,商户先从应答中获取以下信息。
- HTTP头
Wechatpay-Timestamp中的应答时间戳。 - HTTP头
Wechatpay-Nonce中的应答随机串。 - 应答主体(response Body),需要按照接口返回的顺序进行验签,错误的顺序将导致验签失败。
 
然后,请按照以下规则构造应答的验签名串。签名串共有三行,行尾以\n 结束,包括最后一行。\n为换行符(ASCII编码值为0x0A)。若应答报文主体为空(如HTTP状态码为204 No Content),最后一行仅为一个\n换行符。
应答时间戳\n
应答随机串\n
应答报文主体\n
我们可以看微信它是咋验证的我们就根据文档的要求改造一下子就行idea 按两下 shift 搜索 WechatPay2Validator
引用地址: com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator

好像啊 直接Copy 新增wechat文件夹复制到该文件夹当中 命名为 WechatPay2ValidatorForRequest
模仿微信验证签名,自定义支付通知API验证签名,针对通知请求的签名验证
改造构造函数
// 回调报文
protected final String body;
// 回调唯一ID 没啥用反正原来存在我们就放在这呗
protected final String requestId;
/**
 * 微信验证器
 *
 * @param verifier  验证器
 * @param requestId 请求id
 * @param body      微信回调的body
 */
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
    this.verifier = verifier;
    this.requestId = requestId;
    this.body = body;
}
改造验证方法
/**
 * 验证
 *
 * @param request 请求
 * @return boolean 是否成功
 * @throws IOException ioexception
 */
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, request.getHeader(REQUEST_ID));
        }
    } catch (IllegalArgumentException e) {
        log.warn(e.getMessage());
        return false;
    }
    return true;
}
    /**
     * 构建验证签名消息
     * 参考文档:<a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml">参考文档</a>
     * <p>
     * 构造验签名串
     * 首先,商户先从应答中获取以下信息。
     * <p>
     * HTTP头Wechatpay-Timestamp 中的应答时间戳。
     * HTTP头Wechatpay-Nonce 中的应答随机串。
     * 应答主体(response Body),需要按照接口返回的顺序进行验签,错误的顺序将导致验签失败。
     * 然后,请按照以下规则构造应答的验签名串。签名串共有三行,行尾以\n 结束,包括最后一行。\n为换行符(ASCII编码值为0x0A)。
     * 若应答报文主体为空(如HTTP状态码为204 No Content),最后一行仅为一个\n换行符。
     * <p>
     ************************************
     * 应答时间戳\n
     * 应答随机串\n
     * 应答报文主体\n
     ************************************
     * <p>
     *
     * @param request 请求
     * @return {@link String}
     * @throws IOException ioexception
     */
    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";
    }
改造验证回调参数
/**
 * 验证参数
 *
 * @param request 请求
 */
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);
    }
}
完整代码 自定义验证签名器
package com.yby6.wechat;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.*;
/**
 * 模仿微信验证签名,自定义支付通知API验证签名,针对通知请求的签名验证
 *
 * @author Yang Shuai
 * Create By 2023/06/18
 */
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 body;
    protected final String requestId;
    /**
     * 微信验证器
     *
     * @param verifier  验证器
     * @param requestId 请求id
     * @param body      微信回调的body
     */
    public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
        this.verifier = verifier;
        this.requestId = requestId;
        this.body = body;
    }
    /**
     * 参数错误
     *
     * @param message 消息
     * @return {@link IllegalArgumentException}
     */
    protected static IllegalArgumentException parameterError(String message, Object... args) {
        message = String.format(message, args);
        return new IllegalArgumentException("parameter error: " + message);
    }
    /**
     * 验证失败
     *
     * @param message 消息
     * @return {@link IllegalArgumentException}
     */
    protected static IllegalArgumentException verifyFail(String message, Object... args) {
        message = String.format(message, args);
        return new IllegalArgumentException("signature verify fail: " + message);
    }
    /**
     * 验证
     *
     * @param request 请求
     * @return boolean 是否成功
     * @throws IOException ioexception
     */
    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, request.getHeader(REQUEST_ID));
            }
        } catch (IllegalArgumentException e) {
            log.warn(e.getMessage());
            return false;
        }
        return true;
    }
    /**
     * 验证参数
     *
     * @param request 请求
     */
    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);
        }
    }
    /**
     * 构建验证签名消息
     * 参考文档:<a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml">参考文档</a>
     * <p>
     * 构造验签名串
     * 首先,商户先从应答中获取以下信息。
     * <p>
     * HTTP头Wechatpay-Timestamp 中的应答时间戳。
     * HTTP头Wechatpay-Nonce 中的应答随机串。
     * 应答主体(response Body),需要按照接口返回的顺序进行验签,错误的顺序将导致验签失败。
     * 然后,请按照以下规则构造应答的验签名串。签名串共有三行,行尾以\n 结束,包括最后一行。\n为换行符(ASCII编码值为0x0A)。
     * 若应答报文主体为空(如HTTP状态码为204 No Content),最后一行仅为一个\n换行符。
     * <p>
     ************************************
     * 应答时间戳\n
     * 应答随机串\n
     * 应答报文主体\n
     ************************************
     * <p>
     *
     * @param request 请求
     * @return {@link String}
     * @throws IOException ioexception
     */
    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";
    }
}
处理报文解密
2.验证成功后解密加密的报文
参数解密
下面详细描述对通知数据进行解密的流程:
- 1、用商户平台上设置的APIv3密钥【微信商户平台—>账户设置—>API安全—>设置APIv3密钥】,记为key;
 - 2、针对resource.algorithm中描述的算法(目前为AEAD_AES_256_GCM),取得对应的参数nonce和associated_data;
 - 3、使用key、nonce和associated_data,对数据密文resource.ciphertext进行解密,得到JSON形式的资源对象;
 
注: AEAD_AES_256_GCM算法的接口细节,请参考rfc5116。微信支付使用的密钥key长度为32个字节,随机串nonce长度12个字节,associated_data长度小于16个字节并可能为空字符串。
证书和回调报文解密
为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密。本章节详细介绍了加密报文的格式,以及如何进行解密。
微信返回来的加密报文格式
AES-GCM是一种NIST标准的认证加密算法, 是一种能够同时保证数据的保密性、 完整性和真实性的一种加密模式。它最广泛的应用是在TLS中。
证书和回调报文使用的加密密钥为APIv3密钥。
对于加密的数据,我们使用了一个独立的JSON对象来表示。为了方便阅读,示例做了Pretty格式化,并加入了注释。
{
	"original_type": "transaction", // 加密前的对象类型
	"algorithm": "AEAD_AES_256_GCM", // 加密算法
	// Base64编码后的密文
	"ciphertext": "...",
	// 加密使用的随机串初始化向量)
	"nonce": "...",
	// 附加数据包(可能为空)
	"associated_data": ""
}
️ 加密的随机串,跟签名时使用的随机串没有任何关系,是不一样的。
解密
算法接口的细节,可以参考RFC 5116。
大部分编程语言(较新版本)都支持了AEAD_AES_256_GCM 。开发者可以参考下列的示例,了解如何使用您的编程语言实现解密。
我们引入的SDK已经有工具类直接用 com.wechat.pay.contrib.apache.httpclient.util.AesUtil
创建处理返回的
\n的报文转json
package com.yby6.wechat;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
 * 用于解析微信支付回调的数据
 *
 * @author Yang Shuai
 * Create By 2023/06/14
 */
public class HttpUtils {
    /**
     * 分析数据
     *
     * @param request 请求
     * @return {@link Map}<{@link String}, {@link Object}>
     */
    public static Map<String, Object> analysisData(HttpServletRequest request) {
        String body = HttpUtils.readData(request);
        return JSONUtil.toBean(body, HashMap.class);
    }
    /**
     * 将通知参数转化为字符串
     */
    public static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
知识点就这些了 实现一手
创建 nativeNotify 映射方法
/**
 * 支付通知->微信支付通过支付通知接口将用户支付成功消息通知给商户
 * 参考:<a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_5.shtml">...</a>
 */
@PostMapping("/notify")
    public Map<String, String> nativeNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("接收到微信服务回调......");
        try {
            //处理通知参数
            String body = HttpUtils.readData(request);
            Map<String, Object> bodyMap = JSONUtil.toBean(body, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            log.info("支付通知的id ===> {}", requestId);
            // 签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId, body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                log.error("通知验签失败");
                //失败应答
                response.setStatus(500);
                return WechatRep.fail();
            }
            log.info("通知验签成功:{}", bodyMap);
            log.info("回调业务处理完毕");
            // 成功应答
            response.setStatus(200);
            return WechatRep.ok();
        } catch (Exception e) {
            log.error("处理微信回调失败:", e);
            // 失败应答
            response.setStatus(500);
            return WechatRep.fail();
        }
    }
启动项目测试流程
开启内网穿透 映射你启动项目的端口 自己访问一下是否通
启动程序 请求下单接口  /api/wx-pay/native/native/{productId}
{productId} 查看商品表数据的ID
复制返回的微信二维码地址
进入 https://cli.im/url 生成扫描二维码 使用微信扫描
等待微信回调

ok我们可以正常的接收到微信的回调我们需要根据回调的数据来处理自己系统的业务
修改回调方法 新增 processOrder 业务传递报文
log.info("通知验签成功:{}", bodyMap);
// 通知回调 -> 更新订单状态逻辑
wxPayService.processOrder(bodyMap);
log.info("回调业务处理完毕");
修改 WxPayService 服务类
可以搞redsi分布式锁根据实际业务来我们只是个demo就不要那么严谨
/**
* 一个可重入互斥 锁
*/
private final ReentrantLock lock = new ReentrantLock();
/**
 * 通知回调-> 更新订单状态逻辑
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException, InterruptedException {
    log.info("处理订单");
    //解密报文
    String plainText = decryptFromResource(bodyMap);
        // 将明文转换成map
        Map<String, Object> plainTextMap = JSONUtil.toBean(plainText, Map.class);
        String orderNo = (String) plainTextMap.get("out_trade_no");
    // 微信特别提醒:
    // 在对业务数据进行状态检查和处理之前,
    // 要采用数据锁进行并发控制,以避免函数重入造成的数据混乱.
    // 尝试获取锁:
    // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放.
    if (lock.tryLock()) {
        try {
            // 处理重复的通知
            // 接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
            OrderInfo orderInfo = orderInfoService.lambdaQuery().eq(OrderInfo::getOrderNo, (orderNo)).one();
            if (null != orderInfo && !OrderStatus.NOTPAY.getType().equals(orderInfo.getOrderStatus())) {
                log.info("重复的通知,已经支付成功啦");
                return;
            }
            // 模拟通知并发
            //TimeUnit.SECONDS.sleep(5);
            // 更新订单状态
            orderInfoService.lambdaUpdate().eq(OrderInfo::getOrderNo, orderNo).set(OrderInfo::getOrderStatus, OrderStatus.SUCCESS.getType()).update();
            log.info("更新订单状态,订单号:{},订单状态:{}", orderNo, OrderStatus.SUCCESS);
            // 记录支付日志
            paymentInfoService.createPaymentInfo(plainText);
        } finally {
            // 要主动释放锁
            lock.unlock();
        }
    }
}
参数解密
/**
 * 对称解密
 */
private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
    log.info("密文解密");
    //通知数据拿到 resource 节点
    Map<String, String> resourceMap = (Map) bodyMap.get("resource");
    //数据密文
    String ciphertext = resourceMap.get("ciphertext");
    //随机串
    String nonce = resourceMap.get("nonce");
    //附加数据
    String associatedData = resourceMap.get("associated_data");
    log.info("密文 ===> {}", ciphertext);
    AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
    // 使用key、nonce和associated_data,对数据密文resource.ciphertext进行解密,得到JSON形式的资源对象
    String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
    log.info("明文 ===> {}", plainText);
    return plainText;
}
记录支付日志
引入
/**
 * 支付日志
 */
private final PaymentInfoService paymentInfoService;
修改 PaymentInfoService
package com.yby6.service;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yby6.domain.PaymentInfo;
import com.yby6.enums.PayType;
import com.yby6.mapper.PaymentInfoMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Slf4j
@Service
public class PaymentInfoService extends ServiceImpl<PaymentInfoMapper, PaymentInfo> {
    /**
     * 创建付款信息
     *
     * @param plainText 纯文本
     */
    @Transactional
    public void createPaymentInfo(String plainText) {
        log.info("记录支付日志: {}", plainText);
        Map<String, Object> plainTextMap = JSONUtil.toBean(plainText, Map.class);
        //订单号
        String orderNo = (String) plainTextMap.get("out_trade_no");
        //业务编号
        String transactionId = (String) plainTextMap.get("transaction_id");
        //支付类型
        String tradeType = (String) plainTextMap.get("trade_type");
        //交易状态
        String tradeState = (String) plainTextMap.get("trade_state");
        //用户实际支付金额
        Map<String, Object> amount = (Map<String, Object>) plainTextMap.get("amount");
        Integer payerTotal = MapUtil.getInt(amount, "payer_total");
        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setPaymentType(PayType.WXPAY.getType());
        paymentInfo.setTransactionId(transactionId);
        paymentInfo.setTradeType(tradeType);
        paymentInfo.setTradeState(tradeState);
        paymentInfo.setPayerTotal(payerTotal);
        paymentInfo.setContent(plainText);
        baseMapper.insert(paymentInfo);
    }
}
启动项目测试流程
开启内网穿透 映射你启动项目的端口 自己访问一下是否通
启动程序 请求下单接口  /api/wx-pay/native/native/{productId}
{productId} 查看商品表数据的ID
复制返回的微信二维码地址
进入 https://cli.im/url 生成扫描二维码 使用微信扫描
等待微信回调处理系统业务订单状态更改

从零玩转系列之微信支付实战PC端支付微信回调接口搭建的更多相关文章
- 微信开放平台PC端扫码登录功能个人总结
		
最近公司给我安排一个微信登录的功能,需求是这样的: 1.登录授权 点击二维码图标后,登录界面切换为如下样式(二维码),微信扫描二维码并授权,即可成功登录: 若当前账号未绑定微信账号,扫描后提示“ ...
 - c#版在pc端发起微信扫码支付
		
等了好久,微信官方终于发布了.net的demo. 主要代码: /** * 生成直接支付url,支付url有效期为2小时,模式二 * @param productId 商品ID * @return 模式 ...
 - 【微信开发】PC端 微信扫码支付成功之后自动跳转
		
场景: PC端 微信扫码支付 结果: 支付成功 自动跳转 实现思路: 支付二维码页面,写ajax请求支付状态,请求到结果,无论成功还是失败,都跳转到相应的结果页面 具体实现方法: html部分: ...
 - 前端:微信支付和支付宝支付在pc端和h5页面中的应用
		
1:h5微信支付 使用的是https://pay.weixin.qq.com/wiki/doc/api/index.html 中的 (1):公司需要首先要配置公众号微信支付地址和测试白名单(支付的时 ...
 - Java SpringMVC实现PC端网页微信扫码支付完整版
		
一:前期微信支付扫盲知识 前提条件是已经有申请了微信支付功能的公众号,然后我们需要得到公众号APPID和微信商户号,这个分别在微信公众号和微信支付商家平台上面可以发现.其实在你申请成功支付功能之后,微 ...
 - 【weixin】微信支付---Native支付模式二(PC端支付大多采用此模式)
		
[模式二]:商户后台系统调用微信支付[统一下单API]生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易.注意:该模式的预付单有效期为2小时,过期后无法支付 模式二与模式一相比, ...
 - pc端用微信扫一扫实现微信第三方登陆
		
官方文档链接 第一步:获取AppID AppSecret (微信开发平台申请PC端微信登陆) 第二步:生成扫描二维码,获取code https://open.weixin.qq.com/conn ...
 - 支付宝pc端支付接入PHP实现
		
引入支付宝接口 放入一个插件库中,方便管理 创建支付类 1.发起支付 public function init() { $order_id = $_REQUEST['order_id']; $orde ...
 - Python接入支付宝进行PC端支付
		
1. 支付宝开放平台登录,使用支付宝账号登录 https://open.alipay.com/platform/home.htm 2.选择沙箱模式 [支付宝提供了测试环境] https://docs ...
 - PC端网站微信扫码登录
		
需求分析:用户通过扫描我们网页的二维码,如果已经绑定我们平台的账户,即成功进入首页,否则提示先绑定个人微信账号. 1.绑定微信账号:是通过关注微信公众号实现绑定个人微信账号.首先通过后台接口获取到ti ...
 
随机推荐
- 【机器学习与深度学习理论要点】20. 什么是激活函数,为什么要用激活函数,常见的激活函数和特点,softmax函数
			
1)什么是激活函数,为什么要用激活函数? 激活函数,指神经网络中将输入信号的总和转换为输出信号的函数,激活函数将多层感知机输出转换为非线性,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应 ...
 - SSM整合之基础xml文件以及基础理解
			
文章目录 前言 什么是SSM框架 实例 pom依赖 Spring 文件配置 最后 展示代码 总结 前言 最近在学习SSM框架,就去找了一些视频来看,写一篇整合的文章,文章包括一些整合的依赖,以及创建的 ...
 - 案例分享-full gc导致k8s pod重启
			
在之前的记一次k8s pod频繁重启的优化之旅中分享过对于pod频繁重启的一些案例,最近又遇到一例,继续分享出来希望能给大家带来些许收获. 问题现象 报警群里突然显示某pod频繁重启,我随即上去查看日 ...
 - IPS 和 IDS
			
IPS/IDS 什么是IPS和IDS IDS/IPS是检测和防止对网络服务器进行未授权的访问的系统.有许多产品同时有IDS和IPS的作用,作为加强企业信息安全所必须的系统 什么是IDS(Intrusi ...
 - 2.OS-Virtualization|抽象:进程
			
进程:运行中的程序. 没有运行前,就是一个代码,他是静态的. 通过一系列抽象,有了状态. 通过进程抽象,我们需要明白计算机设计的一些思考点:如何分离?如何管理?如何抽象? 运行 就绪 阻塞 它就是一个 ...
 - 2022-03-04:爱吃香蕉的珂珂。 珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。 珂珂可以决定她吃香蕉的速度 K (单位:根
			
2022-03-04:爱吃香蕉的珂珂. 珂珂喜欢吃香蕉.这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉.警卫已经离开了,将在 H 小时后回来. 珂珂可以决定她吃香蕉的速度 K (单位:根 ...
 - Django4全栈进阶之路9 STATIC静态文件路径设置、MEDIA媒体文件路径设置
			
在 Django 4 中,可以在 settings.py 文件中设置 STATICFILES_DIRS 来指定应用程序静态文件所在的文件夹路径,设置 STATIC_ROOT 来指定收集所有应用程序静态 ...
 - Python基础 - 输入和输出
			
输出 Python提供了 print() 内置函数完成输出 1 print("你好") 2 3 4 # 你好 n = "你好" print(n) # 你好 ...
 - WPF 入门笔记 - 02 - 布局综合应用
			
本篇博文对接上篇末尾处WPF常用布局控件的综合应用,为痕迹g布局控件介绍课后作业的一个思路方法. 前言 首先来谈一谈布局原则: WPF窗口只能包含一个元素(Window元素属于内容控件,内容控件只允许 ...
 - C#使用HtmlAgilityPack解析Html 爬取图片和视频
			
HtmlAgilityPack简介 HtmlAgilityPack是.net下的一个HTML解析类库.支持用XPath来解析HTML. 问题来了,有人就会问为什么要使用能XPath呢? 小编答:因为对 ...