实现用户下单功能,微信小程序用户通过购物车去支付按钮下单
@RestController("userOrderController")
@RequestMapping("user/order")
@Api(tags = "用户端订单相关接口")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 用户下单
*
* @param orderSubmitDTO
* @return
*/
@PostMapping("submit")
@ApiOperation("用户下单")
public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO orderSubmitDTO) {
log.info("用户下单,参数为:{}", orderSubmitDTO);
OrderSubmitVO ordersubmit = orderService.ordersubmit(orderSubmitDTO);
return Result.success(ordersubmit);
}
}
package com.sky.service;
import com.sky.dto.OrdersPaymentDTO;
import com.sky.dto.OrdersSubmitDTO;
import com.sky.vo.OrderPaymentVO;
import com.sky.vo.OrderSubmitVO;
public interface OrderService {
/**
* 用户下单
*
* @param orderSubmitVO
* @return
*/
OrderSubmitVO ordersubmit(OrdersSubmitDTO orderSubmitVO);
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;
/**
* 支付成功,修改订单状态
*
* @param outTradeNo
*/
void paySuccess(String outTradeNo);
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@Autowired
private AddressBookMapper addressBookMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Override
@Transactional
public OrderSubmitVO ordersubmit(OrdersSubmitDTO ordersSubmitDTO) {
//处理业务异常(地址簿为空,购物车为空),前端和后端都应作此校验,以保证代码的健壮性
Long addressBookId = ordersSubmitDTO.getAddressBookId();
AddressBook addressBook = addressBookMapper.getById(addressBookId);
//判断地址簿是否为空
if (addressBook == null) {
throw new OrderBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
}
Long userId = BaseContext.getCurrentId();
ShoppingCart shoppingCart = ShoppingCart.builder().userId(userId).build();
List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);
//判断购物车是否为空
if (shoppingCartList == null || shoppingCartList.size() == 0) {
throw new OrderBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
//向订单表中插入一条数据
Orders orders = new Orders();
BeanUtils.copyProperties(ordersSubmitDTO, orders);
orders.setNumber(String.valueOf(System.currentTimeMillis()));
orders.setStatus(Orders.PENDING_PAYMENT);
orders.setUserId(userId);
orders.setOrderTime(LocalDateTime.now());
orders.setPayStatus(Orders.UN_PAID);
orders.setPhone(addressBook.getPhone());
orders.setAddress(addressBook.getDetail());
orders.setConsignee(addressBook.getConsignee());
orderMapper.insert(orders);
ArrayList<OrderDetail> orderDetails = new ArrayList<>();
//向订单详情表中插入n条数据
for (ShoppingCart cart : shoppingCartList) {
OrderDetail orderDetail = new OrderDetail();
BeanUtils.copyProperties(cart, orderDetail);
orderDetail.setOrderId(orders.getId());
orderDetails.add(orderDetail);
}
orderDetailMapper.insertBatch(orderDetails);
//清空购物车数据
shoppingCartMapper.deleteByUserId(userId);
//封装OrderSubmitVO数据
OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
.id(orders.getId())
.orderTime(orders.getOrderTime())
.orderAmount(orders.getAmount())
.orderNumber(orders.getNumber())
.build();
return orderSubmitVO;
}
}
@Mapper
public interface AddressBookMapper {
/**
* 根据id查询
* @param id
* @return
*/
@Select("select * from address_book where id = #{id}")
AddressBook getById(Long id);
}
@Mapper
public interface ShoppingCartMapper {
/**
* 动态条件查询购物车
*
* @param shoppingCart
* @return
*/
List<ShoppingCart> list(ShoppingCart shoppingCart);
/**
* 根据用户id删除购物车数据
*
* @param userId
*/
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteByUserId(Long userId);
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.ShoppingCartMapper">
<select id="list" resultType="com.sky.entity.ShoppingCart">
select *
from shopping_cart
<where>
<if test="userId!=null">
and user_id = #{userId}
if>
<if test="dishId != null">
and dish_id = #{dishId}
if>
<if test="setmealId != null">
and setmeal_id = #{setmealId}
if>
<if test="dishFlavor">
and dish_flavor = #{dishFlavor}
if>
where>
select>
mapper>
@Mapper
public interface OrderMapper {
/**
* 插入订单数据
*
* @param orders
*/
void insert(Orders orders);
}
@Mapper
public interface OrderDetailMapper {
void insertBatch(ArrayList<OrderDetail> orderDetails);
}
@Data
public class OrdersSubmitDTO implements Serializable {
//地址簿id
private Long addressBookId;
//付款方式
private int payMethod;
//备注
private String remark;
//预计送达时间
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime estimatedDeliveryTime;
//配送状态 1立即送出 0选择具体时间
private Integer deliveryStatus;
//餐具数量
private Integer tablewareNumber;
//餐具数量状态 1按餐量提供 0选择具体数量
private Integer tablewareStatus;
//打包费
private Integer packAmount;
//总金额
private BigDecimal amount;
}
/**
* 地址簿
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddressBook implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//用户id
private Long userId;
//收货人
private String consignee;
//手机号
private String phone;
//性别 0 女 1 男
private String sex;
//省级区划编号
private String provinceCode;
//省级名称
private String provinceName;
//市级区划编号
private String cityCode;
//市级名称
private String cityName;
//区级区划编号
private String districtCode;
//区级名称
private String districtName;
//详细地址
private String detail;
//标签
private String label;
//是否默认 0否 1是
private Integer isDefault;
}
/**
* 购物车
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ShoppingCart implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//名称
private String name;
//用户id
private Long userId;
//菜品id
private Long dishId;
//套餐id
private Long setmealId;
//口味
private String dishFlavor;
//数量
private Integer number;
//金额
private BigDecimal amount;
//图片
private String image;
private LocalDateTime createTime;
}
/**
* 订单
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Orders implements Serializable {
/**
* 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消
*/
public static final Integer PENDING_PAYMENT = 1;
public static final Integer TO_BE_CONFIRMED = 2;
public static final Integer CONFIRMED = 3;
public static final Integer DELIVERY_IN_PROGRESS = 4;
public static final Integer COMPLETED = 5;
public static final Integer CANCELLED = 6;
/**
* 支付状态 0未支付 1已支付 2退款
*/
public static final Integer UN_PAID = 0;
public static final Integer PAID = 1;
public static final Integer REFUND = 2;
private static final long serialVersionUID = 1L;
private Long id;
//订单号
private String number;
//订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消 7退款
private Integer status;
//下单用户id
private Long userId;
//地址id
private Long addressBookId;
//下单时间
private LocalDateTime orderTime;
//结账时间
private LocalDateTime checkoutTime;
//支付方式 1微信,2支付宝
private Integer payMethod;
//支付状态 0未支付 1已支付 2退款
private Integer payStatus;
//实收金额
private BigDecimal amount;
//备注
private String remark;
//用户名
private String userName;
//手机号
private String phone;
//地址
private String address;
//收货人
private String consignee;
//订单取消原因
private String cancelReason;
//订单拒绝原因
private String rejectionReason;
//订单取消时间
private LocalDateTime cancelTime;
//预计送达时间
private LocalDateTime estimatedDeliveryTime;
//配送状态 1立即送出 0选择具体时间
private Integer deliveryStatus;
//送达时间
private LocalDateTime deliveryTime;
//打包费
private int packAmount;
//餐具数量
private int tablewareNumber;
//餐具数量状态 1按餐量提供 0选择具体数量
private Integer tablewareStatus;
}
/**
* 订单明细
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderDetail implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//名称
private String name;
//订单id
private Long orderId;
//菜品id
private Long dishId;
//套餐id
private Long setmealId;
//口味
private String dishFlavor;
//数量
private Integer number;
//金额
private BigDecimal amount;
//图片
private String image;
}
实现订单支付功能,用户通过微信小程序支付
注意:要实现微信支付就需要注册微信支付的一个商户号,这个商户号是必须要有一家企业并且有正规的营业执照。只有具备了这些资质之后,才可以去注册商户号,才能开通支付权限。
完成微信支付有两个关键的步骤:
第一个就是需要在商户系统当中调用微信后台的一个下单接口,就是生成预支付交易单。
第二个就是支付成功之后微信后台会给推送消息。
这两个接口数据的安全性,要求其实是非常高的。
**解决:**微信提供的方式就是对数据进行加密、解密、签名多种方式。要完成数据加密解密,需要提前准备相应的一些文件,其实就是一些证书。
获取微信支付平台证书、商户私钥文件:
apiclient_key.pem
wechatpay_1111111111111111111.pem
在后绪程序开发过程中,就会使用到这两个文件,需要提前把这两个文件准备好。
微信后台会调用到商户系统给推送支付的结果,在这里我们就会遇到一个问题,就是微信后台怎么就能调用到我们这个商户系统呢?因为这个调用过程,其实本质上也是一个HTTP请求。
目前,商户系统它的ip地址就是当前自己电脑的ip地址,只是一个局域网内的ip地址,微信后台无法调用到。
解决:内网穿透。通过cpolar软件可以获得一个临时域名,而这个临时域名是一个公网ip,这样,微信后台就可以请求到商户系统了。
cpolar软件的使用:
1). 下载与安装
下载地址:https://dashboard.cpolar.com/get-started
2). cpolar指定authtoken
在官网获取authtoken,在安装目录下执行命令
cpolar.exe authtoken xxxxxxxxxxxxxxxxxxxxxxxx
3).在安装目录下使用cpolar.exe http 端口号(eg:cpolar.exe http 8080)命令获取临时公网地址即可
@RestController("userOrderController")
@RequestMapping("user/order")
@Api(tags = "用户端订单相关接口")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 用户下单
*
* @param orderSubmitDTO
* @return
*/
@PostMapping("submit")
@ApiOperation("用户下单")
public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO orderSubmitDTO) {
log.info("用户下单,参数为:{}", orderSubmitDTO);
OrderSubmitVO ordersubmit = orderService.ordersubmit(orderSubmitDTO);
return Result.success(ordersubmit);
}
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
@PutMapping("/payment")
@ApiOperation("订单支付")
public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
log.info("订单支付:{}", ordersPaymentDTO);
OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
log.info("生成预支付交易单:{}", orderPaymentVO);
return Result.success(orderPaymentVO);
}
}
/**
* 支付回调相关接口
*/
@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(plainText);
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();
}
}
public interface OrderService {
/**
* 用户下单
*
* @param orderSubmitVO
* @return
*/
OrderSubmitVO ordersubmit(OrdersSubmitDTO orderSubmitVO);
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;
/**
* 支付成功,修改订单状态
*
* @param outTradeNo
*/
void paySuccess(String outTradeNo);
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@Autowired
private AddressBookMapper addressBookMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private WeChatPayUtil weChatPayUtil;
@Override
@Transactional
public OrderSubmitVO ordersubmit(OrdersSubmitDTO ordersSubmitDTO) {
//处理业务异常(地址簿为空,购物车为空),前端和后端都应作此校验,以保证代码的健壮性
Long addressBookId = ordersSubmitDTO.getAddressBookId();
AddressBook addressBook = addressBookMapper.getById(addressBookId);
//判断地址簿是否为空
if (addressBook == null) {
throw new OrderBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
}
Long userId = BaseContext.getCurrentId();
ShoppingCart shoppingCart = ShoppingCart.builder().userId(userId).build();
List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);
//判断购物车是否为空
if (shoppingCartList == null || shoppingCartList.size() == 0) {
throw new OrderBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
//向订单表中插入一条数据
Orders orders = new Orders();
BeanUtils.copyProperties(ordersSubmitDTO, orders);
orders.setNumber(String.valueOf(System.currentTimeMillis()));
orders.setStatus(Orders.PENDING_PAYMENT);
orders.setUserId(userId);
orders.setOrderTime(LocalDateTime.now());
orders.setPayStatus(Orders.UN_PAID);
orders.setPhone(addressBook.getPhone());
orders.setAddress(addressBook.getDetail());
orders.setConsignee(addressBook.getConsignee());
orderMapper.insert(orders);
ArrayList<OrderDetail> orderDetails = new ArrayList<>();
//向订单详情表中插入n条数据
for (ShoppingCart cart : shoppingCartList) {
OrderDetail orderDetail = new OrderDetail();
BeanUtils.copyProperties(cart, orderDetail);
orderDetail.setOrderId(orders.getId());
orderDetails.add(orderDetail);
}
orderDetailMapper.insertBatch(orderDetails);
//清空购物车数据
shoppingCartMapper.deleteByUserId(userId);
//封装OrderSubmitVO数据
OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
.id(orders.getId())
.orderTime(orders.getOrderTime())
.orderAmount(orders.getAmount())
.orderNumber(orders.getNumber())
.build();
return orderSubmitVO;
}
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
@Override
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
User user = userMapper.getById(userId);
//调用微信支付接口,生成预支付交易单
JSONObject jsonObject = weChatPayUtil.pay(
ordersPaymentDTO.getOrderNumber(), //商户订单号
new BigDecimal(0.01), //支付金额,单位 元
"苍穹外卖订单", //商品描述
user.getOpenid() //微信用户的openid
);
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
*/
@Override
public void paySuccess(String outTradeNo) {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
// 根据订单号查询当前用户的订单
Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId);
// 根据订单id更新订单的状态、支付方式、支付状态、结账时间
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
}
}
@Mapper
public interface UserMapper {
/**
* 根据id查询用户
*
* @param userId
* @return
*/
@Select("select * from user where id = #{id}")
User getById(Long userId);
}
package com.sky.mapper;
import com.sky.entity.Orders;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface OrderMapper {
/**
* 插入订单数据
*
* @param orders
*/
void insert(Orders orders);
/**
* 根据订单号和用户id查询订单
*
* @param orderNumber
* @param userId
*/
@Select("select * from orders where number = #{orderNumber} and user_id= #{userId}")
Orders getByNumberAndUserId(String orderNumber, Long userId);
/**
* 修改订单信息
*
* @param orders
*/
void update(Orders orders);
}
/**
* 微信支付工具类
*/
@Component
public class WeChatPayUtil {
//微信支付下单接口地址
public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
//申请退款接口地址
public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
@Autowired
private WeChatProperties weChatProperties;
/**
* 获取调用微信接口的客户端工具对象
*
* @return
*/
private CloseableHttpClient getClient() {
PrivateKey merchantPrivateKey = null;
try {
//merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题
merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
//加载平台证书文件
X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
//wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉
List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
.withWechatPay(wechatPayCertificates);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
CloseableHttpClient httpClient = builder.build();
return httpClient;
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
/**
* 发送post方式请求
*
* @param url
* @param body
* @return
*/
private String post(String url, String body) throws Exception {
CloseableHttpClient httpClient = getClient();
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 = getClient();
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);
System.out.println(jsonObject);
String prepayId = jsonObject.getString("prepay_id");
if (prepayId != null) {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = RandomStringUtils.randomNumeric(32);
ArrayList<Object> list = new ArrayList<>();
list.add(weChatProperties.getAppid());
list.add(timeStamp);
list.add(nonceStr);
list.add("prepay_id=" + prepayId);
//二次签名,调起支付需要重新签名
StringBuilder stringBuilder = new StringBuilder();
for (Object o : list) {
stringBuilder.append(o).append("\n");
}
String signMessage = stringBuilder.toString();
byte[] message = signMessage.getBytes();
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
signature.update(message);
String packageSign = Base64.getEncoder().encodeToString(signature.sign());
//构造数据给微信小程序,用于调起微信支付
JSONObject jo = new JSONObject();
jo.put("timeStamp", timeStamp);
jo.put("nonceStr", nonceStr);
jo.put("package", "prepay_id=" + prepayId);
jo.put("signType", "RSA");
jo.put("paySign", packageSign);
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);
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderPaymentVO implements Serializable {
private String nonceStr; //随机字符串
private String paySign; //签名
private String timeStamp; //时间戳
private String signType; //签名算法
private String packageStr; //统一下单接口返回的 prepay_id 参数值
}
sky:
wechat:
appid: ****************
secret: ****************************
mchid : 1561414111
mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF111111
privateKeyFilePath: D:\apiclient_key.pem
apiV3Key: CZBK51236435wxpay435434323FF1111
weChatPayCertFilePath: D:\wechatpay_166D96F876F45C7D07CE98952A96EC9803681111.pem
notifyUrl: https://www.weixin.qq.com/wxpay/pay.php
refundNotifyUrl: https://www.weixin.qq.com/wxpay/pay.php
sky:
wechat:
appid: ${sky.wechat.appid}
secret: ${sky.wechat.secret}
mchid : ${sky.wechat.mchid}
mchSerialNo: ${sky.wechat.mchSerialNo}
privateKeyFilePath: ${sky.wechat.privateKeyFilePath}
apiV3Key: ${sky.wechat.apiV3Key}
weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}
notifyUrl: ${sky.wechat.notifyUrl}
refundNotifyUrl: ${sky.wechat.refundNotifyUrl}
@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {
private String appid; //小程序的appid
private String secret; //小程序的秘钥
private String mchid; //商户号
private String mchSerialNo; //商户API证书的证书序列号
private String privateKeyFilePath; //商户私钥文件
private String apiV3Key; //证书解密的密钥
private String weChatPayCertFilePath; //平台证书
private String notifyUrl; //支付成功的回调地址
private String refundNotifyUrl; //退款成功的回调地址
}