/*
Navicat Premium Data Transfer
Source Server : 1本地数据
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : foodie-shop-dev
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 20/09/2023 20:21:05
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单主键',
`merchant_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商户订单号',
`merchant_user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商户方的发起用户的用户主键id',
`amount` int(0) NOT NULL COMMENT '实际支付总金额(包含商户所支付的订单费邮费总额)',
`pay_method` int(0) NOT NULL COMMENT '支付方式',
`pay_status` int(0) NOT NULL COMMENT '支付状态 10:未支付 20:已支付 30:支付失败 40:已退款',
`come_from` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '从哪一端来的,比如从天天吃货这门实战过来的',
`return_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '支付成功后的通知地址,这个是开发者那一段的,不是第三方支付通知的地址',
`is_delete` int(0) NOT NULL COMMENT '逻辑删除状态;1: 删除 0:未删除',
`created_time` datetime(0) NOT NULL COMMENT '创建时间(成交时间)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单表;' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
oredrs实体类
package com.imooc.pojo;
import java.util.Date;
@Data
public class Orders {
/**
* 订单主键
*/
private String id;
/**
* 商户订单号
*/
private String merchantOrderId;
/**
* 商户方的发起用户的用户主键id
*/
private String merchantUserId;
/**
* 实际支付总金额(包含商户所支付的订单费邮费总额)
*/
private Integer amount;
/**
* 支付方式
*/
private Integer payMethod;
/**
* 支付状态 10:未支付 20:已支付 30:支付失败 40:已退款
*/
private Integer payStatus;
/**
* 从哪一端来的,比如从天天吃货这门实战过来的
*/
private String comeFrom;
/**
* 支付成功后的通知地址,这个是开发者那一段的,不是第三方支付通知的地址
*/
private String returnUrl;
/**
* 逻辑删除状态;1: 删除 0:未删除
*/
private Integer isDelete;
/**
* 创建时间(成交时间)
*/
private Date createdTime;
}
/*
Navicat Premium Data Transfer
Source Server : 1本地数据
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : foodie-shop-dev
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 20/09/2023 20:27:05
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for order_status
-- ----------------------------
DROP TABLE IF EXISTS `order_status`;
CREATE TABLE `order_status` (
`order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单ID;对应订单表的主键id',
`order_status` int(0) NOT NULL COMMENT '订单状态',
`created_time` datetime(0) NULL DEFAULT NULL COMMENT '订单创建时间;对应[10:待付款]状态',
`pay_time` datetime(0) NULL DEFAULT NULL COMMENT '支付成功时间;对应[20:已付款,待发货]状态',
`deliver_time` datetime(0) NULL DEFAULT NULL COMMENT '发货时间;对应[30:已发货,待收货]状态',
`success_time` datetime(0) NULL DEFAULT NULL COMMENT '交易成功时间;对应[40:交易成功]状态',
`close_time` datetime(0) NULL DEFAULT NULL COMMENT '交易关闭时间;对应[50:交易关闭]状态',
`comment_time` datetime(0) NULL DEFAULT NULL COMMENT '留言时间;用户在交易成功后的留言时间',
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单状态表;订单的每个状态更改都需要进行记录\n10:待付款 20:已付款,待发货 30:已发货,待收货(7天自动确认) 40:交易成功(此时可以评价)50:交易关闭(待付款时,用户取消 或 长时间未付款,系统识别后自动关闭)\n退货/退货,此分支流程不做,所以不加入' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
OrderStatus实体类
package com.imooc.pojo;
import java.util.Date;
@Data
public class OrderStatus {
/**
* 订单ID;对应订单表的主键id
*/
private String orderId;
/**
* 订单状态
*/
private Integer orderStatus;
/**
* 订单创建时间;对应[10:待付款]状态
*/
private Date createdTime;
/**
* 支付成功时间;对应[20:已付款,待发货]状态
*/
private Date payTime;
/**
* 发货时间;对应[30:已发货,待收货]状态
*/
private Date deliverTime;
/**
* 交易成功时间;对应[40:交易成功]状态
*/
private Date successTime;
/**
* 交易关闭时间;对应[50:交易关闭]状态
*/
private Date closeTime;
/**
* 留言时间;用户在交易成功后的留言时间
*/
private Date commentTime;
}
package com.imooc.pojo;
/**
* 订单商品关联表
*/
public class OrderItems {
/**
* 主键id
*/
private String id;
/**
* 归属订单id
*/
private String orderId;
/**
* 商品id
*/
private String itemId;
/**
* 商品图片
*/
private String itemImg;
/**
* 商品名称
*/
private String itemName;
/**
* 规格id
*/
private String itemSpecId;
/**
* 规格名称
*/
private String itemSpecName;
/**
* 成交价格
*/
private Integer price;
/**
* 购买数量
*/
private Integer buyCounts;
}
sql脚本
/*
Navicat Premium Data Transfer
Source Server : 1本地数据
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : foodie-shop-dev
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 20/09/2023 20:56:56
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for order_items
-- ----------------------------
DROP TABLE IF EXISTS `order_items`;
CREATE TABLE `order_items` (
`id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键id',
`order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '归属订单id',
`item_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品id',
`item_img` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品图片',
`item_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称',
`item_spec_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '规格id',
`item_spec_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '规格名称',
`price` int(0) NOT NULL COMMENT '成交价格',
`buy_counts` int(0) NOT NULL COMMENT '购买数量',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单商品关联表 ' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
1.电商平台保存订单信息=>2.订单信息发送到支付中心
web接口
@ApiOperation(value = "用户下单", notes = "用户下单", httpMethod = "POST")
@PostMapping("/create")
public IMOOCJSONResult create(
@RequestBody SubmitOrderBO submitOrderBO,
HttpServletRequest request,
HttpServletResponse response) {
if (submitOrderBO.getPayMethod() != PayMethod.WEIXIN.type
&& submitOrderBO.getPayMethod() != PayMethod.ALIPAY.type ) {
return IMOOCJSONResult.errorMsg("支付方式不支持!");
}
// 1. 创建订单
OrderVO orderVO = orderService.createOrder(submitOrderBO);
String orderId = orderVO.getOrderId();
// 2. 创建订单以后,移除购物车中已结算(已提交)的商品
/**
* 1001
* 2002 -> 用户购买
* 3003 -> 用户购买
* 4004
*/
// TODO 整合redis之后,完善购物车中的已结算商品清除,并且同步到前端的cookie
// CookieUtils.setCookie(request, response, FOODIE_SHOPCART, "", true);
// 3. 向支付中心发送当前订单,用于保存支付中心的订单数据
MerchantOrdersVO merchantOrdersVO = orderVO.getMerchantOrdersVO();
merchantOrdersVO.setReturnUrl(payReturnUrl);
// 为了方便测试购买,所以所有的支付金额都统一改为1分钱
merchantOrdersVO.setAmount(1);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("imoocUserId","imooc");
headers.add("password","imooc");
HttpEntity<MerchantOrdersVO> entity =
new HttpEntity<>(merchantOrdersVO, headers);
ResponseEntity<IMOOCJSONResult> responseEntity =
restTemplate.postForEntity(paymentUrl,
entity,
IMOOCJSONResult.class);
IMOOCJSONResult paymentResult = responseEntity.getBody();
if (paymentResult.getStatus() != 200) {
logger.error("发送错误:{}", paymentResult.getMsg());
return IMOOCJSONResult.errorMsg("支付中心订单创建失败,请联系管理员!");
}
return IMOOCJSONResult.ok(orderId);
}
业务层=>创建订单
/**
* 新增订单信息
* 1. 新增订单信息
* => 2. 新增订单状态信息
* => 3. 构建商户订单 用于传给支付中心
* @param submitOrderBO
* @return
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public OrderVO createOrder(SubmitOrderBO submitOrderBO) {
String userId = submitOrderBO.getUserId();
String addressId = submitOrderBO.getAddressId();
String itemSpecIds = submitOrderBO.getItemSpecIds();
Integer payMethod = submitOrderBO.getPayMethod();
String leftMsg = submitOrderBO.getLeftMsg();
// 包邮费用设置为0
Integer postAmount = 0;
String orderId = sid.nextShort();
UserAddress address = addressService.queryUserAddres(userId, addressId);
// 1. 新订单数据保存
Orders newOrder = new Orders();
newOrder.setId(orderId);
newOrder.setUserId(userId);
//收货信息
newOrder.setReceiverName(address.getReceiver());
newOrder.setReceiverMobile(address.getMobile());
newOrder.setReceiverAddress(address.getProvince() + " "
+ address.getCity() + " "
+ address.getDistrict() + " "
+ address.getDetail());
// newOrder.setTotalAmount();
// newOrder.setRealPayAmount();
newOrder.setPostAmount(postAmount);
newOrder.setPayMethod(payMethod);
newOrder.setLeftMsg(leftMsg);
newOrder.setIsComment(YesOrNo.NO.type);
newOrder.setIsDelete(YesOrNo.NO.type);
newOrder.setCreatedTime(new Date());
newOrder.setUpdatedTime(new Date());
// 2. 循环根据itemSpecIds保存订单商品信息表
String itemSpecIdArr[] = itemSpecIds.split(",");
Integer totalAmount = 0; // 商品原价累计
Integer realPayAmount = 0; // 优惠后的实际支付价格累计
for (String itemSpecId : itemSpecIdArr) {
// TODO 整合redis后,商品购买的数量重新从redis的购物车中获取
int buyCounts = 1;
// 2.1 根据规格id,查询规格的具体信息,主要获取价格
ItemsSpec itemSpec = itemService.queryItemSpecById(itemSpecId);
totalAmount += itemSpec.getPriceNormal() * buyCounts;
realPayAmount += itemSpec.getPriceDiscount() * buyCounts;
// 2.2 根据商品id,获得商品信息以及商品图片
String itemId = itemSpec.getItemId();
Items item = itemService.queryItemById(itemId);
String imgUrl = itemService.queryItemMainImgById(itemId);
// 2.3 循环保存子订单数据到数据库
String subOrderId = sid.nextShort();
OrderItems subOrderItem = new OrderItems();
subOrderItem.setId(subOrderId);
subOrderItem.setOrderId(orderId);
subOrderItem.setItemId(itemId);
subOrderItem.setItemName(item.getItemName());
subOrderItem.setItemImg(imgUrl);
subOrderItem.setBuyCounts(buyCounts);
subOrderItem.setItemSpecId(itemSpecId);
subOrderItem.setItemSpecName(itemSpec.getName());
subOrderItem.setPrice(itemSpec.getPriceDiscount());
orderItemsMapper.insert(subOrderItem);
// 2.4 在用户提交订单以后,规格表中需要扣除库存
itemService.decreaseItemSpecStock(itemSpecId, buyCounts);
}
newOrder.setTotalAmount(totalAmount);
newOrder.setRealPayAmount(realPayAmount);
ordersMapper.insert(newOrder);
// 3. 保存订单状态表
OrderStatus waitPayOrderStatus = new OrderStatus();
waitPayOrderStatus.setOrderId(orderId);
waitPayOrderStatus.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);
waitPayOrderStatus.setCreatedTime(new Date());
orderStatusMapper.insert(waitPayOrderStatus);
// 4. 构建商户订单,用于传给支付中心
MerchantOrdersVO merchantOrdersVO = new MerchantOrdersVO();
merchantOrdersVO.setMerchantOrderId(orderId);
merchantOrdersVO.setMerchantUserId(userId);
merchantOrdersVO.setAmount(realPayAmount + postAmount);
merchantOrdersVO.setPayMethod(payMethod);
// 5. 构建自定义订单vo
OrderVO orderVO = new OrderVO();
orderVO.setOrderId(orderId);
orderVO.setMerchantOrdersVO(merchantOrdersVO);
return orderVO;
}
其他:1 整合RestTemplate
package com.imooc.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 实现静态资源的映射
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/META-INF/resources/") // 映射swagger2
.addResourceLocations("file:/workspaces/images/"); // 映射本地静态资源
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
其他2: OrderVO订单信息
package com.imooc.pojo.vo;
public class OrderVO {
private String orderId;
private MerchantOrdersVO merchantOrdersVO;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public MerchantOrdersVO getMerchantOrdersVO() {
return merchantOrdersVO;
}
public void setMerchantOrdersVO(MerchantOrdersVO merchantOrdersVO) {
this.merchantOrdersVO = merchantOrdersVO;
}
}
其他3: MerchantOrdersVO对象
传递到支付中心的视图vo对象
package com.imooc.pojo.vo;
@Data
public class MerchantOrdersVO {
private String merchantOrderId; // 商户订单号
private String merchantUserId; // 商户方的发起用户的用户主键id
private Integer amount; // 实际支付总金额(包含商户所支付的订单费邮费总额)
private Integer payMethod; // 支付方式 1:微信 2:支付宝
private String returnUrl; // 支付成功后的回调地址(学生自定义)
}
接受电商平台订单信息,保存到数据库中
web接口
/**
* 接受商户订单信息,保存到自己的数据库
* @param merchantOrdersBO 商户订单信息
* @param request
* @param response
* @return
* @throws Exception
*/
@PostMapping("/createMerchantOrder")
public IMOOCJSONResult createMerchantOrder(@RequestBody MerchantOrdersBO merchantOrdersBO, HttpServletRequest request, HttpServletResponse response) throws Exception {
String merchantOrderId = merchantOrdersBO.getMerchantOrderId(); // 订单id
String merchantUserId = merchantOrdersBO.getMerchantUserId(); // 用户id
Integer amount = merchantOrdersBO.getAmount(); // 实际支付订单金额
Integer payMethod = merchantOrdersBO.getPayMethod(); // 支付方式
String returnUrl = merchantOrdersBO.getReturnUrl(); // 支付成功后的回调地址(学生自定义)
if (StringUtils.isBlank(merchantOrderId)) {
return IMOOCJSONResult.errorMsg("参数[orderId]不能为空");
}
if (StringUtils.isBlank(merchantUserId)) {
return IMOOCJSONResult.errorMsg("参数[userId]不能为空");
}
if (amount == null || amount < 1) {
return IMOOCJSONResult.errorMsg("参数[realPayAmount]不能为空并且不能小于1");
}
if (payMethod == null) {
return IMOOCJSONResult.errorMsg("参数[payMethod]不能为空并且不能小于1");
}
if (payMethod != PayMethod.WEIXIN.type && payMethod != PayMethod.ALIPAY.type) {
return IMOOCJSONResult.errorMsg("参数[payMethod]目前只支持微信支付或支付宝支付");
}
if (StringUtils.isBlank(returnUrl)) {
return IMOOCJSONResult.errorMsg("参数[returnUrl]不能为空");
}
// 保存传来的商户订单信息
boolean isSuccess = false;
try {
isSuccess = paymentOrderService.createPaymentOrder(merchantOrdersBO);
} catch (Exception e) {
e.printStackTrace();
IMOOCJSONResult.errorException(e.getMessage());
}
if (isSuccess) {
return IMOOCJSONResult.ok("商户订单创建成功!");
} else {
return IMOOCJSONResult.errorMsg("商户订单创建失败,请重试...");
}
}
保存订单数据接口
@Transactional(propagation=Propagation.REQUIRED)
@Override
public boolean createPaymentOrder(MerchantOrdersBO merchantOrdersBO) {
String id = sid.nextShort();
Orders paymentOrder = new Orders();
BeanUtils.copyProperties(merchantOrdersBO, paymentOrder);
paymentOrder.setId(id);
paymentOrder.setPayStatus(PaymentStatus.WAIT_PAY.type);
paymentOrder.setComeFrom("天天吃货");
paymentOrder.setIsDelete(YesOrNo.NO.type);
paymentOrder.setCreatedTime(new Date());
int result = ordersMapper.insert(paymentOrder);
return result == 1 ? true : false;
}
其他1: Orders对象
package com.imooc.pojo;
import java.util.Date;
@Data
public class Orders {
/**
* 订单主键
*/
private String id;
/**
* 商户订单号
*/
private String merchantOrderId;
/**
* 商户方的发起用户的用户主键id
*/
private String merchantUserId;
/**
* 实际支付总金额(包含商户所支付的订单费邮费总额)
*/
private Integer amount;
/**
* 支付方式
*/
private Integer payMethod;
/**
* 支付状态 10:未支付 20:已支付 30:支付失败 40:已退款
*/
private Integer payStatus;
/**
* 从哪一端来的,比如从天天吃货这门实战过来的
*/
private String comeFrom;
/**
* 支付成功后的通知地址,这个是开发者那一段的,不是第三方支付通知的地址
*/
private String returnUrl;
/**
* 逻辑删除状态;1: 删除 0:未删除
*/
private Integer isDelete;
/**
* 创建时间(成交时间)
*/
private Date createdTime;
}
其他2: MerchantOrdersBO对象
package com.imooc.pojo.bo;
@Data
public class MerchantOrdersBO {
private String merchantOrderId; // 商户订单号
private String merchantUserId; // 商户方的发起用户的用户主键id
private Integer amount; // 实际支付总金额(包含商户所支付的订单费邮费总额)
private Integer payMethod; // 支付方式 1:微信 2:支付宝
private String returnUrl; // 支付成功后的回调地址(学生自定义)
}
提交订单后,前端接收到支付链接,前端转化成二维码即可
支付中心:微信配置
# 微信支付二维码key
wxpay.qrcodeKey=wxpay_qrcode
# 微信支付二维码过期时间为<2小时(微信二维码code_url有效期为2小时)
wxpay.qrcodeExpire=7000
# 微信异步通知频率为15/15/30/180/1800/1800/1800/1800/3600 秒
# 公众账号ID
wxpay.appId=wx10516asde1h8ki
# 商户号
wxpay.merchantId=3411210594
# 商户秘钥
wxpay.secrectKey=4fkbcKhMcSxSXYZQc369nP1SI
# APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP, 即:服务器ip地址
wxpay.spbillCreateIp=127.0.0.1
# 接受微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数. (需配置)
# public static final String NOTIFY_URL = "http://1s7p978583.iok.la/pay/notice/wxpay.shtml";
#wxpay.notifyUrl=http://micmcq.natappfree.cc/payment/notice/wxpay
wxpay.notifyUrl=http://payment.t.mukewang.com/foodie-payment/payment/notice/wxpay
# 支付方式,取值如下
wxpay.tradeType=NATIVE
# 微信支付统一下单地址
wxpay.placeOrderUrl=https://api.mch.weixin.qq.com/pay/unifiedorder
package com.imooc.resource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
//@Configuration
@Component
@ConfigurationProperties(prefix="wxpay")
@PropertySource("classpath:wxpay.properties")
@Data
public class WXPayResource {
private String qrcodeKey;
private long qrcodeExpire;
private String appId;
private String merchantId;
private String secrectKey;
private String spbillCreateIp;
private String notifyUrl;
private String tradeType;
private String placeOrderUrl;
}
web接口
/**
* 获取微信扫码支付链接地址
* @param merchantOrderId 订单Id
* @param merchantUserId 用户Id
* @return
* @throws Exception
*/
@PostMapping(value="/getWXPayQRCode")
public IMOOCJSONResult getWXPayQRCode(String merchantOrderId, String merchantUserId) throws Exception{
// 根据订单ID和用户ID查询订单详情
Orders waitPayOrder = paymentOrderService.queryOrderByStatus(merchantUserId, merchantOrderId, PaymentStatus.WAIT_PAY.type);
// 商品描述
String body = "天天吃货-付款用户[" + merchantUserId + "]";
// 商户订单号
String out_trade_no = merchantOrderId;
// 从redis中去获得这笔订单的微信支付二维码,如果订单状态没有支付没有就放入,这样的做法防止用户频繁刷新而调用微信接口
if (waitPayOrder != null) {
String qrCodeUrl = redis.get(wxPayResource.getQrcodeKey() + ":" + merchantOrderId);
if (StringUtils.isEmpty(qrCodeUrl)) {
// 订单总金额,单位为分
String total_fee = String.valueOf(waitPayOrder.getAmount());
// 统一下单
PreOrderResult preOrderResult = wxOrderService.placeOrder(body, out_trade_no, total_fee);
qrCodeUrl = preOrderResult.getCode_url();
}
PaymentInfoVO paymentInfoVO = new PaymentInfoVO();
paymentInfoVO.setAmount(waitPayOrder.getAmount());
paymentInfoVO.setMerchantOrderId(merchantOrderId);
paymentInfoVO.setMerchantUserId(merchantUserId);
paymentInfoVO.setQrCodeUrl(qrCodeUrl);
redis.set(wxPayResource.getQrcodeKey() + ":" + merchantOrderId, qrCodeUrl, wxPayResource.getQrcodeExpire());
return IMOOCJSONResult.ok(paymentInfoVO);
} else {
return IMOOCJSONResult.errorMsg("该订单不存在,或已经支付");
}
}
统一下单
官网:统一下单文档
@Autowired
private WXPayResource wxPayResource;
/**
* ==========================================
* 微信预付单:指的是在自己的平台需要和微信进行支付交易生成的一个微信订单,称之为“预付单”
* 订单:指的是自己的网站平台与用户之间交易生成的订单
*
* 1. 用户购买产品 --> 生成网站订单
* 2. 用户支付 --> 网站在微信平台生成预付单
* 3. 最终实际根据预付单的信息进行支付
* ==========================================
*/
@Override
public PreOrderResult placeOrder(String body, String out_trade_no, String total_fee) throws Exception {
// 生成预付单对象
PreOrder o = new PreOrder();
// 生成随机字符串
String nonce_str = UUID.randomUUID().toString().trim().replaceAll("-", "");
o.setAppid(wxPayResource.getAppId());
o.setBody(body);
o.setMch_id(wxPayResource.getMerchantId());
o.setNotify_url(wxPayResource.getNotifyUrl());
o.setOut_trade_no(out_trade_no);
// 判断有没有输入订单总金额,没有输入默认1分钱
if (total_fee != null && !total_fee.equals("")) {
o.setTotal_fee(Integer.parseInt(total_fee));
} else {
o.setTotal_fee(1);
}
o.setNonce_str(nonce_str);
o.setTrade_type(wxPayResource.getTradeType());
o.setSpbill_create_ip(wxPayResource.getSpbillCreateIp());
SortedMap<Object, Object> p = new TreeMap<Object, Object>();
p.put("appid", wxPayResource.getAppId());
p.put("mch_id", wxPayResource.getMerchantId());
p.put("body", body);
p.put("nonce_str", nonce_str);
p.put("out_trade_no", out_trade_no);
p.put("total_fee", total_fee);
p.put("spbill_create_ip", wxPayResource.getSpbillCreateIp());
p.put("notify_url", wxPayResource.getNotifyUrl());
p.put("trade_type", wxPayResource.getTradeType());
// 获得签名
String sign = Sign.createSign("utf-8", p, wxPayResource.getSecrectKey());
o.setSign(sign);
// Object转换为XML
String xml = XmlUtil.object2Xml(o, PreOrder.class);
// 统一下单地址
String url = wxPayResource.getPlaceOrderUrl();
// 调用微信统一下单地址
String returnXml = HttpUtil.sendPost(url, xml);
// XML转换为Object
PreOrderResult preOrderResult = (PreOrderResult) XmlUtil.xml2Object(returnXml, PreOrderResult.class);
return preOrderResult;
}
其他1: 微信支付对象PreOrderResult
package com.imooc.wx.entity;
/**
*
* @Title: PreOrderResult.java
* @Package com.itzixi.wx.entity
* @Description: 微信支付 - 统一下单返回结果的封装entity
* Copyright: Copyright (c) 2016
* Company:FURUIBOKE.SCIENCE.AND.TECHNOLOGY
*
* @author leechenxiang
* @date 2017年8月31日 上午10:39:14
* @version V1.0
*/
public class PreOrderResult {
private String return_code; // 返回状态码
private String return_msg; // 返回信息
private String appid; // 公众账号ID
private String mch_id; // 商户号
private String device_info; // 设备号
private String nonce_str; // 随机字符串
private String sign; // 签名
private String result_code; // 业务结果
private String err_code; // 错误代码
private String err_code_des; // 错误代码描述
private String trade_type; // 交易类型
private String prepay_id; // 预支付交易会话标识
private String code_url; // 二维码链接
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getDevice_info() {
return device_info;
}
public void setDevice_info(String device_info) {
this.device_info = device_info;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
public String getErr_code() {
return err_code;
}
public void setErr_code(String err_code) {
this.err_code = err_code;
}
public String getErr_code_des() {
return err_code_des;
}
public void setErr_code_des(String err_code_des) {
this.err_code_des = err_code_des;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
public String getPrepay_id() {
return prepay_id;
}
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
public String getCode_url() {
return code_url;
}
public void setCode_url(String code_url) {
this.code_url = code_url;
}
@Override
public String toString() {
return "OrderReturn [return_code=" + return_code + ", return_msg="
+ return_msg + ", appid=" + appid + ", mch_id=" + mch_id
+ ", device_info=" + device_info + ", nonce_str=" + nonce_str
+ ", sign=" + sign + ", result_code=" + result_code
+ ", err_code=" + err_code + ", err_code_des=" + err_code_des
+ ", trade_type=" + trade_type + ", prepay_id=" + prepay_id
+ ", code_url=" + code_url + "]";
}
}
/**
* 支付成功后的微信支付异步通知
*/
@RequestMapping(value="/wxpay")
public void wxpay(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("支付成功后的微信支付异步通知");
// 获取微信支付结果
PayResult payResult = wxOrderService.getWxPayResult(request.getInputStream());
boolean isPaid = payResult.getReturn_code().equals("SUCCESS") ? true : false;
// 查询该笔订单在微信那边是否成功支付
// 支付成功,商户处理后同步返回给微信参数
PrintWriter writer = response.getWriter();
if (isPaid) {
String merchantOrderId = payResult.getOut_trade_no(); // 商户订单号
String wxFlowId = payResult.getTransaction_id();
Integer paidAmount = payResult.getTotal_fee();
// System.out.println("================================= 支付成功 =================================");
// ====================== 操作商户自己的业务,比如修改订单状态等 start ==========================
String merchantReturnUrl = paymentOrderService.updateOrderPaid(merchantOrderId, paidAmount);
// ============================================ 业务结束, end ==================================
log.info("************* 支付成功(微信支付异步通知) - 时间: {} *************", DateUtil.getCurrentDateString(DateUtil.DATETIME_PATTERN));
log.info("* 商户订单号: {}", merchantOrderId);
log.info("* 微信订单号: {}", wxFlowId);
log.info("* 实际支付金额: {}", paidAmount);
log.info("*****************************************************************************");
// 通知天天吃货服务端订单已支付
// String url = "http://192.168.1.2:8088/orders/notifyMerchantOrderPaid";
MultiValueMap<String, String> requestEntity = new LinkedMultiValueMap<>();
requestEntity.add("merchantOrderId", merchantOrderId);
String httpStatus = restTemplate.postForObject(merchantReturnUrl, requestEntity, String.class);
log.info("*** 通知天天吃货后返回的状态码 httpStatus: {} ***", httpStatus);
// 通知微信已经收到消息,不要再给我发消息了,否则微信会10连击调用本接口
String noticeStr = setXML("SUCCESS", "");
writer.write(noticeStr);
writer.flush();
} else {
System.out.println("================================= 支付失败 =================================");
// 支付失败
String noticeStr = setXML("FAIL", "");
writer.write(noticeStr);
writer.flush();
}
}
解析HttpServletRequest的getInputStream
@Override
public PayResult getWxPayResult(InputStream inStream) throws Exception {
BufferedReader in = null;
String result = "";
in = new BufferedReader(
new InputStreamReader(inStream));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
PayResult pr = (PayResult)XmlUtil.xml2Object(result, PayResult.class);
// System.out.println(pr.toString());
return pr;
}
XmlUtil工具
package com.imooc.wx.util;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import java.io.InputStream;
public class XmlUtil {
// private static XStream xstream;
// static {
// xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
// }
/**
*
* @Description: xml字符串转换为对象
* @param inputXml
* @param type
* @return
* @throws Exception
*
* @author imooc
* @date 2019年8月31日 下午4:52:13
*/
public static Object xml2Object(String inputXml, Class<?> type) throws Exception {
if (null == inputXml || "".equals(inputXml)) {
return null;
}
XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
xstream.alias("xml", type);
return xstream.fromXML(inputXml);
}
/**
*
* @Description: 从inputStream中读取对象
* @param inputStream
* @param type
* @return
* @throws Exception
*
* @author imooc
* @date 2019年8月31日 下午4:52:29
*/
public static Object xml2Object(InputStream inputStream, Class<?> type) throws Exception {
if (null == inputStream) {
return null;
}
XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
xstream.alias("xml", type);
return xstream.fromXML(inputStream, type);
}
/**
*
* @Description: 对象转换为xml字符串
* @param ro
* @param types
* @return
* @throws Exception
*
* @author imooc
* @date 2019年8月31日 下午4:52:45
*/
public static String object2Xml(Object ro, Class<?> types) throws Exception {
if (null == ro) {
return null;
}
XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
xstream.alias("xml", types);
return xstream.toXML(ro);
}
}
PayResult支付对象
package com.imooc.wx.entity;
/**
*
* @Title: PayResult.java
* @Package com.itzixi.wx.entity
* @Description: 支付结果封装类
* Copyright: Copyright (c) 2016
* Company:FURUIBOKE.SCIENCE.AND.TECHNOLOGY
*
* @author imooc
* @date 2019年8月31日 下午4:26:14
* @version V1.0
*/
public class PayResult {
private String return_code; // 返回状态码
private String appid; // 公众账号ID
private String mch_id; // 商户号
private String nonce_str; // 随机字符串
private String sign; // 签名
private String result_code; // 业务结果
private String openid; // 用户标识
private String trade_type; // 交易类型
private String bank_type; // 付款银行
private int total_fee; // 总金额
private int cash_fee; // 现金支付金额
private String transaction_id; // 微信支付订单号
private String out_trade_no; // 商户订单号
private String time_end; // 支付完成时间
private String return_msg; // 返回信息
private String device_info; // 设备号
private String err_code; // 错误代码
private String err_code_des; // 错误代码描述
private String is_subscribe; // 是否关注公众账号
private String fee_type; // 货币种类
private String cash_fee_type; // 现金支付货币类型
private String coupon_fee; // 代金券或立减优惠金额
private String coupon_count; // 代金券或立减优惠使用数量
private String coupon_id_$n; // 代金券或立减优惠ID
private String coupon_fee_$n; // 单个代金券或立减优惠支付金额
private String attach; // 商家数据包
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
public String getBank_type() {
return bank_type;
}
public void setBank_type(String bank_type) {
this.bank_type = bank_type;
}
public int getTotal_fee() {
return total_fee;
}
public void setTotal_fee(int total_fee) {
this.total_fee = total_fee;
}
public int getCash_fee() {
return cash_fee;
}
public void setCash_fee(int cash_fee) {
this.cash_fee = cash_fee;
}
public String getTransaction_id() {
return transaction_id;
}
public void setTransaction_id(String transaction_id) {
this.transaction_id = transaction_id;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getTime_end() {
return time_end;
}
public void setTime_end(String time_end) {
this.time_end = time_end;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public String getDevice_info() {
return device_info;
}
public void setDevice_info(String device_info) {
this.device_info = device_info;
}
public String getErr_code() {
return err_code;
}
public void setErr_code(String err_code) {
this.err_code = err_code;
}
public String getErr_code_des() {
return err_code_des;
}
public void setErr_code_des(String err_code_des) {
this.err_code_des = err_code_des;
}
public String getIs_subscribe() {
return is_subscribe;
}
public void setIs_subscribe(String is_subscribe) {
this.is_subscribe = is_subscribe;
}
public String getFee_type() {
return fee_type;
}
public void setFee_type(String fee_type) {
this.fee_type = fee_type;
}
public String getCash_fee_type() {
return cash_fee_type;
}
public void setCash_fee_type(String cash_fee_type) {
this.cash_fee_type = cash_fee_type;
}
public String getCoupon_fee() {
return coupon_fee;
}
public void setCoupon_fee(String coupon_fee) {
this.coupon_fee = coupon_fee;
}
public String getCoupon_count() {
return coupon_count;
}
public void setCoupon_count(String coupon_count) {
this.coupon_count = coupon_count;
}
public String getCoupon_id_$n() {
return coupon_id_$n;
}
public void setCoupon_id_$n(String coupon_id_$n) {
this.coupon_id_$n = coupon_id_$n;
}
public String getCoupon_fee_$n() {
return coupon_fee_$n;
}
public void setCoupon_fee_$n(String coupon_fee_$n) {
this.coupon_fee_$n = coupon_fee_$n;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
@Override
public String toString() {
return "OrderMessage [return_code=" + return_code + ", appid=" + appid
+ ", mch_id=" + mch_id + ", nonce_str=" + nonce_str + ", sign="
+ sign + ", result_code=" + result_code + ", openid=" + openid
+ ", trade_type=" + trade_type + ", bank_type=" + bank_type
+ ", total_fee=" + total_fee + ", cash_fee=" + cash_fee
+ ", transaction_id=" + transaction_id + ", out_trade_no="
+ out_trade_no + ", time_end=" + time_end + ", return_msg="
+ return_msg + ", device_info=" + device_info + ", err_code="
+ err_code + ", err_code_des=" + err_code_des
+ ", is_subscribe=" + is_subscribe + ", fee_type=" + fee_type
+ ", cash_fee_type=" + cash_fee_type + ", coupon_fee="
+ coupon_fee + ", coupon_count=" + coupon_count
+ ", coupon_id_$n=" + coupon_id_$n + ", coupon_fee_$n="
+ coupon_fee_$n + ", attach=" + attach + "]";
}
}