目录
概要
最近在做《苍穹外卖》项目练手练手时,由于无法创建商家号,所以无法直接体验原生的微信支付流程。浏览了网上很多解决方案,基本都是从小程序后端直接返回模拟数据给小程序,以达到“跳过支付”的目的。但为了深入理解和体验微信支付整个流程,我新建了一个微信支付模块(也可以直接新建一个项目),让后端和小程序直接与其交互,从而实现完整的微信支付流程。
代码实现
模拟微信支付后端实现
数据库设计
新建数据库wechat_pay,用于存储微信支付相关信息(实际可能是NoSQL数据库,为了方便所以直接使用关系型数据库)。prepay_order表用来存储预支付订单信息,transaction_record用来存储实际交易记录。两张表根据微信开放文档设计,很多字段在本项目中可能不会使用到(因为本人对签名算法了解有限,暂未模拟签名验证)。
create schema wechat_pay;
create table prepay_order
(
id bigint auto_increment comment '预支付id'
primary key,
total decimal(9, 2) not null,
currency varchar(16) not null comment '金额单位',
out_trade_no varchar(32) not null comment '商户系统内部订单号',
openid varchar(128) not null comment '微信用户唯一标识',
description varchar(127) not null comment '商品描述',
notify_url varchar(255) null comment '支付回调通知URL',
create_time datetime null comment '创建时间',
appid varchar(32) not null comment '公众号id',
mchid varchar(32) not null comment '直连商户号'
)
comment '预支付订单';
create table transaction_record
(
id bigint auto_increment comment '交易id'
primary key,
total decimal(9, 2) not null comment '交易金额',
currency varchar(16) not null comment '金额单位',
out_trade_no varchar(32) not null comment '商户系统内部订单号',
openid varchar(128) not null comment '微信用户唯一标识',
description varchar(127) not null comment '商品描述',
create_time datetime null comment '交易时间',
appid varchar(32) not null comment '公众号id',
mchid varchar(32) not null comment '直连商户号'
)
comment '交易记录';
application.yml配置文件
为了避免和商户后端服务端口号冲突,这里配置为8081,因为我是用的mybatis-plus,所以也进行了相关配置(根据实际需要配置)
server:
port: 8081
spring:
main:
allow-circular-references: true
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/wechat_pay?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
username: root
password: ********
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.wechat.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
global-config:
db-config:
id-type: auto
update-strategy: not_null
pom.xml文件
使用和商户后端同样的springboot版本和依赖版本,避免版本错误。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wechat</groupId>
<artifactId>wechat-pay</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>wechat-pay</name>
<description>wechat-pay</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
entity实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 微信支付后端预支付订单
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PrepayOrder implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//金额
private BigDecimal total;
//金额单位
private String currency;
//商户系统订单号
private String outTradeNo;
//openid
private String openid;
//商品描述
private String description;
//回调通知地址
private String notifyUrl;
//创建时间
private LocalDateTime createTime;
private String appid;
private String mchid;
}
package com.wechat.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 微信支付后端交易记录
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TransactionRecord implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//金额
private BigDecimal total;
//金额单位
private String currency;
//商户系统订单号
private String outTradeNo;
//openid
private String openid;
//商品描述
private String description;
//创建时间
private LocalDateTime createTime;
private String appid;
private String mchid;
}
实际中这些参数都是用来签名验证的,虽然没有实现,但都做了保留。
package com.wechat.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 小程序微信支付传递的数据模型(模拟微信支付后台数据模型)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PaymentDTO {
private String timeStamp; //时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间
private String nonceStr; //随机字符串,长度为32个字符以下
private Long prepayId; //统一下单接口返回的 prepay_id 参数值
private String signType; //MD5签名算法,应与后台下单时的值一致
private String paySign; //签名
}
controller类
package com.wechat.controller;
import com.alibaba.fastjson.JSONObject;
import com.wechat.entity.PaymentDTO;
import com.wechat.entity.PrepayOrder;
import com.wechat.entity.TransactionRecord;
import com.wechat.result.PayResult;
import com.wechat.result.Result;
import com.wechat.service.PrepayOrderService;
import com.wechat.service.TransactionRecordService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/pay/transactions")
@Slf4j
public class PayController {
@Autowired
private PrepayOrderService prepayOrderService;
@Autowired
private TransactionRecordService transactionRecordService;
/**
* 模拟微信支付后端返回预支付交易标识
* 商家后端发送post请求,请求体中携带的是封装的JSONObject对象,包含金额、商家号、订单号、appid、订单描述、下单微信用户的openid、通知地址
* @return
*/
@PostMapping("/jsapi")
public PayResult jsapi(@RequestBody JSONObject body){
log.info("模拟微信预支付交易{}",body);//{"amount":{"total":1,"currency":"CNY"},"mchid":"123456","out_trade_no":"1733894836304","appid":"wx*********","description":"外卖订单","notify_url":"http://localhost:8080/notify/paySuccess","payer":{"openid":"*******"}}
//将该预支付单加入数据库
PrepayOrder prepayOrder = new PrepayOrder();
prepayOrder.setAppid(body.getString("appid"));
prepayOrder.setMchid(body.getString("mchid"));
prepayOrder.setDescription(body.getString("description"));
prepayOrder.setNotifyUrl(body.getString("notify_url"));
prepayOrder.setOutTradeNo(body.getString("out_trade_no"));
prepayOrder.setOpenid(body.getJSONObject("payer").getString("openid"));
prepayOrder.setTotal(body.getJSONObject("amount").getBigDecimal("total"));
prepayOrder.setCurrency(body.getJSONObject("amount").getString("currency"));
prepayOrder.setCreateTime(LocalDateTime.now());
prepayOrderService.save(prepayOrder);
return new PayResult(prepayOrder.getId(),"SUCCESS");
}
/**
* 小程序用户发起微信支付
* 模拟微信支付后端服务
* 支付成功后通知商家后端
* @return
*/
@PostMapping("/payment")
public Result payment(@RequestBody PaymentDTO paymentDTO) throws IOException {
log.info("模拟微信正式支付交易{}",paymentDTO);
//根据预支付订单id获取商户系统订单号
PrepayOrder prepayOrder = prepayOrderService.getById(paymentDTO.getPrepayId());
//如果该预支付订单查不到,则返回错误信息
if(prepayOrder == null){
return Result.error("支付失败");
} else {
//获取客户端向商户后端发送通知
CloseableHttpClient httpClient = HttpClients.createDefault();
String outTradeNo = prepayOrder.getOutTradeNo();
//生成支付记录
TransactionRecord transactionRecord = new TransactionRecord();
BeanUtils.copyProperties(prepayOrder, transactionRecord);
transactionRecordService.save(transactionRecord);
//删除预支付订单
prepayOrderService.removeById(prepayOrder);
//支付成功,向商家后端推送结果
HttpPost httpPost = new HttpPost(prepayOrder.getNotifyUrl());
httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
JSONObject jsonObject = new JSONObject();
jsonObject.put("out_trade_no", outTradeNo);
jsonObject.put("transaction_id", transactionRecord.getId());
httpPost.setEntity(new StringEntity(jsonObject.toJSONString(), "UTF-8"));
httpClient.execute(httpPost);
//给小程序返回结果
return Result.success("支付成功");
}
}
}
service及其实现类
package com.wechat.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wechat.entity.PrepayOrder;
public interface PrepayOrderService extends IService<PrepayOrder> {
}
package com.wechat.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wechat.entity.TransactionRecord;
public interface TransactionRecordService extends IService<TransactionRecord> {
}
package com.wechat.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wechat.entity.PrepayOrder;
import com.wechat.mapper.PrepayOrderMapper;
import com.wechat.service.PrepayOrderService;
import org.springframework.stereotype.Service;
@Service
public class PrepayOrderServiceImp extends ServiceImpl<PrepayOrderMapper, PrepayOrder> implements PrepayOrderService {
}
package com.wechat.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wechat.entity.TransactionRecord;
import com.wechat.mapper.TransactionRecordMapper;
import com.wechat.service.TransactionRecordService;
import org.springframework.stereotype.Service;
@Service
public class TransactionRecordServiceImpl extends ServiceImpl<TransactionRecordMapper, TransactionRecord> implements TransactionRecordService {
}
统一返回结果类
package com.wechat.result;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 模拟微信支付后端返回的预支付交易标识
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayResult {
private Long prepay_id;
private String code;
}
package com.wechat.result;
import lombok.Data;
import java.io.Serializable;
/**
* 后端统一返回结果
* @param <T>
*/
@Data
public class Result<T> implements Serializable {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}
public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
}
《苍穹外卖》后端代码改动
微信支付工具类
这里没有使用微信支付提供的httpClient,因为它需要读取并解析两个密钥文件(pem),并自动进行签名。接口请求地址也换成了我们自己实现的模拟微信支付端地址。签名等可以随便写,配置文件中的商户id,密钥等都可以随便配置,因为并没有使用到,只是做了保留。
package com.sky.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.properties.WeChatProperties;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
/**
* 微信支付工具类
*/
@Component
public class MyWeChatPayUtil {
//微信支付下单接口地址
public static final String JSAPI = "http://localhost:8081/pay/transactions/jsapi";
//申请退款接口地址
public static final String REFUNDS = "http://localhost:8081/refund/domestic/refunds";
@Autowired
private WeChatProperties weChatProperties;
/**
* 发送post方式请求
*
* @param url
* @param body
* @return
*/
private String post(String url, String body) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
httpPost.setEntity(new StringEntity(body, "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());
return bodyAsString;
} finally {
httpClient.close();
response.close();
}
}
/**
* 发送get方式请求
*
* @param url
* @return
*/
private String get(String url) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());
return bodyAsString;
} finally {
httpClient.close();
response.close();
}
}
/**
* jsapi下单
*
* @param orderNum 商户订单号
* @param total 总金额
* @param description 商品描述
* @param openid 微信用户的openid
* @return
*/
private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("appid", weChatProperties.getAppid());
jsonObject.put("mchid", weChatProperties.getMchid());
jsonObject.put("description", description);
jsonObject.put("out_trade_no", orderNum);
jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
JSONObject amount = new JSONObject();
amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
amount.put("currency", "CNY");
jsonObject.put("amount", amount);
JSONObject payer = new JSONObject();
payer.put("openid", openid);
jsonObject.put("payer", payer);
String body = jsonObject.toJSONString();
return post(JSAPI, body);
}
/**
* 小程序支付
*
* @param orderNum 商户订单号
* @param total 金额,单位 元
* @param description 商品描述
* @param openid 微信用户的openid
* @return
*/
public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
//统一下单,生成预支付交易单
String bodyAsString = jsapi(orderNum, total, description, openid);
//解析返回结果
JSONObject jsonObject = JSON.parseObject(bodyAsString);
String prepayId = jsonObject.getString("prepay_id");
//如果成功生成预支付交易单,经过签名验证封装后返回给前端
if (prepayId != null) {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = RandomStringUtils.randomNumeric(32);
//二次签名,调起支付需要重新签名(签名省略)
//构造数据给微信小程序,用于调起微信支付
JSONObject jo = new JSONObject();
jo.put("timeStamp", timeStamp);
jo.put("nonceStr", nonceStr);
jo.put("package", prepayId);
jo.put("signType", "RSA");
jo.put("paySign", "123456789");
return jo;
}
return jsonObject;
}
/**
* 申请退款
*
* @param outTradeNo 商户订单号
* @param outRefundNo 商户退款单号
* @param refund 退款金额
* @param total 原订单金额
* @return
*/
public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("out_trade_no", outTradeNo);
jsonObject.put("out_refund_no", outRefundNo);
JSONObject amount = new JSONObject();
amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
amount.put("currency", "CNY");
jsonObject.put("amount", amount);
jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());
String body = jsonObject.toJSONString();
//调用申请退款接口
return post(REFUNDS, body);
}
}
OrderServiceImpl类
package com.sky.service.impl;
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {
@Autowired
private UserMapper userMapper;
@Autowired
private MyWeChatPayUtil myWeChatPayUtil;
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
User user = userMapper.selectById(userId);
// 根据订单号查询订单金额
String orderNumber = ordersPaymentDTO.getOrderNumber();
LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<Orders>()
.eq(Orders::getUserId, userId)
.eq(Orders::getNumber, orderNumber)
.select(Orders::getAmount);
Orders order = baseMapper.selectOne(wrapper);
//调用微信支付接口,生成预支付交易单
JSONObject jsonObject = null;
try {
jsonObject = myWeChatPayUtil.pay(
ordersPaymentDTO.getOrderNumber(), //商户订单号
order.getAmount(), //支付金额,单位 元
"外卖订单", //商品描述
user.getOpenid() //微信用户的openid
);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
throw new OrderBusinessException("该订单已支付");
}
OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
vo.setPackageStr(jsonObject.getString("package"));
return vo;
}
/**
* 支付成功,修改订单状态
*
* @param outTradeNo
*/
public void paySuccess(String outTradeNo) {
// 根据根据订单号更新订单的状态、支付方式、支付状态、结账时间
LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>()
.eq(outTradeNo != null, Orders::getNumber, outTradeNo)
.set(Orders::getStatus, Orders.TO_BE_CONFIRMED)
.set(Orders::getPayStatus, Orders.PAID)
.set(Orders::getCheckoutTime, LocalDateTime.now());
baseMapper.update(wrapper);
}
}
PayNotifyController.java类
同样,这里也没有用到解密。
package com.sky.controller.user;
import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.properties.WeChatProperties;
import com.sky.service.OrderService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* 支付回调相关接口
*/
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {
@Autowired
private OrderService orderService;
@Autowired
private WeChatProperties weChatProperties;
/**
* 支付成功回调
*
* @param request
*/
@RequestMapping("/paySuccess")
public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
//读取数据
String body = readData(request);
log.info("支付成功回调:{}", body);
//数据解密
//String plainText = decryptData(body);
//log.info("解密后的文本:{}", plainText);
JSONObject jsonObject = JSON.parseObject(body);
String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
String transactionId = jsonObject.getString("transaction_id");//微信支付交易号
log.info("商户平台订单号:{}", outTradeNo);
log.info("微信支付交易号:{}", transactionId);
//业务处理,修改订单状态、来单提醒
orderService.paySuccess(outTradeNo);
//给微信响应
responseToWeixin(response);
}
/**
* 读取数据
*
* @param request
* @return
* @throws Exception
*/
private String readData(HttpServletRequest request) throws Exception {
BufferedReader reader = request.getReader();
StringBuilder result = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
}
/**
* 数据解密
*
* @param body
* @return
* @throws Exception
*/
private String decryptData(String body) throws Exception {
JSONObject resultObject = JSON.parseObject(body);
JSONObject resource = resultObject.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String nonce = resource.getString("nonce");
String associatedData = resource.getString("associated_data");
AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
//密文解密
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return plainText;
}
/**
* 给微信响应
* @param response
*/
private void responseToWeixin(HttpServletResponse response) throws Exception{
response.setStatus(200);
HashMap<Object, Object> map = new HashMap<>();
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
}
小程序端代码改动
将注释处的代码改成下面的代码即可,原来是直接请求微信支付官方接口,现在是请求我们自己实现的微信支付后端,后端会根据参数中的预支付id查找是否有该预支付订单,如果有,会把支付记录存入数据库,并分别通知前端和后端,做出相应的操作。
//请求模拟微信支付后端,完成支付
wx.request({
url: 'http://localhost:8081/pay/transactions/payment', //模拟微信支付后端
data: {
nonceStr: res.data.nonceStr,
prepayId: res.data.packageStr,
paySign: res.data.paySign,
timeStamp: res.data.timeStamp,
signType: res.data.signType,
},
header: {
'content-type': 'application/json' // 默认值
},
method: 'POST',
success(res) {
console.log(res)
//支付成功
if(res.data.code == 1){
wx.showModal({
title: '提示',
content: '支付成功',
success: function () {
uni.redirectTo({
url: '/pages/success/index?orderId=' + _this.orderId
});
}
})
console.log('支付成功!')
}
}
})
// 调起微信支付
// wx.requestPayment({
// nonceStr: res.data.nonceStr,
// package: res.data.packageStr,
// paySign: res.data.paySign,
// timeStamp: res.data.timeStamp,
// signType: res.data.signType,
// success: function (res) {
// wx.showModal({
// title: '提示',
// content: '支付成功',
// success: function () {
// uni.redirectTo({
// url: '/pages/success/index?orderId=' + _this.orderId
// });
// }
// })
// console.log('支付成功!')
// }
// })
小结
这里只是做了一个简单的模拟实现,很多地方并不规范,如有问题,欢迎指正!
后续对微信后端接口的调用,都可以在此代码的基础上实现,例如退款等业务。