Skip to content

本文将详细讲讲微信小程序的注册登录 + 微信支付的前后端完整流程,涵盖所有参数传递和配合细节,按模块一步步拆解,非常适合 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(保存用户、订单等)
  • 已获取:微信小程序 appIdsecret
  • 已配置:微信商户平台的 mchidapiV3Key商户证书

📌 项目结构简述

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 专用工具类。

所有文章版权皆归博主所有,仅供学习参考。