一、微信登录

微信登录主要包含两种场景:

  1. 小程序登录
  2. PC(公众号)扫码登录

1. 小程序登录

思路: 使用 weixin-java sdk实现小程序登录

1
2
3
4
5
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.6.0</version>
</dependency>

使用此sdk需要先进行配置

yml文件:

1
2
3
4
wx:
miniapp:
appId: # 小程序微信公众平台appId
secret: # 小程序微信公众平台api秘钥

配置类:

1
2
3
4
5
6
7
8
9
10
11
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxConfigProperties {
private String appId;
private String secret;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class WxConfigOperator {

@Autowired
private WxConfigProperties wxConfigProperties;

@Bean
public WxMaService wxMaService() {

//微信小程序id和秘钥
WxMaDefaultConfigImpl wxMaConfig = new WxMaDefaultConfigImpl();
wxMaConfig.setAppid(wxConfigProperties.getAppId());
wxMaConfig.setSecret(wxConfigProperties.getSecret());

WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(wxMaConfig);
return service;
}
}
  1. 前端调用获取 code:
    前端通过 wx.login 接口获取临时登录凭证 code,并传递给后台。

  2. 调用微信接口获取会话信息:
    后台利用 weixin‐java‐mp SDK 的 WxMaService.getUserService().getSessionInfo(code) 方法,根据 code 调用微信接口获取 session_keyopenid(以及可能的 unionid)。

    getSessionInfo调用了BaseWxMaServiceImpl 中的 jsCode2SessionInfo(jsCode)方法

    代码:

    将上面配置的 appid 和 secret 设置到请求参数中,并将前端传来的 jsCode 设置到请求参数中,之后请求微信官方的接口获取 session_key 与 openid

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException {
    WxMaConfig config = this.getWxMaConfig();
    Map<String, String> params = new HashMap(8);
    params.put("appid", config.getAppid());
    params.put("secret", config.getSecret());
    params.put("js_code", jsCode);
    params.put("grant_type", "authorization_code");
    String result = this.get("https://api.weixin.qq.com/sns/jscode2session", Joiner.on("&").withKeyValueSeparator("=").join(params));
    return WxMaJscode2SessionResult.fromJson(result);
    }
  3. 业务处理:

    根据 openid 查询数据库,判断用户是否已注册;

    • 若不存在,则注册新用户;
    • 若已存在,则更新用户最后登录时间或其他必要信息。
  4. 返回登录结果:
    最后将用户信息和必要的认证数据返回给前端。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.WxMaInMemoryConfig;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class WxMiniProgramLoginService {

private WxMaService wxMaService;

/**
* 小程序登录:通过 code 获取微信会话信息,并处理业务逻辑(如用户注册或登录)
*
* @param code 前端传递的临时登录凭证
* @return 微信会话结果对象,包含 openid、session_key 等信息
* @throws Exception 若调用微信接口失败,则抛出异常
*/
public WxMaJscode2SessionResult login(String code) throws Exception {
// Step 1: 调用微信接口获取 session_key 和 openid
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(code);

// Step 2: 根据 session.getOpenid() 进行业务处理,例如:
// User user = userService.findByOpenid(session.getOpenid());
// if (user == null) {
// user = userService.registerNewUser(session.getOpenid());
// } else {
// userService.updateLastLoginTime(user);
// }
// 此处仅返回微信接口结果,业务逻辑根据项目情况进行扩展

return session;
}
}

想要token可以自定义一个VO返回用户信息和token,在业务逻辑后 根据 openid 或者 用户信息生成 token

关于获取用户信息:

需要前端调用 wx.getUserInfo 接口获取 encryptedDataiv ,并传递给后台。

之后后端调用 wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, ivStr)接口,获取用户信息。

2.PC登录

思路:使用 weixin-java sdk实现PC扫码登录

1
2
3
4
5
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.6.0</version>
</dependency>

yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
wx:
mp:
useRedis: false
redisConfig:
host: 127.0.0.1
port: 6379
configs:
- appId: 1111 # 第一个公众号的appid
secret: 1111 # 公众号的appsecret
token: 111 # 接口配置里的Token值
aesKey: 111 # 接口配置里的EncodingAESKey值
- appId: 2222 # 第二个公众号的appid,以下同上
secret: 1111
token: 111
aesKey: 111

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import com.github.binarywang.demo.wx.mp.utils.JsonUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@Data
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
/**
* 是否使用redis存储access token
*/
private boolean useRedis;

/**
* redis 配置
*/
private RedisConfig redisConfig;

@Data
public static class RedisConfig {
/**
* redis服务器 主机地址
*/
private String host;

/**
* redis服务器 端口号
*/
private Integer port;

/**
* redis服务器 密码
*/
private String password;

/**
* redis 服务连接超时时间
*/
private Integer timeout;
}

/**
* 多个公众号配置信息
*/
private List<MpConfig> configs;

@Data
public static class MpConfig {
/**
* 设置微信公众号的appid
*/
private String appId;

/**
* 设置微信公众号的app secret
*/
private String secret;

/**
* 设置微信公众号的token
*/
private String token;

/**
* 设置微信公众号的EncodingAESKey
*/
private String aesKey;
}

@Override
public String toString() {
return JsonUtils.toJson(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import com.github.binarywang.demo.wx.mp.handler.*;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.common.redis.JedisWxRedisOps;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.List;
import java.util.stream.Collectors;

import static me.chanjar.weixin.common.api.WxConsts.EventType;
import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY;

@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpConfiguration {
private final WxMpProperties properties;

@Bean
public WxMpService wxMpService() {
final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();
if (configs == null) {
throw new RuntimeException("配置错误!");
}

WxMpService service = new WxMpServiceImpl();
service.setMultiConfigStorages(configs
.stream().map(a -> {
WxMpDefaultConfigImpl configStorage;
if (this.properties.isUseRedis()) {
final WxMpProperties.RedisConfig redisConfig = this.properties.getRedisConfig();
JedisPoolConfig poolConfig = new JedisPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
redisConfig.getTimeout(), redisConfig.getPassword());
configStorage = new WxMpRedisConfigImpl(new JedisWxRedisOps(jedisPool), a.getAppId());
} else {
configStorage = new WxMpDefaultConfigImpl();
}

configStorage.setAppId(a.getAppId());
configStorage.setSecret(a.getSecret());
configStorage.setToken(a.getToken());
configStorage.setAesKey(a.getAesKey());
return configStorage;
}).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
return service;
}

}
  1. 登录二维码url生成
    调用 wxMpService.buildQrConnectUrl(redirectUri, scope, state)方法,生成url。将url传递返回前端,前端生成二维码或者后台生成二维码返回前端

  2. 微信扫码与授权
    用户使用微信扫描二维码后,会跳转到微信的授权页面,用户确认后微信会将 code 参数传回你在微信公众号后台配置的回调 URL。你需要在该回调接口中调用微信接口获取 openidsession_key,并完成用户信息的查询或注册操作。
    在回调接口中调用:

    1
    2
    3
    4
    // 获取accessToken和openid
    WxOAuth2AccessToken accessToken = wxService.getOAuth2Service().getAccessToken(code);
    // 获取用户信息(看需求选择是否调用)
    WxOAuth2UserInfo user = wxService.getOAuth2Service().getUserInfo(accessToken, null);
  3. 状态更新
    后台在完成用户授权和业务处理后,更新数据库中用户信息或最近登录时间等

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@GetMapping("/generateQrUrl")
public String generateQrUrl() {
// 回调地址
String redirectUri = "http://yourdomain.com/oauth_callback";
String scope = "snsapi_login"; // PC扫码登录建议使用此 scope
String state = "STATE"; // 用于防止CSRF攻击,可以自定义
String qrConnectUrl = wxMpService.buildQrConnectUrl(redirectUri, scope, state);
return qrConnectUrl;
}

@RequestMapping("/greet")
public String greetUser(@RequestParam String code) {
// 调用微信接口获取openid和accessToken
WxOAuth2AccessToken accessToken = wxService.getOAuth2Service().getAccessToken(code);
// 根据 accessToken.getOpenid() 进行业务处理,例如:
// User user = userService.findByOpenid(accessToken.getOpenid());
// if (user == null) {
// 可以通过accessToken.getAccessToken()获取accessToken查询用户信息,在注册时补全信息
WxOAuth2UserInfo user = wxService.getOAuth2Service().getUserInfo(accessToken, null);
// user = userService.registerNewUser(accessToken.getOpenid());
// } else {
// userService.updateLastLoginTime(user);
// }

// 按照需求自行设计返回体
return "greet_user";
}

二、微信支付

1. 下单

思路:使用 weixin-java sdk实现 统一下单接口

1
2
3
4
5
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.6.0</version>
</dependency>

使用此sdk需要先进行配置

yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
wx:
pay:
appId: #微信公众号或者小程序等的appid (V3商户模式需要)
mchId: #微信支付商户号 (V3商户模式需要)
mchKey: #微信支付商户密钥
subAppId: #服务商模式下的子商户公众账号ID
subMchId: #服务商模式下的子商户号
keyPath: # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
apiV3Key: # apiV3 秘钥值 (V3商户模式需要)
certSerialNo: # apiV3 证书序列号值 (V3商户模式需要)
privateCertPath: # apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径. (V3商户模式需要)
privateKeyPath: # apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径. (V3商户模式需要)
publicKeyPath: # 微信支付公钥,pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径. (V3商户模式需要)
publicKeyId: # 微信支付公钥ID (V3商户模式需要)

配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.github.binarywang.demo.wx.pay.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {
/**
* 设置微信公众号或者小程序等的appid
* (V3商户模式需要)
*/
private String appId;

/**
* 微信支付商户号
* (V3商户模式需要)
*/
private String mchId;

/**
* 微信支付商户密钥
*/
private String mchKey;

/**
* 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
*/
private String subAppId;

/**
* 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
*/
private String subMchId;

/**
* apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
*/
private String keyPath;

/**
* apiV3 秘钥值
* (V3商户模式需要)
*/
private String apiV3Key;

/**
* apiV3 证书序列号值
* (V3商户模式需要)
*/
private String certSerialNo;

/**
* apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.
* (V3商户模式需要,这里用的是文件路径,可以用其它base64编码或字节数组参数替代)
*/
private String privateCertPath;

/**
* apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
* (V3商户模式需要,这里用的是文件路径,可以用其它base64编码或字节数组参数替代)
*/
private String privateKeyPath;

/**
* 微信支付公钥,pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
* (V3商户模式需要,这里用的是文件路径,可以用其它base64编码或字节数组参数替代,2024.08后的新商户验签需要用此公钥)
*/
private String publicKeyPath;

/**
* 微信支付公钥ID
* (V3商户模式需要,2024.08后的新商户验签需要用此公钥ID)
*/
private String publicKeyId;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.github.binarywang.demo.wx.pay.config;

import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass(WxPayService.class)
@EnableConfigurationProperties(WxPayProperties.class)
@AllArgsConstructor
public class WxPayConfiguration {
private WxPayProperties properties;

@Bean
@ConditionalOnMissingBean
public WxPayService wxService() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));//V3商户模式需要
payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));//V3商户模式需要
payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiV3Key()));//V3商户模式需要
payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo()));//V3商户模式需要
payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));//V3商户模式需要
payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));//V3商户模式需要
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));//V3商户模式需要
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));//V3商户模式需要

// 可以指定是否使用沙箱环境
payConfig.setUseSandboxEnv(false);

WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}

}
  1. 前端提交订单信息

    前端根据用户选购的商品生成订单(包含订单号、金额、商品描述、支付类型、用户openid(JSAPI支付时必传)、客户端IP等信息),将这些订单数据传递给后台下单接口。
    接口请求参数参考:https://pay.weixin.qq.com/doc/v2/merchant/4011935214
    一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置已经设置)

  2. 调用统一下单接口,组装支付参数

    后台利用 weixin‐java‐pay SDK 的 wxPayService.createOrder(request)方法,调用统一下单接口并组装支付参数。

    wxPayService.createOrder(request)做了以下操作:

    1. 统一下单

      接收到订单数据后,构造一个 WxPayUnifiedOrderRequest 对象。

      接着调用 wxPayService.unifiedOrder(request) 方法,该方法会:

      1. 对请求参数进行校验和签名;
      2. 将请求参数转换为XML格式后通过HTTP POST请求调用微信支付统一下单接口;
      3. 解析返回的XML数据为 WxPayUnifiedOrderResult 对象,并检查返回结果。如果返回中包含预支付交易会话标识(prepay_id),则下单成功,否则抛出异常。
    2. 组装支付参数

      再根据订单请求中的 tradeType 不同(如JSAPI、APP、NATIVE、MWEB),调用 createOrder(WxPayUnifiedOrderRequest request) 方法会进一步根据返回的prepay_id组装出支付参数包。例如:

      1. 对于 JSAPI 支付,返回 WxPayMpOrderResult,其中包括 appId、timeStamp、nonceStr、package(格式为 “prepay_id=” + prepay_id)、signType,以及根据这些参数生成的 paySign;
      2. 对于 APP 支付,返回 WxPayAppOrderResult,其中封装了所有客户端调起微信支付所需的参数。
      3. 对于 NATIVE 支付(扫码支付),返回 WxPayNativeOrderResult,其中包含二维码生成所需的 CodeURL;
      4. 对于 MWEB 支付(H5支付),返回 WxPayMwebOrderResult,其中包含支付链接。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    public <T> T createOrder(WxPayUnifiedOrderRequest request) throws WxPayException {
    WxPayUnifiedOrderResult unifiedOrderResult = this.unifiedOrder(request);
    String prepayId = unifiedOrderResult.getPrepayId();
    if (StringUtils.isBlank(prepayId)) {
    throw new WxPayException(String.format("无法获取prepay id,错误代码: '%s',信息:%s。", unifiedOrderResult.getErrCode(), unifiedOrderResult.getErrCodeDes()));
    } else {
    String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
    String nonceStr = unifiedOrderResult.getNonceStr();
    String signType;
    switch (request.getTradeType()) {
    case "MWEB":
    return new WxPayMwebOrderResult(unifiedOrderResult.getMwebUrl());
    case "NATIVE":
    return new WxPayNativeOrderResult(unifiedOrderResult.getCodeURL());
    case "APP":
    signType = unifiedOrderResult.getAppid();
    if (StringUtils.isNotEmpty(unifiedOrderResult.getSubAppId())) {
    signType = unifiedOrderResult.getSubAppId();
    }

    Map<String, String> configMap = new HashMap(8);
    String partnerId = unifiedOrderResult.getMchId();
    if (StringUtils.isNotEmpty(unifiedOrderResult.getSubMchId())) {
    partnerId = unifiedOrderResult.getSubMchId();
    }

    configMap.put("prepayid", prepayId);
    configMap.put("partnerid", partnerId);
    String packageValue = "Sign=WXPay";
    configMap.put("package", packageValue);
    configMap.put("timestamp", timestamp);
    configMap.put("noncestr", nonceStr);
    configMap.put("appid", signType);
    WxPayAppOrderResult result = WxPayAppOrderResult.builder().sign(SignUtils.createSign(configMap, request.getSignType(), this.getConfig().getMchKey(), (String[])null)).prepayId(prepayId).partnerId(partnerId).appId(signType).packageValue(packageValue).timeStamp(timestamp).nonceStr(nonceStr).build();
    return result;
    case "JSAPI":
    signType = request.getSignType();
    if (signType == null) {
    signType = "MD5";
    }

    String appid = unifiedOrderResult.getAppid();
    if (StringUtils.isNotEmpty(unifiedOrderResult.getSubAppId())) {
    appid = unifiedOrderResult.getSubAppId();
    }

    WxPayMpOrderResult payResult = WxPayMpOrderResult.builder().appId(appid).timeStamp(timestamp).nonceStr(nonceStr).packageValue("prepay_id=" + prepayId).signType(signType).build();
    payResult.setPaySign(SignUtils.createSign(payResult, signType, this.getConfig().getMchKey(), (String[])null));
    return payResult;
    default:
    throw new WxPayException("该交易类型暂不支持");
    }
    }
    }
  3. 返回支付参数给前端
    后端将组装好的支付参数包返回给前端,前端据此调用微信支付SDK完成支付流程。如果需要,也可以自定义VO将用户信息和token(如果与支付相关)一并返回。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.github.binarywang.wxpay.bean.coupon.*;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;

@AllArgsConstructor
public class WechatPayService {

private WxPayService wxPayService;

/**
* 调用统一下单接口,并组装生成支付所需参数对象.
*
* @param request 统一下单请求参数
* @param <T> 请使用{@link com.github.binarywang.wxpay.bean.order}包下的类
* @return 返回 {@link com.github.binarywang.wxpay.bean.order}包下的类对象
*/
public <T> T createOrder(WxPayUnifiedOrderRequest request) throws WxPayException {
// 判断商品信息、数量、金额,用户信息等操作
T t = wxPayService.createOrder(request);
// 根据 request 进行业务操作, 例如保存订单信息到数据库中
return t;
}
}

2. 支付回调通知处理

思路:使用 weixin-java sdk实现 支付回调通知

配置同上

  1. 接收XML数据
    通过 @RequestBody 接收微信支付平台发送过来的 XML 数据。
  2. 解析与签名校验
    调用 wxPayService.parseOrderNotifyResult(xmlData) 方法,将 XML 转换为 WxPayOrderNotifyResult 对象。该方法内部会根据配置的微信支付参数进行签名校验、数据校验,如果校验不通过则抛出 WxPayException
  3. 业务处理
    解析成功后,你可以根据 notifyResult 中的 outTradeNotransaction_idtrade_typetotal_fee 等字段进行订单状态更新、支付记录保存等业务操作。
  4. 返回响应
    无论是成功还是失败,回调接口都必须返回微信规定的响应格式。这里我们使用 WxPayNotifyResponse.success("成功")WxPayNotifyResponse.fail("处理失败") 来生成响应字符串。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 微信支付订单回调通知处理接口
*
* 微信支付后台会将支付结果以 XML 格式 POST 到你设置的 notifyUrl,
* 这里我们调用 WxPayService.parseOrderNotifyResult 方法解析通知,
* 验证签名后根据业务需求处理订单状态更新等逻辑。
*
* @param xmlData 微信支付异步通知的 XML 数据
* @return 返回给微信的响应结果,格式必须符合微信要求
*/
@PostMapping("/order")
public String processOrderNotify(@RequestBody String xmlData) {
try {
// 解析支付通知并自动完成签名校验、数据校验
WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlData);
log.info("微信支付回调解析成功:{}", notifyResult);

// TODO:根据notifyResult中的订单号、支付状态等字段完成业务逻辑处理,
// 如更新订单状态、记录支付流水等

// 通知微信支付平台处理成功
return WxPayNotifyResponse.success("成功");
} catch (WxPayException e) {
log.error("微信支付回调处理失败:{}", e.getMessage(), e);
// 若处理失败,也需返回标准格式的错误响应
return WxPayNotifyResponse.fail("处理失败");
}
}