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

表结构
230920_整合微信支付_第1张图片
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: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

230920_整合微信支付_第2张图片
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: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

230920_整合微信支付_第3张图片

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;

2. 微信支付时序图

2.1 参考文档

  • 微信支付开发文档首页
  • 微信_native二维码支付文档
  • 微信支付文档

2.2 二维码支付时序图

230920_整合微信支付_第4张图片

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)
    @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;               // 支付成功后的回调地址(学生自定义)
}

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();             	// 订单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;               // 支付成功后的回调地址(学生自定义)

}

4. 时序图2=>调统一下单api

前端直接调用支付平台的统一下单api
230920_整合微信支付_第5张图片

提交订单后,前端接收到支付链接,前端转化成二维码即可
230920_整合微信支付_第6张图片
支付中心:微信配置

# 微信支付二维码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 + "]";
	}
	
}

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

	@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 + "]";
	}


}

你可能感兴趣的:(Java架构师0到1(mkw),微信)