本文将详细讲讲微信小程序的注册登录 + 微信支付的前后端完整流程,涵盖所有参数传递和配合细节,按模块一步步拆解,非常适合 Java + Spring Boot 项目开发者食用。
📌 一、微信小程序注册登录 — 前后端协作详解
📲 前端(小程序端)
1. 使用 wx.login() 获取临时登录凭证(code):
js
wx.login({
success(res) {
const code = res.code;
wx.request({
url: 'https://你的后端地址/api/wx/login',
method: 'POST',
data: { code },
success: (resp) => {
const token = resp.data.token;
wx.setStorageSync('token', token); // 存入本地
}
});
}
});💻 后端(Spring Boot)
1. 控制器接口
java
@PostMapping("/api/wx/login")
public R<LoginVO> wxLogin(@RequestBody WxLoginDTO dto) {
String code = dto.getCode();
// 通过 code 获取 session_key + openid
String url = "https://api.weixin.qq.com/sns/jscode2session" +
"?appid=" + appId +
"&secret=" + appSecret +
"&js_code=" + code +
"&grant_type=authorization_code";
String resStr = restTemplate.getForObject(url, String.class);
JSONObject resJson = JSON.parseObject(resStr);
String openId = resJson.getString("openid");
String sessionKey = resJson.getString("session_key");
// 绑定 openId 到用户(如不存在则注册)
User user = userService.getOrCreateByOpenId(openId);
// 生成 JWT
String token = jwtUtils.generateToken(user.getId());
return R.ok(new LoginVO(token));
}2. DTO / VO 示例:
java
@Data
public class WxLoginDTO {
private String code;
}
@Data
@AllArgsConstructor
public class LoginVO {
private String token;
}3. JWT 工具类 generateToken()
java
public String generateToken(Long userId) {
return Jwts.builder()
.setSubject(String.valueOf(userId))
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}💰 二、微信小程序支付 — 前后端完整流程
🧠 场景说明
- 前端发起支付请求(需要携带订单号)
- 后端生成 prepay_id,构造支付参数
- 前端用
wx.requestPayment()发起支付 - 支付完成,微信回调 notify_url
📲 前端流程
js
// 获取后台支付参数
wx.request({
url: 'https://你的后端地址/api/pay/wx',
method: 'POST',
header: {
Authorization: 'Bearer ' + wx.getStorageSync('token')
},
data: { orderId: 'ORDER_123456' },
success: (res) => {
const payInfo = res.data;
wx.requestPayment({
...payInfo,
success() {
console.log('支付成功');
},
fail() {
console.log('支付失败');
}
});
}
});💻 后端流程
1. 接收支付请求 & 调用微信统一下单 API
java
@PostMapping("/api/pay/wx")
public R<Map<String, String>> unifiedOrder(@RequestBody WxPayDTO dto, HttpServletRequest request) {
Order order = orderService.getById(dto.getOrderId());
if (order == null || order.getStatus() != OrderStatus.UNPAID) {
return R.fail("订单不存在或已支付");
}
WxPayUnifiedOrderRequest payRequest = new WxPayUnifiedOrderRequest();
payRequest.setOutTradeNo(order.getOrderNo());
payRequest.setTotalFee(order.getPayAmount().multiply(new BigDecimal(100)).intValue()); // 单位:分
payRequest.setBody("订单支付");
payRequest.setOpenid(getOpenIdFromToken());
payRequest.setTradeType("JSAPI");
payRequest.setNotifyUrl("https://你的后端地址/api/pay/notify");
WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(payRequest);
// 二次签名参数
Map<String, String> payParam = new HashMap<>();
payParam.put("appId", appId);
payParam.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
payParam.put("nonceStr", RandomUtil.randomString(32));
payParam.put("package", "prepay_id=" + result.getPrepayId());
payParam.put("signType", "MD5");
// 签名
String paySign = WxPayUtil.createSign(payParam, "UTF-8", mchKey);
payParam.put("paySign", paySign);
return R.ok(payParam);
}2. 支付回调(微信服务器调用)
java
@PostMapping("/api/pay/notify")
public String wxNotify(HttpServletRequest request) throws Exception {
String xml = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xml);
String outTradeNo = notifyResult.getOutTradeNo();
if ("SUCCESS".equals(notifyResult.getResultCode())) {
orderService.markPaid(outTradeNo);
}
return "<xml><return_code>SUCCESS</return_code></xml>";
}🚀 数据流向总结图:
text
小程序(wx.login) → 后端(code换openId) → 生成JWT → 小程序存token
小程序(请求支付) → 后端(unifiedOrder) → 生成prepayId → 二次签名返回参数
小程序(wx.requestPayment) → 微信支付系统 → 回调后端 /api/pay/notify → 修改订单状态✅ 补充建议
| 模块 | 要点 |
|---|---|
| 登录 | 用 openId 绑定用户 |
| token | 建议用 JWT,避免 session 存储 |
| 微信支付 | 必须备案商户 + 配置 v3 key + 证书 |
| 安全 | 所有接口加 token 校验 + 参数校验 |
| 回调 | 幂等处理,订单状态不可重复变更 |
微信登录/注册、付款、退款核心功能最佳实践,后端功能demo
好的,咱们直接来一套完整的、可落地的微信小程序核心功能后端实践代码:包含登录/注册、付款 和 退款。用的是 Java + Spring Boot,结构清晰、实际可跑,适合你作为后端开发直接集成和调试。
🧩 技术栈假设
- Spring Boot 3.x
- 使用 RestTemplate / HttpClient 请求微信服务器
- 数据库:MySQL(保存用户、订单等)
- 已获取:微信小程序
appId和secret - 已配置:微信商户平台的
mchid、apiV3Key、商户证书
📌 项目结构简述
com.xxx.wechat
├── controller
│ └── WxController.java // 控制器
├── service
│ ├── WxAuthService.java // 登录逻辑
│ ├── WxPayService.java // 支付逻辑
├── util
│ └── WxPayUtils.java // 签名工具
├── dto
│ ├── LoginRequest.java
│ └── PayRequest.java
├── entity
│ └── User.java
└── config
└── WxPayConfig.java // 微信配置✅ 登录与注册实现(wx.login)
前端小程序
js
// login.js
wx.login({
success: res => {
wx.request({
url: 'http://localhost:8080/wx/login',
method: 'POST',
data: {
code: res.code
},
success: r => {
console.log('登录成功', r.data)
wx.setStorageSync('token', r.data.token)
}
})
}
})后端 Controller
java
@PostMapping("/wx/login")
public ApiResponse<?> login(@RequestBody LoginRequest request) {
return ApiResponse.success(wxAuthService.loginOrRegister(request.getCode()));
}Service:微信登录实现
java
@Service
public class WxAuthService {
@Value("${wx.appid}")
private String appId;
@Value("${wx.secret}")
private String secret;
@Autowired
private UserRepository userRepository;
public LoginResponse loginOrRegister(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId +
"&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code";
String response = HttpUtil.get(url); // Hutool 可替换为其他 Http 工具
JSONObject json = JSONUtil.parseObj(response);
String openid = json.getStr("openid");
String sessionKey = json.getStr("session_key");
User user = userRepository.findByOpenid(openid)
.orElseGet(() -> {
User newUser = new User();
newUser.setOpenid(openid);
newUser.setCreateTime(LocalDateTime.now());
return userRepository.save(newUser);
});
String token = JwtUtils.generateToken(user.getId()); // 自行封装 JWT 工具类
return new LoginResponse(token, user.getId(), openid);
}
}💰 发起微信支付(JSAPI)
前端支付流程(微信小程序)
js
// pay.js
wx.request({
url: 'http://localhost:8080/wx/pay',
method: 'POST',
data: {
orderNo: 'ORDER12345',
amount: 100 // 单位:分
},
success: res => {
const payData = res.data.data;
wx.requestPayment({
...payData,
success(res) {
console.log('支付成功', res)
},
fail(err) {
console.log('支付失败', err)
}
})
}
})后端支付接口
java
@PostMapping("/wx/pay")
public ApiResponse<?> pay(@RequestBody PayRequest req) {
return ApiResponse.success(wxPayService.createOrder(req));
}微信支付核心逻辑(V3 JSAPI)
java
@Service
public class WxPayService {
@Autowired
private WxPayConfig config;
public Map<String, String> createOrder(PayRequest req) {
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
Map<String, Object> body = new HashMap<>();
body.put("appid", config.getAppId());
body.put("mchid", config.getMchId());
body.put("description", "订单支付");
body.put("out_trade_no", req.getOrderNo());
body.put("notify_url", config.getNotifyUrl());
Map<String, Object> amount = new HashMap<>();
amount.put("total", req.getAmount());
body.put("amount", amount);
Map<String, String> payer = new HashMap<>();
payer.put("openid", getOpenIdFromToken()); // 从 JWT token 中解析用户信息
body.put("payer", payer);
String result = WxPayUtils.v3Post(url, body, config);
JSONObject json = JSONUtil.parseObj(result);
String prepayId = json.getStr("prepay_id");
return WxPayUtils.buildPaySign(config.getAppId(), prepayId, config.getPrivateKey());
}
}💸 微信退款接口
java
@PostMapping("/wx/refund")
public ApiResponse<?> refund(@RequestBody RefundRequest req) {
return ApiResponse.success(wxPayService.refund(req));
}java
public Map<String, Object> refund(RefundRequest req) {
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
Map<String, Object> body = new HashMap<>();
body.put("out_trade_no", req.getOrderNo());
body.put("out_refund_no", req.getRefundNo());
body.put("reason", "用户取消订单");
body.put("notify_url", config.getRefundNotifyUrl());
Map<String, Object> amount = new HashMap<>();
amount.put("refund", req.getRefundAmount());
amount.put("total", req.getTotalAmount());
amount.put("currency", "CNY");
body.put("amount", amount);
String result = WxPayUtils.v3Post(url, body, config);
return JSONUtil.parseObj(result);
}🧠 工具类 WxPayUtils(简化封装)
你需要封装以下几个功能:
- ✅ v3 接口的签名和请求
- ✅ prepay_id 转为前端支付参数
- ✅ 解密回调数据(退款结果通知)
建议参考官方 v3 SDK 或使用轻量开源库如 wechatpay-apiv3
✅ 小结
| 功能 | 状态 | 备注 |
|---|---|---|
| 登录/注册 | ✅ | wx.login + 后端换取 openid |
| 发起支付 | ✅ | 支持 JSAPI |
| 退款处理 | ✅ | V3 接口 |
| 实时回调 | ✅ | 支付回调、退款回调要配置接口、解密、验签 |
微信工具类的设计参考
下面是一个完整实践中常见的 WxPayUtils 工具类设计,主要用于:
- 微信支付签名生成
- 统一下单参数构建
- 回调通知签名验证
- XML/Map 转换处理(微信支付 V2)
(注:若使用微信支付 V3,这部分会不同,需要使用微信提供的 SDK 和证书加密)
1. 依赖配置
xml
<!-- fastxml(xml 和 map 转换) -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- apache commons-lang -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- hutool 工具包(可选) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>2. 工具类设计示例
java
package com.example.utils;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
/**
* 微信支付工具类(适用于微信支付V2)
*/
public class WxPayUtils {
private static final XmlMapper xmlMapper = new XmlMapper();
/**
* 生成带签名的请求参数(用于统一下单)
*/
public static Map<String, String> buildSignedRequest(Map<String, String> params, String apiKey) {
params.put("nonce_str", RandomUtil.randomString(32));
String sign = createSign(params, apiKey);
params.put("sign", sign);
return params;
}
/**
* 创建签名(MD5方式)
*/
public static String createSign(Map<String, String> data, String apiKey) {
// 1. 参数排序
SortedMap<String, String> sortedMap = new TreeMap<>(data);
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
if (StringUtils.isNotBlank(entry.getValue()) && !"sign".equals(entry.getKey())) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
sb.append("key=").append(apiKey);
// 2. MD5加密并转大写
return DigestUtil.md5Hex(sb.toString()).toUpperCase();
}
/**
* 验证回调参数签名是否正确
*/
public static boolean isSignValid(Map<String, String> data, String apiKey) {
if (!data.containsKey("sign")) return false;
String sign = data.get("sign");
return sign.equals(createSign(data, apiKey));
}
/**
* Map -> XML
*/
public static String mapToXml(Map<String, String> data) {
try {
return xmlMapper.writeValueAsString(data);
} catch (IOException e) {
throw new RuntimeException("mapToXml error", e);
}
}
/**
* XML -> Map
*/
public static Map<String, String> xmlToMap(String xml) {
try {
return xmlMapper.readValue(xml, Map.class);
} catch (IOException e) {
throw new RuntimeException("xmlToMap error", e);
}
}
/**
* 构造基础请求参数
*/
public static Map<String, String> baseRequest(String appId, String mchId, String notifyUrl) {
Map<String, String> map = new HashMap<>();
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("notify_url", notifyUrl);
map.put("nonce_str", RandomUtil.randomString(32));
map.put("sign_type", "MD5");
return map;
}
}3. 用法举例(统一下单)
java
Map<String, String> params = WxPayUtils.baseRequest(APPID, MCHID, NOTIFY_URL);
params.put("body", "测试商品");
params.put("out_trade_no", orderNo);
params.put("total_fee", "1"); // 单位:分
params.put("spbill_create_ip", "127.0.0.1");
params.put("trade_type", "JSAPI");
params.put("openid", userOpenId);
// 签名 & 发送
Map<String, String> signedParams = WxPayUtils.buildSignedRequest(params, API_KEY);
String requestXml = WxPayUtils.mapToXml(signedParams);
String resultXml = HttpUtils.post(WX_PAY_UNIFIED_ORDER_URL, requestXml);
// 响应处理
Map<String, String> resultMap = WxPayUtils.xmlToMap(resultXml);
if (!"SUCCESS".equals(resultMap.get("return_code")) || !"SUCCESS".equals(resultMap.get("result_code"))) {
throw new RuntimeException("微信统一下单失败: " + resultMap.get("return_msg"));
}4. 常见回调验签处理
java
@PostMapping("/wxpay/notify")
public String handleWxPayNotify(HttpServletRequest request) throws IOException {
String xml = IOUtils.toString(request.getInputStream(), "UTF-8");
Map<String, String> notifyData = WxPayUtils.xmlToMap(xml);
if (!"SUCCESS".equals(notifyData.get("result_code"))) {
return WxPayUtils.mapToXml(Map.of("return_code", "FAIL", "return_msg", "支付失败"));
}
boolean valid = WxPayUtils.isSignValid(notifyData, API_KEY);
if (!valid) {
return WxPayUtils.mapToXml(Map.of("return_code", "FAIL", "return_msg", "签名无效"));
}
// 处理订单状态更新业务...
return WxPayUtils.mapToXml(Map.of("return_code", "SUCCESS", "return_msg", "OK"));
}如果用的是微信支付 V3(更安全,推荐),会涉及证书签名、V3 SDK、HTTP 请求头校验机制等,逻辑复杂不少。如果项目后续有这个方向的需求,也可以专门构建一版 V3 专用工具类。
