230920_整合微信支付
整合微信支付
- 1. 支付中心表结构
- 1.1 订单表orders
- 1.2 订单状态表order_status
- 1.3 订单商品关联表 OrderItems
- 2. 微信支付时序图
- 2.1 参考文档
- 2.2 二维码支付时序图
- 3. 时序图1=>生成订单环节
- 3.1 用户下单
- 3.1.1 电商平台
- 3.1.2 支付平台
- 4. 时序图2=>调统一下单api
- 5. 时序10=>支付成功后的微信支付异步通知
1. 支付中心表结构
1.1 订单表orders
表结构

sql脚本
/*Navicat Premium Data TransferSource Server : 1本地数据Source Server Type : MySQLSource Server Version : 80029Source Host : localhost:3306Source Schema : foodie-shop-devTarget Server Type : MySQLTarget Server Version : 80029File Encoding : 65001Date: 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;
}
1.2 订单状态表order_status

sql脚本
/*Navicat Premium Data TransferSource Server : 1本地数据Source Server Type : MySQLSource Server Version : 80029Source Host : localhost:3306Source Schema : foodie-shop-devTarget Server Type : MySQLTarget Server Version : 80029File Encoding : 65001Date: 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;
}
1.3 订单商品关联表 OrderItems

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 TransferSource Server : 1本地数据Source Server Type : MySQLSource Server Version : 80029Source Host : localhost:3306Source Schema : foodie-shop-devTarget Server Type : MySQLTarget Server Version : 80029File Encoding : 65001Date: 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;
2. 微信支付时序图
2.1 参考文档
- 微信支付开发文档首页
- 微信_native二维码支付文档
- 微信支付文档
2.2 二维码支付时序图

3. 时序图1=>生成订单环节
3.1 用户下单
1.电商平台保存订单信息=>2.订单信息发送到支付中心
3.1.1 电商平台
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)@Overridepublic OrderVO createOrder(SubmitOrderBO submitOrderBO) {String userId = submitOrderBO.getUserId();String addressId = submitOrderBO.getAddressId();String itemSpecIds = submitOrderBO.getItemSpecIds();Integer payMethod = submitOrderBO.getPayMethod();String leftMsg = submitOrderBO.getLeftMsg();// 包邮费用设置为0Integer 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. 构建自定义订单voOrderVO 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 {// 实现静态资源的映射@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/") // 映射swagger2.addResourceLocations("file:/workspaces/images/"); // 映射本地静态资源}@Beanpublic 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; // 商户方的发起用户的用户主键idprivate Integer amount; // 实际支付总金额(包含商户所支付的订单费邮费总额)private Integer payMethod; // 支付方式 1:微信 2:支付宝private String returnUrl; // 支付成功后的回调地址(学生自定义)
}
3.1.2 支付平台
接受电商平台订单信息,保存到数据库中
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(); // 订单idString merchantUserId = merchantOrdersBO.getMerchantUserId(); // 用户idInteger 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)@Overridepublic 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; // 商户方的发起用户的用户主键idprivate Integer amount; // 实际支付总金额(包含商户所支付的订单费邮费总额)private Integer payMethod; // 支付方式 1:微信 2:支付宝private String returnUrl; // 支付成功后的回调地址(学生自定义)}
4. 时序图2=>调统一下单api
前端直接调用支付平台的统一下单api

提交订单后,前端接收到支付链接,前端转化成二维码即可

支付中心:微信配置
# 微信支付二维码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("该订单不存在,或已经支付");}}
统一下单
官网:统一下单文档
@Autowiredprivate WXPayResource wxPayResource;/*** ==========================================* 微信预付单:指的是在自己的平台需要和微信进行支付交易生成的一个微信订单,称之为“预付单”* 订单:指的是自己的网站平台与用户之间交易生成的订单* * 1. 用户购买产品 --> 生成网站订单* 2. 用户支付 --> 网站在微信平台生成预付单* 3. 最终实际根据预付单的信息进行支付* ==========================================*/@Overridepublic 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转换为XMLString xml = XmlUtil.object2Xml(o, PreOrder.class);// 统一下单地址String url = wxPayResource.getPlaceOrderUrl();// 调用微信统一下单地址String returnXml = HttpUtil.sendPost(url, xml);// XML转换为ObjectPreOrderResult 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; // 公众账号IDprivate 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;}@Overridepublic 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 + "]";}}
5. 时序10=>支付成功后的微信支付异步通知
/*** 支付成功后的微信支付异步通知*/@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
@Overridepublic 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; // 公众账号IDprivate 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; // 代金券或立减优惠IDprivate 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;}@Overridepublic 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 + "]";}}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
