感觉本篇对你有帮助可以关注一下我的微信公众号(深入浅出谈java)

会不定期更新知识!!!

一、概述

在移动互联网时代,支付功能已成为应用开发的核心能力之一。本文将以 UniApp前端+Java后端技术栈为例,系统解析微信支付功能的设计实现与关键实践,为开发者提供从技术架构到安全防护的全景视角。

微信支付功能是跨平台应用(UniApp前端 + Java后端)与微信支付系统对接的核心模块,实现用户从下单到支付完成的闭环流程。支持多种支付场景(如APP支付、小程序支付、H5支付),确保交易安全、实时性和数据一致性

对于支付系统,公司一般会对其进行独立,确保服务的安全、可靠、稳定。因此支付系统是现代互联网的关键核心系统

1.1:微信支付流程图

大致如下:流程图中和流程步骤 描述的2、3 步进行了调换,不受影响,但是建议可以先创建订单

1.2:流程步骤

1、用户提交订单请求

  • 行为主体:用户(通过UniApp前端操作)

  • 动作描述:用户在UniApp中选择商品或服务,点击支付按钮,前端将订单信息(如商品ID、数量、金额等)发送至Java后端。

2、生成业务订单

  • 行为主体:Java后端

  • 动作描述

    1. 后端接收订单请求后,验证数据合法性(如金额、商品库存等)。

    2. 在数据库中生成唯一业务订单号(out_trade_no),并记录订单状态为「待支付」

3、调用微信统一下单API

  • 行为主体:Java后端 → 微信支付平台

  • 动作描述

    1. 后端构造统一下单请求参数(包括商户号、订单号、金额、回调地址等)。

    2. 使用商户API密钥生成签名(保障请求安全性)。

    3. 向微信支付平台发送HTTP请求,调用统一下单接口(URL: https://api.mch.weixin.qq.com/pay/unifiedorder)。

4、接收预支付交易单响应

  • 行为主体:微信支付平台 → Java后端

  • 动作描述

    1. 微信验证请求参数和签名,确认无误后生成预支付交易单。

    2. 返回XML格式响应数据,包含关键字段:

      • prepay_id(预支付交易标识,用于后续支付)

      • return_code(通信状态码,如SUCCESS/FAIL)

      • result_code(业务结果码,如SUCCESS/FAIL)

5、返回支付参数至前端

  • 行为主体:Java后端 → UniApp前端
  • 动作描述
    1. 后端解析微信返回的prepay_id,重新构造前端支付参数(需二次签名)。
    2. 返回JSON数据给UniApp,包含:
      • appId(微信应用ID)
      • timeStamp(时间戳)
      • nonceStr(随机字符串)
      • package(固定值Sign=WXPay
      • signType(签名类型,通常为MD5或HMAC-SHA256)
      • paySign(最终支付签名)

6、调起微信支付界面

  • 行为主体:用户(UniApp前端) → 微信客户端

  • 动作描述

    1. UniApp通过uni.requestPayment API,传入后端返回的支付参数。

    2. 微信客户端(APP/小程序)根据参数拉起支付界面,用户确认金额并输入密码。

7、用户完成支付

  • 行为主体:微信支付平台 → 用户
  • 动作描述
    1. 微信验证支付密码和账户余额,扣款成功后,向用户展示支付结果页面(成功/失败)。

8、异步通知支付结果

  • 行为主体:微信支付平台 → Java后端
  • 动作描述
    1. 微信通过POST请求调用后端预设的notify_url(需公网可访问)。
    2. 推送XML格式回调数据,包含:
      • out_trade_no(商户订单号)
      • transaction_id(微信支付单号)
      • total_fee(实际支付金额)
      • result_code(支付结果,如SUCCESS/FAIL)

9、处理回调并响应微信

  • 行为主体:Java后端 → 微信支付平台
  • 动作描述
    1. 后端接收回调数据后:

      • 验证签名防止伪造请求
      • 检查订单金额与业务系统是否一致
      • 更新订单状态为「已支付」(需做幂等处理,避免重复更新)
    2. 返回XML响应(必须包含<return_code><![CDATA[SUCCESS]]></return_code>),告知微信已正确处理。

10、通知前端最终结果

  • 行为主体:Java后端 → UniApp前端
  • 动作描述
    1. 若前端未实时感知支付结果(如用户关闭页面),可通过两种方式同步:

      • 轮询查询:前端定期请求后端订单状态接口
      • WebSocket推送:后端主动推送支付结果
    2. 更新前端界面显示支付成功/失败状态。

二、账号准备工作

2.1:申请微信小程序账号

1、开发小程序的第一步,你需要拥有一个小程序账号,因此先申请小程序账号

小程序注册地址:小程序

2、信息填好,进行注册,就会产生AppID、AppSecret

小程序的 AppID 相当于小程序平台的一个身份证,后续你会在很多地方要用到 AppID (注意这里要区别于服务号或订阅号的 AppID)

2.2:微信支付 官网开通商户支付能力

微信支付官网:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式

1、找到接入微信支付,进行绑定注册

注册需要营业执照、法人信息,按照要求填写即可

2、注册微信支付商户号

3、填写必要信息进行注册

4、申请证书和AIPv3秘钥,v2现在有淘汰趋势,不需要申请v2,直接v3走起。

5、下载和保存好 秘钥及证书(PS:一定要好好保存)

6、获取商户号

三、接入实现

3.1:服务端代码

1、导入maven依赖

        <!-- 微信支付API -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.20</version>
<scope>compile</scope>
</dependency>
<!-- 微信支付SDK(官方或第三方封装) -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>

2、配置商户信息

# 微信支付配置
pay:
appId: xxx #应用id
mchId: xxx #商户id
notifyUrl: https://服务器ip或对应域名/wxpay/weixin/callback #支付回调地址

3、实体类代码

这个部分是需要用到的实体类代码

WeChatPay:微信支付预下单实体类

@Data
@Accessors(chain = true)
public class WeChatPay { /**
* 返回状态码 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
*/
public String return_code; /**
* 返回信息 当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
*/
private String return_msg; /**
* 公众账号ID 调用接口提交的公众账号ID
*/
private String appid; /**
* 商户号 调用接口提交的商户号
*/
private String mch_id; /**
* api密钥 详见:https://pay.weixin.qq.com/index.php/extend/employee
*/
private String api_key; /**
* 设备号 自定义参数,可以为请求支付的终端设备号等
*/
private String device_info; /**
* 随机字符串 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的随机字符串
*/
private String nonce_str; /**
* 签名 微信返回的签名值,详见签名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
*/
private String sign; /**
* 签名类型
*/
private String sign_type; /**
* 业务结果 SUCCESS SUCCESS/FAIL
*/
private String result_code; /**
* 错误代码 当result_code为FAIL时返回错误代码,详细参见下文错误列表
*/
private String err_code; /**
* 错误代码描述 当result_code为FAIL时返回错误描述,详细参见下文错误列表
*/
private String err_code_des; /**
* 交易类型 JSAPI JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 说明详见;https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
*/
private String trade_type; /**
* 预支付交易会话标识 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
*/
private String prepay_id; /**
* 二维码链接 weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 trade_type=NATIVE时有返回,此url用于生成支付二维码,然后提供给用户进行扫码支付。注意:code_url的值并非固定,使用时按照URL格式转成二维码即可
*/
private String code_url; /**
* 商品描述 商品简单描述,该字段请按照规范传递,具体请见 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
*/
private String body; /**
* 商家订单号 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
*/
private String out_trade_no; /**
* 标价金额 订单总金额,单位为分,详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
*/
private String total_fee; /**
* 终端IP 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
*/
private String spbill_create_ip; /**
* 通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
*/
private String notify_url; /**
* 子商户号 sub_mch_id 非必填(商户不需要传入,服务商模式才需要传入) 微信支付分配的子商户号
*/
private String sub_mch_id; /**
* 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
*/
private String attach; /**
* 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
*/
private String out_refund_no; /**
* 退款总金额,单位为分,只能为整数,可部分退款。详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=4_2
*/
private String refund_fee; /**
* 退款原因 若商户传入,会在下发给用户的退款消息中体现退款原因 注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
*/
private String refund_desc; /**
* 交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟
*/
private String time_expire; /**
* 用户标识 trade_type=JSAPI,此参数必传,用户在主商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid。下单前需要调用【网页授权获取用户信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口获取到用户的Openid。
*/
private String openid; /**
* 时间戳
*/
private String time_stamp; /**
* 会员类型
*/
private String memberShipType; }

PayParameterVO:微信支付,商品信息对象

@Data
public class PayParameterVO { /** 商品价格(单位:分) */
private String price; /** 微信openId */
private String wxOpenId; /** 商品描述 */
private String goodsTitle; }

OrderReturnInfo:预下单成功之后返回结果对象

@Data
public class OrderReturnInfo { /** 返回状态码 */
private String return_code; /** 返回信息 */
private String return_msg; /** 业务结果 */
private String result_code; /** 小程序appID */
private String appid; /** 商户号 */
private String mch_id; /** 随机字符串 */
private String nonce_str; /** 签名 */
private String sign; /** 预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时 */
private String prepay_id; /** 交易类型 */
private String trade_type;
}

QueryReturnInfo:查询订单返回实体类

@Data
public class QueryReturnInfo { /** 返回状态码 */
private String return_code; /** 返回信息 */
private String return_msg; /** 业务结果 */
private String result_code; /** 错误代码 */
private String err_code; /** 错误代码描述 */
private String err_code_des; /** 小程序appID */
private String appid; /** 商户号 */
private String mch_id; /** 随机字符串 */
private String nonce_str; /** 签名 */
private String sign; /** 签名类型 */
private String sign_type; private String prepay_id; /** 交易类型 */
private String trade_type; /** 设备号 */
private String device_info; /** 用户标识 */
private String openid; /** 是否关注公众账号 */
private String is_subscribe; private String trade_state; /** 付款银行 */
private String bank_type; /** 订单金额 */
private int total_fee; /** 应结订单金额 */
private int settlement_total_fee; /** 货币种类 */
private String fee_type; /** 现金支付金额 */
private int cash_fee; /** 现金支付货币类型 */
private String cash_fee_type; /** 总代金券金额 */
private int coupon_fee; /** 代金券使用数量 */
private int coupon_count; /** 代金券类型 */
private String coupon_type_$n; /** 代金券ID */
private String coupon_id_$n; /** 单个代金券支付金额 */
private String coupon_fee_$n; /** 微信支付订单号 */
private String transaction_id; /** 商户订单号 */
private String out_trade_no; /** 支付完成时间 */
private String time_end; private String trade_state_desc; /** 商家数据包 */
private String attach;
}

SignInfo:签名实体类

@Data
public class SignInfo { private String appId;//小程序ID private String timeStamp;//时间戳 private String nonceStr;//随机串 @XStreamAlias("package")
private String repay_id; private String signType;//签名方式
public void setSignType(String signType) {
this.signType = signType;
}
}

4、工具类代码

SignUtils:签名工具类

@Slf4j
public class SignUtils { /**
* 签名算法
*
* @param o 要参与签名的数据对象
* @return 签名
* @throws IllegalAccessException
*/
public static String getSign(Object o) throws IllegalAccessException {
ArrayList<String> list = new ArrayList<String>();
Class cls = o.getClass();
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
if (f.get(o) != null && f.get(o) != "") {
String name = f.getName();
XStreamAlias anno = f.getAnnotation(XStreamAlias.class);
if (anno != null) {
name = anno.value();
}
list.add(name + "=" + f.get(o) + "&");
}
}
int size = list.size();
String[] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + Configure.getKey();
log.info("签名数据:" + result);
result = MD5.MD5Encode(result).toUpperCase();
return result;
} public static String getSign(Map<String, Object> map) {
ArrayList<String> list = new ArrayList<String>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() != "") {
list.add(entry.getKey() + "=" + entry.getValue() + "&");
}
}
int size = list.size();
String[] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + Configure.getKey();
//Util.log("Sign Before MD5:" + result);
result = MD5.MD5Encode(result).toUpperCase();
//Util.log("Sign Result:" + result);
return result;
}
}

MD5:MD5 加密工具类

public class MD5 {
private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "a", "b", "c", "d", "e", "f"}; /**
* 转换字节数组为16进制字串
*
* @param b 字节数组
* @return 16进制字串
*/
public static String byteArrayToHexString(byte[] b) {
StringBuilder resultSb = new StringBuilder();
for (byte aB : b) {
resultSb.append(byteToHexString(aB));
}
return resultSb.toString();
} /**
* 转换byte到16进制
*
* @param b 要转换的byte
* @return 16进制格式
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0) {
n = 256 + n;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
} /**
* MD5编码
*
* @param origin 原始字符串
* @return 经过MD5加密之后的结果
*/
public static String MD5Encode(String origin) {
String resultString = null;
try {
resultString = origin;
MessageDigest md = MessageDigest.getInstance("MD5");
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
} catch (Exception e) {
e.printStackTrace();
}
return resultString;
}
}

MapToObject:map 转化对象工具类

public class MapToObject {

    /**
* Map数据转为java对象
* @param map map数据
* @param targetType 对象类型
* @return
* @param <T>
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static <T> T convertMapToObject(Map<String, String> map, Class<T> targetType) throws IllegalAccessException, InstantiationException {
T targetObject = targetType.newInstance(); for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue(); try {
// 使用反射获取字段
Field field = targetType.getDeclaredField(key); // 设置字段可访问(如果是私有字段)
field.setAccessible(true); // 获取字段的类型
Class<?> fieldType = field.getType(); // 将字符串值转换为字段类型
Object convertedValue = convertStringToType(value, fieldType); // 设置字段的值
field.set(targetObject, convertedValue);
} catch (NoSuchFieldException e) {
// 处理字段不存在的异常
e.printStackTrace();
}
} return targetObject;
} private static Object convertStringToType(String value, Class<?> targetType) {
if (targetType == int.class || targetType == Integer.class) {
return Integer.parseInt(value);
}
// 添加其他可能的类型转换逻辑,例如 double、float、Date 等 // 默认情况下,返回字符串值
return value;
}
}

HttpRequest:请求工具类

public class HttpRequest {

    //连接超时时间,默认10秒
private static final int socketTimeout = 10000; //传输超时时间,默认30秒
private static final int connectTimeout = 30000; /**
* post请求
*
* @throws IOException
* @throws ClientProtocolException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws KeyManagementException
* @throws UnrecoverableKeyException
*/
public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException { HttpPost httpPost = new HttpPost(url);
//解决XStream对出现双下划线的bug
XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
xStreamForRequestPostData.alias("xml", xmlObj.getClass());
//将要提交给API的数据对象转换成XML格式数据Post给API
String postDataXML = xStreamForRequestPostData.toXML(xmlObj); //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity); //设置请求器的配置
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
httpPost.setConfig(requestConfig); HttpClient httpClient = HttpClients.createDefault();
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, "UTF-8");
return result;
} /**
* 自定义证书管理器,信任所有证书
*
* @author pc
*/
public static class MyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException { } @Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException { } @Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
} }

5、配置类代码

WxPayConfig:微信支付配置

@Data
@Component
@Configuration
@ConfigurationProperties(prefix = "pay")
public class WxPayConfig { /**
* 微信小程序appid
*/
private String appId; /**
* 小程序设置的API v2密钥
*/
private String apiKey; /**
* 微信商户平台 商户id
*/
private String mchId; /**
*小程序密钥
*/
private String appSecret; /**
* 小程序支付异步回调地址
*/
private String notifyUrl; }

Configure:商户支付秘钥

public class Configure {

    /**
* 商户支付秘钥
*/
@Getter
private static String key = "此处填写秘钥"; public static void setKey(String key) {
Configure.key = key;
} }

6、常量类代码

WeChatPayUrlConstants:微信支付API地址常量

public class WeChatPayUrlConstants {

    /**
* 统一下单预下单接口url
*/
public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /**
* 订单状态查询接口URL
*/
public static final String Orderquery = "https://api.mch.weixin.qq.com/pay/orderquery"; /**
* 订单申请退款
*/
public static final String Refund = "https://api.mch.weixin.qq.com/secapi/pay/refund"; /**
* 付款码 支付
*/
public static final String MicroPay = "https://api.mch.weixin.qq.com/pay/micropay"; /**
* 微信网页授权 获取“code”请求地址
*/
public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize"; /**
* 微信网页授权 获取“code” 回调地址
*/
public static final String GainCodeRedirect_uri = "http://i5jmxe.natappfree.cc/boss/WeChatPayMobile/SkipPage.html"; }

7、业务实现类代码

Controller 层

@Slf4j
@RestController
@RequestMapping("/system/wxpay")
public class WxPayController { @Autowired
private WxPayInfoService wxPayInfoService; /**
* 小程序支付下单接口
*
* @return 返回结果
*/
@ApiOperation("小程序支付功能")
@PostMapping("/pay")
public InvokeResult wxPay(@RequestBody PayParameterVO payParameterVO) { PayParameterVO parameterVO = new PayParameterVO();
parameterVO.setWxOpenId(payParameterVO.getWxOpenId());
parameterVO.setPrice("1");
parameterVO.setGoodsTitle("测试支付商品"); HashMap<String, String> payHistory = wxPayInfoService.insertPayRecord(parameterVO);
return InvokeResultBuilder.success(payHistory);
} /**
* 查询订单
*/
@ApiOperation("订单查询")
@PostMapping("/wx/query")
public InvokeResult orderQuery(@RequestParam("out_trade_no") String out_trade_no) {
QueryReturnInfo queryReturnInfo = wxPayInfoService.orderQuery(out_trade_no);
// return InvokeResultBuilder.success(queryReturnInfo.getTrade_state_desc(), queryReturnInfo);
return InvokeResultBuilder.success(queryReturnInfo);
} /**
* 微信小程序支付成功回调
*
* @param request 请求
* @param response 响应
* @return 返回结果
* @throws Exception 异常处理
*/
@RequestMapping("/weixin/callback")
public String callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("接收到微信支付回调信息");
String notifyXml = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8); // 解析返回结果
Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyXml);
// 判断支付是否成功
if ("SUCCESS".equals(notifyMap.get("result_code"))) {
//支付成功时候,处理业务逻辑
wxPayInfoService.payCallbackSuccess(notifyMap);
//返回处理成功的格式数据,避免微信重复回调
return "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} // 创建响应对象:微信接收到校验失败的结果后,会反复的调用当前回调函数
Map<String, String> returnMap = new HashMap<>();
returnMap.put("return_code", "FAIL");
returnMap.put("return_msg", "");
String returnXml = WXPayUtil.mapToXml(returnMap);
response.setContentType("text/xml");
System.out.println("校验失败");
return returnXml;
}

}

接口层

public interface  WxPayInfoService {

    /**
* 创建统一支付订单
*/
HashMap<String, String> insertPayRecord(PayParameterVO payParameterVO); /**
* 查询订单
* @param out_trade_no 订单号
* @return 返回结果
*/
QueryReturnInfo orderQuery(String out_trade_no); /**
* 微信小程序支付成功回调
* @param notifyMap
*/
void payCallbackSuccess(Map<String, String> notifyMap); }

实现层

@Slf4j
@Service
public class WxPayInfoServiceImpl implements WxPayInfoService { //这是注入的业务处理类
// @Autowired
// private IFPayOrderService ifPayOrderService; @Resource
private WxPayConfig payProperties; private static final DecimalFormat df = new DecimalFormat("#"); /**
* 创建统一支付订单
*
* @param payParameterVO 商品信息
* @return 返回结果
*/
@Override
@Transactional
public HashMap<String, String> insertPayRecord(PayParameterVO payParameterVO) {
String title = payParameterVO.getGoodsTitle();
//金额 * 100 以分为单位
BigDecimal fee = BigDecimal.valueOf(1);
BigDecimal RMB = new BigDecimal(payParameterVO.getPrice());
BigDecimal totalFee = fee.multiply(RMB); try {
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(payProperties.getAppId());
weChatPay.setMch_id(payProperties.getMchId());
weChatPay.setNonce_str(getRandomStringByLength(32));
weChatPay.setBody(title);
weChatPay.setOut_trade_no(getRandomStringByLength(32));
weChatPay.setTotal_fee(df.format(Double.parseDouble(String.valueOf(totalFee))));
// 获取当前服务器IP地址
// weChatPay.setSpbill_create_ip(IpUtils());
weChatPay.setNotify_url(payProperties.getNotifyUrl());
weChatPay.setTrade_type("JSAPI");
//这里直接使用当前用户的openid
weChatPay.setOpenid(payParameterVO.getWxOpenId());
weChatPay.setSign_type("MD5");
//生成签名
String sign = SignUtils.getSign(weChatPay);
weChatPay.setSign(sign); log.info("订单号:" + weChatPay.getOut_trade_no());
//向微信发送下单请求
String result = HttpRequest.sendPost(WeChatPayUrlConstants.Uifiedorder, weChatPay); //将返回结果从xml格式转换为map格式
Map<String, String> wxResultMap = WXPayUtil.xmlToMap(result);
if (StringUtils.isNotEmpty(wxResultMap.get("return_code")) && wxResultMap.get("return_code").equals("SUCCESS")) {
if (wxResultMap.get("result_code").equals("FAIL")) {
log.error("微信统一下单失败!");
return null;
}
}
OrderReturnInfo returnInfo = MapToObject.convertMapToObject(wxResultMap, OrderReturnInfo.class); // 二次签名
if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) {
SignInfo signInfo = new SignInfo();
signInfo.setAppId(payProperties.getAppId());
long time = System.currentTimeMillis() / 1000;
signInfo.setTimeStamp(String.valueOf(time));
signInfo.setNonceStr(WXPayUtil.generateNonceStr());
signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id());
signInfo.setSignType("MD5");
//生成签名
String sign1 = SignUtils.getSign(signInfo);
HashMap<String, String> payInfo = new HashMap<>();
payInfo.put("timeStamp", signInfo.getTimeStamp());
payInfo.put("nonceStr", signInfo.getNonceStr());
payInfo.put("package", signInfo.getRepay_id());
payInfo.put("signType", signInfo.getSignType());
payInfo.put("paySign", sign1);
payInfo.put("placeOrderJsonMsg", JSON.toJSONString(weChatPay));
payInfo.put("orderNum", weChatPay.getOut_trade_no()); // 业务逻辑结束 回传给小程序端唤起支付
return payInfo;
}
return null;
} catch (Exception e) {
log.error(e.getMessage());
}
return null;
} /**
* 查询订单
*
* @param out_trade_no 订单号
* @return 返回结果
*/
@Override
public QueryReturnInfo orderQuery(String out_trade_no) {
try {
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(payProperties.getAppId());
weChatPay.setMch_id(payProperties.getMchId());
weChatPay.setNonce_str(WXPayUtil.generateNonceStr());
weChatPay.setOut_trade_no(out_trade_no);
//order.setSign_type("MD5");
//生成签名
String sign = SignUtils.getSign(weChatPay);
weChatPay.setSign(sign);
//向微信发送查询订单详情请求
String result = HttpRequest.sendPost(WXPayConstants.ORDERQUERY_URL, weChatPay);
Map<String, String> xmlToMap = WXPayUtil.xmlToMap(result); // 将 Map 转换为对象
return MapToObject.convertMapToObject(xmlToMap, QueryReturnInfo.class);
} catch (Exception e) {
log.error("查询支付订单失败:[{}]", e.getMessage());
}
return null;
} /**
* 微信小程序支付成功回调
*
* @param notifyMap 回调Map数据
*/
@Override
public void payCallbackSuccess(Map<String, String> notifyMap) {
//保存相关支付数据
try {
QueryReturnInfo queryReturnInfo = MapToObject.convertMapToObject(notifyMap, QueryReturnInfo.class);
log.info("支付回调信息:" + queryReturnInfo);
//处理回调信息,此处根据自己的项目业务进行处理
// ifPayOrderService.receivePayCallback(queryReturnInfo);
} catch (Exception e) {
throw new RuntimeException(e);
} } /**
* 获取一定长度的随机字符串
*
* @param length 指定字符串长度
* @return 一定长度的字符串
*/
public static String getRandomStringByLength(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
}

4.2:前端核心代码

支付方法

goPay () {
console.log('goPay');
const that = this //向服务器发送下单请求(服务端会返回预下单信息)
uni.request({
url: common.api_base_url + "/system/wxpay/pay",
header: {
"Content-Type": "application/json",
"Authorization": "Bearer " + uni.getStorageSync('token'),
},
method: 'POST',
data: {
goodsId: that.nowGoodsId,
},
success(res) {
console.log("下单信息结果",res)
if (res.data.code == 200){
//成功,调用微信支付接口进行支付()
uni.requestPayment({
provider: 'wxpay',
timeStamp: res.data.data.timeStamp,
nonceStr: res.data.data.nonceStr,
package: res.data.data.package,
signType: res.data.data.signType,
paySign: res.data.data.paySign,
// appId: app.globalData.appid,
success: function (ress) {
console.log("支付完成:",ress)
//支付成功后,查询订单情况,或者2 秒自动跳转到其他页面
uni.showToast({
title: '支付成功',
duration: 2000
});
},
fail: function (err) {
uni.showToast({
title: '支付失败!',err,
icon:"none",
duration: 2000
});
}
});
}else {
//弹出下单失败的提示
uni.showToast({
title:res.data.msg,
icon:"none"
});
}
}
})
},

五、效果图

前端传递参数,后端生成预订单,返回前端,前端唤醒支付页面,随后支付即可,再调用订单状态查询接口,对不同状态的订单进行自己的业务逻辑判断。

最后文章有啥不对,欢迎大佬在评论区指点!!!

如果感觉对你有帮助就点赞推荐或者关注一下吧!!!

UniApp前端+Java后端技术栈 解析微信支付功能的设计实现与关键实践的更多相关文章

  1. java后端技术栈

  2. Java后端技术微信交流群!工作、学习、技术、资源等!期待你的加入!

    <Java后端技术>专注Java相关技术:SSM.Spring全家桶.微服务.MySQL.MyCat.集群.分布式.中间件.Linux.网络.多线程,偶尔讲点运维Jenkins.Nexus ...

  3. 那些年,我的前端/Java后端书单

    全文为这些年,我曾阅读.深入理解过(或正在阅读学习.即将阅读)的一些优秀经典前端/Java后端书籍.全文为纯原创,且将持续更新,未经许可,不得进行转载.当然,如果您喜欢这篇文章,可以动手点点赞或者收藏 ...

  4. vue前端+java后端 vue + vuex + koa2开发环境搭建及示例开发

    vue + vuex + koa2开发环境搭建及示例开发 https://segmentfault.com/a/1190000012918518 vue前端+java后端 https://blog.c ...

  5. 我的JAVA后端技术选型(2020版)

    JAVA后端技术选型(2020版)集群网关: LVS+keepalived 或 HAProxy服务网关 : zuul1.x注册中心: Eureka,Zookeeper配置中心: Spring Clou ...

  6. web前端各大技术都能实现什么功能

    web前端各大技术都能实现什么功能 以下是孜然为你总结的web前端开发你必须要一项一项掌握的技术:Html.css.ajax.jquery.extjs.JavaScript,今天为你详细解读他们各自都 ...

  7. 微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

    1.点评 对于IM系统来说,如何做到IM聊天消息离线差异拉取(差异拉取是为了节省流量).消息多端同步.消息顺序保证等,是典型的IM技术难点. 就像即时通讯网整理的以下IM开发干货系列一样: <I ...

  8. 前端如何在h5页面调用微信支付?

    在微信服务号开发的时候经常会遇到微信支付的功能实现,通过实际经验自己总结了一下,前端在H5页面调起微信支付有两种办法,一是利用内置对象,二是通过引用微信的js sdk,亲测都能支付成功,从写法上来看用 ...

  9. C#开发微信门户及应用(40)--使用微信JSAPI实现微信支付功能

    在我前面的几篇博客,有介绍了微信支付.微信红包.企业付款等各种和支付相关的操作,不过上面都是基于微信普通API的封装,本篇随笔继续微信支付这一主题,继续介绍基于微信网页JSAPI的方式发起的微信支付功 ...

  10. [5] 微信公众号开发 - 微信支付功能开发(网页JSAPI调用)

    1.微信支付的流程 如下三张手机截图,我们在微信网页端看到的支付,表面上看到的是 "点击支付按钮 - 弹出支付框 - 支付成功后出现提示页面",实际上的核心处理过程是: 点击支付按 ...

随机推荐

  1. gin Http请求Body和Header的获取 request post form Query header

    gin Http请求Body和Header的获取 request post form Query header 请求参数 POST /post?id=1234&page=1 HTTP/1.1 ...

  2. spring 事务失效的 12 种场景

    看这个:https://blog.csdn.net/hanjiaqian/article/details/120501741里面有12种失效场景以及如何解决. 在 spring 中为了支持编程式事务, ...

  3. ASP.NET Session 清除

    // 值为 null,这样对应的 Session 会继续存在,但值为 null Session["UserId"] = null; // 移除指定 Session Session. ...

  4. C# Socket教程入门,Socket的使用方法,附可执行Demo

    核心: 在服务器端和客户端的两个Socket实例,共同维持一个连接,而我们还需要一个服务端绑定了特定端口号的固定的serverSocket,用来监听远方clientSocket的申请,并为这个申请建立 ...

  5. AI团队比单打独斗强!CrewAI多智能体协作系统开发踩坑全解析

    AI团队比单打独斗强!CrewAI多智能体协作系统开发踩坑全解析 阅读时间: 5分钟 | 字数: 1500+ "你是否曾为单个大模型难以解决复杂专业问题而苦恼?是否想过,如果能像组建专业团队 ...

  6. 神级辅助工具,解决GPT-SoVITS配音发音纠正和逐句优化

    即使地表最强AI配音也无法自动识别360应配音成三百六十还是三六零,在长文配音中很难一次满意,总会因为个别几句配音不理想而毁掉整个配音成果. 在GPT-SoVITS配音中,自动把长文章拆分成段落或长句 ...

  7. springAPI对事物支持之注解式配置

    一.无Spring核心配置文件:applicationContext.xml下开启注解式事物 @Configuration @EnableTransactionManagement public cl ...

  8. 🎀Java线程池创建

    简介 Java 手动创建线程池 代码 package com.zk.app.utils; import com.google.common.util.concurrent.ThreadFactoryB ...

  9. Python3爬虫入门(一)

    Python3爬虫入门 ​网络爬虫,也叫网络蜘蛛(Web?Spider).它根据网页地址(URL)爬取网页内容,而网页地址(URL)就是我们在浏览器中输入的网站链接. 在浏览器的地址栏输入URL地址, ...

  10. System Integrity Protection (SIP) iOS10.15安装软件提示文件损坏问题解决方法

    方法如下:1.开机的时候立即按住command+R 组合键不要放手,直到进度条加载完才松手,进入到系统恢复工具界面 2. 然后点击"实用工具"选项卡中的"终端" ...