苍穹外卖08(地址簿功能,用户下单功能,订单支付访问微信平台接口JSAPI全过程,内网穿透Cpolar)

目录

一、导入地址簿功能代码

1. 需求分析和设计

1 产品原型

2 接口设计

2. 代码导入

3. 功能测试

二、用户下单

1. 需求分析和设计

1 产品原型

2 接口设计

3 表设计

2. 代码开发

1 DTO设计

2 VO设计

3 开发代码

3. 功能测试

三、订单支付

1 微信支付介绍

1 微信支付介绍

2 微信支付需要做的准备

3 微信小程序支付流程

4 微信支付相关接口

5 小结

2 微信支付准备工作

1 使用Cpolar开启内网穿透

1 Cpolar注册与下载安装

2 cpolar指定authtoken

3 获取临时域名

4 验证临时域名有效性

2 准备微信支付相关的参数

1 准备支付平台证书和私钥文件

2 准备其它相关参数

3 代码导入

1 读取配置参数

2 支付功能

3 支付成功的回调

4 功能测试

5.小结


一、导入地址簿功能代码

1. 需求分析和设计

1 产品原型

地址簿,指的是消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址

对于地址簿管理,我们需要实现以下几个功能:

  • 查询地址列表

  • 新增地址

  • 修改地址

  • 删除地址

  • 设置默认地址

  • 查询默认地址

2 接口设计

根据上述原型图先粗粒度设计接口,共包含7个接口。

接口设计:

  • 新增地址

  • 查询登录用户所有地址

  • 查询默认地址

  • 根据id修改地址

  • 根据id删除地址

  • 根据id查询地址

  • 设置默认地址

接下来细粒度分析每个接口,明确每个接口的请求方式、请求路径、传入参数和返回值。

1). 新增地址

2). 查询登录用户所有地址

3). 查询默认地址

4). 修改地址

5). 根据id删除地址

6). 根据id查询地址

7). 设置默认地址

2. 代码导入

//AddressBookController

package com.sky.controller.user;

import com.sky.context.BaseContext;
import com.sky.entity.AddressBook;
import com.sky.result.Result;
import com.sky.service.AddressBookService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/user/addressBook")
@Api(tags = "C端地址簿接口")
public class AddressBookController {

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 查询当前登录用户的所有地址信息
     *
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("查询当前登录用户的所有地址信息")
    public Result<List<AddressBook>> list() {
        AddressBook addressBook = new AddressBook();
        addressBook.setUserId(BaseContext.getCurrentId());
        List<AddressBook> list = addressBookService.list(addressBook);
        return Result.success(list);
    }

    /**
     * 新增地址
     *
     * @param addressBook
     * @return
     */
    @PostMapping
    @ApiOperation("新增地址")
    public Result save(@RequestBody AddressBook addressBook) {
        addressBookService.save(addressBook);
        return Result.success();
    }

    @GetMapping("/{id}")
    @ApiOperation("根据id查询地址")
    public Result<AddressBook> getById(@PathVariable Long id) {
        AddressBook addressBook = addressBookService.getById(id);
        return Result.success(addressBook);
    }

    /**
     * 根据id修改地址
     *
     * @param addressBook
     * @return
     */
    @PutMapping
    @ApiOperation("根据id修改地址")
    public Result update(@RequestBody AddressBook addressBook) {
        addressBookService.update(addressBook);
        return Result.success();
    }

    /**
     * 设置默认地址
     *
     * @param addressBook
     * @return
     */
    @PutMapping("/default")
    @ApiOperation("设置默认地址")
    public Result setDefault(@RequestBody AddressBook addressBook) {
        addressBookService.setDefault(addressBook);
        return Result.success();
    }

    /**
     * 根据id删除地址
     *
     * @param id
     * @return
     */
    @DeleteMapping
    @ApiOperation("根据id删除地址")
    public Result deleteById(Long id) {
        addressBookService.deleteById(id);
        return Result.success();
    }

    /**
     * 查询默认地址
     */
    @GetMapping("default")
    @ApiOperation("查询默认地址")
    public Result<AddressBook> getDefault() {
        //SQL:select * from address_book where user_id = ? and is_default = 1
        AddressBook addressBook = new AddressBook();
        addressBook.setIsDefault(1);
        addressBook.setUserId(BaseContext.getCurrentId());
        List<AddressBook> list = addressBookService.list(addressBook);

        if (list != null && list.size() == 1) {
            return Result.success(list.get(0));
        }

        return Result.error("没有查询到默认地址");
    }

}


----------------------
//AddressBookService


package com.sky.service;

import com.sky.entity.AddressBook;
import java.util.List;

public interface AddressBookService {

    List<AddressBook> list(AddressBook addressBook);

    void save(AddressBook addressBook);

    AddressBook getById(Long id);

    void update(AddressBook addressBook);

    void setDefault(AddressBook addressBook);

    void deleteById(Long id);

}


-------------------
//AddressBookServiceImpl

package com.sky.service.impl;

import com.sky.context.BaseContext;
import com.sky.entity.AddressBook;
import com.sky.mapper.AddressBookMapper;
import com.sky.service.AddressBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@Slf4j
public class AddressBookServiceImpl implements AddressBookService {
    @Autowired
    private AddressBookMapper addressBookMapper;

    /**
     * 条件查询
     *
     * @param addressBook
     * @return
     */
    public List<AddressBook> list(AddressBook addressBook) {
        return addressBookMapper.list(addressBook);
    }

    /**
     * 新增地址
     *
     * @param addressBook
     */
    public void save(AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        addressBook.setIsDefault(0);
        addressBookMapper.insert(addressBook);
    }

    /**
     * 根据id查询
     *
     * @param id
     * @return
     */
    public AddressBook getById(Long id) {
        AddressBook addressBook = addressBookMapper.getById(id);
        return addressBook;
    }

    /**
     * 根据id修改地址
     *
     * @param addressBook
     */
    public void update(AddressBook addressBook) {
        addressBookMapper.update(addressBook);
    }

    /**
     * 设置默认地址
     *
     * @param addressBook
     */
    @Transactional
    public void setDefault(AddressBook addressBook) {
        //1、将当前用户的所有地址修改为非默认地址 update address_book set is_default = ? where user_id = ?
        addressBook.setIsDefault(0);
        addressBook.setUserId(BaseContext.getCurrentId());
        addressBookMapper.updateIsDefaultByUserId(addressBook);

        //2、将当前地址改为默认地址 update address_book set is_default = ? where id = ?
        addressBook.setIsDefault(1);
        addressBookMapper.update(addressBook);
    }

    /**
     * 根据id删除地址
     *
     * @param id
     */
    public void deleteById(Long id) {
        addressBookMapper.deleteById(id);
    }

}


--------------------
//AddressBookMapper

package com.sky.mapper;

import com.sky.entity.AddressBook;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface AddressBookMapper {

    /**
     * 条件查询
     * @param addressBook
     * @return
     */
    List<AddressBook> list(AddressBook addressBook);

    /**
     * 新增
     * @param addressBook
     */
    @Insert("insert into address_book" +
            "        (user_id, consignee, phone, sex, province_code, province_name, city_code, city_name, district_code," +
            "         district_name, detail, label, is_default)" +
            "        values (#{userId}, #{consignee}, #{phone}, #{sex}, #{provinceCode}, #{provinceName}, #{cityCode}, #{cityName}," +
            "                #{districtCode}, #{districtName}, #{detail}, #{label}, #{isDefault})")
    void insert(AddressBook addressBook);

    /**
     * 根据id查询
     * @param id
     * @return
     */
    @Select("select * from address_book where id = #{id}")
    AddressBook getById(Long id);

    /**
     * 根据id修改
     * @param addressBook
     */
    void update(AddressBook addressBook);

    /**
     * 根据 用户id修改 是否默认地址
     * @param addressBook
     */
    @Update("update address_book set is_default = #{isDefault} where user_id = #{userId}")
    void updateIsDefaultByUserId(AddressBook addressBook);

    /**
     * 根据id删除地址
     * @param id
     */
    @Delete("delete from address_book where id = #{id}")
    void deleteById(Long id);

}


------------------
//AddressBookMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.AddressBookMapper">

    <select id="list" resultType="AddressBook">
        select * from address_book
        <where>
            <if test="userId != null">
                and user_id = #{userId}
            </if>
            <if test="phone != null">
                and phone = #{phone}
            </if>
            <if test="isDefault != null">
                and is_default = #{isDefault}
            </if>
        </where>
    </select>

    <update id="update" >
        update address_book
        <set>
            <if test="consignee != null">
                consignee = #{consignee},
            </if>
            <if test="sex != null">
                sex = #{sex},
            </if>
            <if test="phone != null">
                phone = #{phone},
            </if>
            <if test="detail != null">
                detail = #{detail},
            </if>
            <if test="label != null">
                label = #{label},
            </if>
            <if test="isDefault != null">
                is_default = #{isDefault},
            </if>
        </set>
        where id = #{id}
    </update>

</mapper>

3. 功能测试

可以通过如下方式进行测试:

  • 查看控制台sql和数据库中的数据变化

  • Swagger接口文档测试

  • 前后端联调

我们直接使用前后端联调测试:

启动后台服务,编译小程序

登录进入首页-->进入个人中心-->进入地址管理

查看收货地址:

在查看数据库

二、用户下单

1. 需求分析和设计

1 产品原型

用户下单业务说明: 在电商系统中,用户是通过下单的方式通知商家,用户已经购买了商品,需要商家进行备货和发货。 用户下单后会产生订单相关数据,订单数据需要能够体现如下信息:

用户将菜品或者套餐加入购物车后,可以点击购物车中的 "去结算" 按钮,页面跳转到订单确认页面,点击 "去支付" 按钮则完成下单操作。

用户点餐业务流程(效果图):

2 接口设计

接口分析:

3 表设计

用户下单业务对应的数据表为orders表和order_detail表(一对多关系,一个订单关联多个订单明细):

具体的表结构如下:

1). orders订单表

2). order_detail订单明细表

说明:用户提交订单时,需要往订单表orders中插入一条记录,并且需要往order_detail中插入一条或多条记录。

2. 代码开发

1 DTO设计

//在sky-pojo模块,OrdersSubmitDTO.java已定义

package com.sky.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
public class OrdersSubmitDTO implements Serializable {
    //地址簿id
    private Long addressBookId;
    //付款方式
    private int payMethod;
    //备注
    private String remark;
    //预计送达时间
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime estimatedDeliveryTime;
    //配送状态  1立即送出  0选择具体时间
    private Integer deliveryStatus;
    //餐具数量
    private Integer tablewareNumber;
    //餐具数量状态  1按餐量提供  0选择具体数量
    private Integer tablewareStatus;
    //打包费
    private Integer packAmount;
    //总金额
    private BigDecimal amount;
}

2 VO设计

//在sky-pojo模块,OrderSubmitVO.java已定义

package com.sky.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSubmitVO implements Serializable {
    //订单id
    private Long id;
    //订单号
    private String orderNumber;
    //订单金额
    private BigDecimal orderAmount;
    //下单时间
    private LocalDateTime orderTime;
}

3 开发代码

//创建UserOrderController并提供用户下单方法:

package com.sky.controller.user;

import com.sky.dto.OrdersSubmitDTO;
import com.sky.result.Result;
import com.sky.service.OrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Api(tags = "用户下单相关接口-C端")
@RequestMapping("/user/order")
public class UserOrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping("/submit")
    @ApiOperation("用户下单")
    public Result submitOrder(@RequestBody OrdersSubmitDTO dto){
        return orderService.submitOrder(dto);
    }
}


------------------
//创建OrderService接口,并声明用户下单方法:

package com.sky.service;

import com.sky.dto.OrdersSubmitDTO;
import com.sky.result.Result;


public interface OrderService {
    /**
     * 用户下单
     * @param dto
     * @return
     */
    Result submitOrder(OrdersSubmitDTO dto);
}


-----------------
//创建OrderServiceImpl实现OrderService接口:

package com.sky.service.impl;

import com.sky.constant.MessageConstant;
import com.sky.context.BaseContext;
import com.sky.dto.OrdersSubmitDTO;
import com.sky.entity.AddressBook;
import com.sky.entity.OrderDetail;
import com.sky.entity.Orders;
import com.sky.entity.ShoppingCart;
import com.sky.exception.AddressBookBusinessException;
import com.sky.exception.ShoppingCartBusinessException;
import com.sky.mapper.AddressBookMapper;
import com.sky.mapper.OrderDetailMapper;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.ShoppingCartMapper;
import com.sky.result.Result;
import com.sky.service.OrderService;
import com.sky.vo.OrderSubmitVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private AddressBookMapper addressBookMapper;
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Override
    @Transactional
    public Result submitOrder(OrdersSubmitDTO dto) {
        //1. 校验数据
        //  如果地址id对应的地址不存在,抛出异常
        AddressBook addressBook = addressBookMapper.getById(dto.getAddressBookId());
        if (addressBook == null) {
            throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
        }
        //  如果购物车里没有数据,抛出异常
        Long currentUser = BaseContext.getCurrentId();
        List<ShoppingCart> shoppingCarts = shoppingCartMapper.selectListByUser(currentUser);
        if (shoppingCarts == null || shoppingCarts.size()==0) {
            throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
        }

        //2. 保存订单信息
        Orders orders = new Orders();
        BeanUtils.copyProperties(dto, orders);
        //  订单号
        orders.setNumber(String.valueOf(System.currentTimeMillis()));
        //  订单状态
        orders.setStatus(Orders.PENDING_PAYMENT);
        //  下单人
        orders.setUserId(currentUser);
        //  下单时间
        orders.setOrderTime(LocalDateTime.now());
        //  支付状态
        orders.setPayStatus(Orders.UN_PAID);
        //  用户名(略掉了..)
        //  手机号
        orders.setPhone(addressBook.getPhone());
        //  地址
        orders.setAddress(addressBook.getDetail());
        //  收货人
        orders.setConsignee(addressBook.getConsignee());

        orderMapper.insert(orders);

        //3. 保存订单明细
        List<OrderDetail> orderDetailList = new ArrayList<>();
        for (ShoppingCart shoppingCart : shoppingCarts) {
            OrderDetail orderDetail = new OrderDetail();
            BeanUtils.copyProperties(shoppingCart, orderDetail);
            orderDetail.setOrderId(orders.getId());
            orderDetailList.add(orderDetail);
        }
        orderDetailMapper.batchInsert(orderDetailList);

        //4. 清空购物车
        shoppingCartMapper.deleteByUser(currentUser);

        //5. 封装返回值
        OrderSubmitVO vo = OrderSubmitVO.builder()
                .id(orders.getId())
                .orderTime(orders.getOrderTime())
                .orderAmount(orders.getAmount())
                .orderNumber(orders.getNumber())
                .build();
        return Result.success(vo);
    }
}


---------------------
//创建OrderMapper接口和对应的xml映射文件:
//OrderMapper.java

package com.sky.mapper;

import com.sky.entity.Orders;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;

@Mapper
public interface OrderMapper {
    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into orders" +
            "        (number, status, user_id, address_book_id, order_time, checkout_time, pay_method, pay_status, amount, remark," +
            "         phone, address, consignee, estimated_delivery_time, delivery_status, pack_amount, tableware_number," +
            "         tableware_status)" +
            "        values (#{number}, #{status}, #{userId}, #{addressBookId}, #{orderTime}, #{checkoutTime}, #{payMethod}," +
            "                #{payStatus}, #{amount}, #{remark}, #{phone}, #{address}, #{consignee}," +
            "                #{estimatedDeliveryTime}, #{deliveryStatus}, #{packAmount}, #{tablewareNumber}, #{tablewareStatus})")
    void insert(Orders orders);
}


---------------------
//OrderDetailMapper

package com.sky.mapper;

import com.sky.entity.OrderDetail;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface OrderDetailMapper {
    void batchInsert(List<OrderDetail> orderDetailList);
}


--------------------
//OrderDetailMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sky.mapper.OrderDetailMapper">
    <insert id="batchInsert">
        insert into order_detail (name, image, order_id, dish_id, setmeal_id, dish_flavor, number, amount) values 
        <foreach collection="orderDetailList" item="od" separator=",">
            (#{od.name},#{od.image},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},#{od.number},#{od.amount})
        </foreach>
    </insert>
</mapper>


3. 功能测试

登录小程序,完成下单操作

下单操作时,同时会删除购物车中的数据

查看数据库表

三、订单支付

加密: 对数据进行掩饰,有加密就有解密
签名:不具备任何加密效果,仅仅是为了防篡改。如果数据被篡改,通过校验签名可以发现

1 微信支付介绍

1 微信支付介绍

前面的课程已经实现了用户下单,那接下来就是订单支付,就是完成付款功能。支付大家应该都不陌生了,在现实生活中经常购买商品并且使用支付功能来付款,在付款的时候可能使用比较多的就是微信支付和支付宝支付了。在苍穹外卖项目中,选择的就是微信支付这种支付方式。

要实现微信支付就需要注册微信支付的一个商户号,这个商户号是必须要有一家企业并且有正规的营业执照。只有具备了这些资质之后,才可以去注册商户号,才能开通支付权限。

个人不具备这种资质,所以我们在学习微信支付时,最重要的是了解微信支付的流程,并且能够阅读微信官方提供的接口文档,能够和第三方支付平台对接起来就可以了。

微信支付产品:

参考:产品中心 - 微信支付商户平台

本项目选择小程序支付

微信支付接入流程:

2 微信支付需要做的准备

如果一个商户的小程序系统需要实现微信支付,需要做以下准备:

  1. 注册一个小程序,获取AppID

  2. 注册一个商户号,获取mchid。需要提供个人身份证、经营许可证、银行卡帐户

  3. 将AppID和mchid绑定起来

  4. 配置API Key密钥。用于对证书的解密,以及 和微信平台交互中 对数据的加密与解密

  5. 下载并配置商户证书

  6. 小程序开通微信支付功能

目前这些准备工作,我们都已经做好。但是因为小程序AppID的唯一性,大家的AppID不能和商户号绑定;因此大家并不能实际完成支付功能,对于接下来的支付功能,大家要以“理解微信支付流程”的主要目标

3 微信小程序支付流程

参考文档:开发指引-小程序支付 | 微信支付商户平台文档中心

4 微信支付相关接口

步骤4:用户下单发起支付,商户可通过JSAPI下单创建支付订单。

步骤9:商户小程序内使用小程序调起支付API(wx.requestPayment)发起微信支付,详见小程序API文档

步骤16:用户支付成功后,商户可接收到微信支付支付结果通知支付通知API

步骤21:商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。

JSAPI下单接口

JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单(对应时序图的第5步)

小程序调起支付API

微信小程序调起支付:通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付(对应时序图的第10步)

支付通知

5 小结

微信支付需要做的事情:去官网上查看文档,根据文档进行操作

大致的步骤:

  1. 申请小程序AppID

  2. 注册商户号mchid

  3. 给商户号配置相关密钥,并下载证书

  4. 把小程序AppID和商户号mchid进行绑定

2 微信支付准备工作

1 使用Cpolar开启内网穿透

微信后台会调用到商户系统给推送支付的结果,在这里我们就会遇到一个问题,就是微信后台怎么就能调用到我们这个商户系统呢?因为这个调用过程,其实本质上也是一个HTTP请求。

目前,商户系统它的ip地址就是当前自己电脑的ip地址,只是一个局域网内的ip地址,微信后台无法调用到。

解决:内网穿透。通过cpolar软件可以获得一个临时域名,而这个临时域名是一个公网ip,这样,微信后台就可以请求到商户系统了。

1 Cpolar注册与下载安装

打开cpolar官网,注册一下帐号,使用0元套餐即可:cpolar - secure introspectable tunnels to localhost

然后下载Cpolar软件(在资料中已提供,可无需下载。)

安装过程中,一直下一步即可

2 cpolar指定authtoken

复制authtoken:

执行命令:

3 获取临时域名

注意:临时域名并非长期有效,在每次执行cpolar http 端口命令时会生成新的域名

在cmd里执行命令:cpolar http 8080。 命令的语法是:cpolar http 后台服务端口

  • 这里的“后台服务端口”,是指我们的AkyApplication启动时的端口。我们的服务端口是8080,所以写8080即可

然后就可以看到,Cpolar给我们生成的临时域名了

  • 下图中的Forwarding后边的域名就是我们的临时域名了。使用https或http的都行

4 验证临时域名有效性

我们可以使用临时域名访问一下Swagger的地址,如果能访问到,就说明临时域名生效了

  • Swagger的原始地址是:http://localhost:8080/doc.html

  • 使用临时域名访问地址:临时域名/doc.html

2 准备微信支付相关的参数

1 准备支付平台证书和私钥文件

完成微信支付有两个关键的步骤:

  • 第一个就是需要在商户系统当中调用微信后台的一个下单接口,就是生成预支付交易单。

  • 第二个就是支付成功之后微信后台会给推送消息。

这两个接口数据的安全性,要求其实是非常高的。

解决:微信提供的方式就是对数据进行加密、解密、签名多种方式。要完成数据加密解密,需要提前准备相应的一些文件,其实就是一些证书。

获取微信支付平台证书、商户私钥文件:

在后绪程序开发过程中,就会使用到这两个文件,需要提前把这两个文件准备好。

2 准备其它相关参数

sky:
  wechat:
    #AppID和密钥
    appid: 自己的  
    secret: 自己的

    #商户号
    mchid : 1561414331
    mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606

    #私钥文件和平台证书
    #  apiclient_key.pem文件的路径
    privateKeyFilePath: C:\programs\ideaProjects\pay\apiclient_key.pem
    apiV3Key: CZBK51236435wxpay435434323FFDuv3
    #  证书文件路径
    weChatPayCertFilePath: C:\programs\ideaProjects\pay\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem
    
    #支付成功后的回调地址
    notifyUrl: 临时域名/notify/paySuccess
    #退款成功后的回调地址
    refundNotifyUrl: 临时域名/notify/refundSuccess

3 代码导入

说明:大家的AppID不能与商户号绑定,所以不能完成支付。这里的重点在于理解支付的流程代码

导入资料中的微信支付功能代码即可

1 读取配置参数

yml配置文件配置,在上面的块引用里

2 支付功能

//WeChatProperties 读取配置(已定义)

package com.sky.properties;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {

    private String appid; //小程序的appid
    private String secret; //小程序的秘钥
    private String mchid; //商户号
    private String mchSerialNo; //商户API证书的证书序列号
    private String privateKeyFilePath; //商户私钥文件
    private String apiV3Key; //证书解密的密钥
    private String weChatPayCertFilePath; //平台证书
    private String notifyUrl; //支付成功的回调地址
    private String refundNotifyUrl; //退款成功的回调地址
}


-------------------
//修改UserOrderController
//增加方法

/**
 * 订单支付
 *
 * @param ordersPaymentDTO
 * @return
 */
@PutMapping("/payment")
@ApiOperation("订单支付")
public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
    log.info("订单支付:{}", ordersPaymentDTO);
    OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
    log.info("生成预支付交易单:{}", orderPaymentVO);
    return Result.success(orderPaymentVO);
}


-------------------
// 修改OrderService
//增加方法

/**
 * 订单支付
 * @param ordersPaymentDTO
 * @return
 */
OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;


-------------------
//修改OrderServiceImpl
//增加方法

@Autowired
private UserMapper userMapper;
@Autowired
private WeChatPayUtil weChatPayUtil;

/**
 * 订单支付
 *
 * @param ordersPaymentDTO
 * @return
 */
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
    // 当前登录用户id
    Long userId = BaseContext.getCurrentId();
    User user = userMapper.getById(userId);

    //调用微信支付接口,生成预支付交易单
    JSONObject jsonObject = weChatPayUtil.pay(
            ordersPaymentDTO.getOrderNumber(), //商户订单号
            new BigDecimal(0.01), //支付金额,单位 元
            "苍穹外卖订单", //商品描述
            user.getOpenid() //微信用户的openid
    );

    if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
        throw new OrderBusinessException("该订单已支付");
    }

    OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
    vo.setPackageStr(jsonObject.getString("package"));

    return vo;
}


------------------
//修改UserMapper
//增加方法

@Select("select * from user where id = #{userId}")
User getById(Long userId);

3 支付成功的回调

创建PayNotifyController

//PayNotifyController

package com.sky.controller.user;

import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.properties.WeChatProperties;
import com.sky.service.OrderService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

/**
 * 支付回调相关接口
 */
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {
    @Autowired
    private OrderService orderService;
    @Autowired
    private WeChatProperties weChatProperties;

    /**
     * 支付成功回调
     *
     * @param request
     */
    @RequestMapping("/paySuccess")
    public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //读取数据
        String body = readData(request);
        log.info("支付成功回调:{}", body);

        //数据解密
        String plainText = decryptData(body);
        log.info("解密后的文本:{}", plainText);

        JSONObject jsonObject = JSON.parseObject(plainText);
        String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
        String transactionId = jsonObject.getString("transaction_id");//微信支付交易号

        log.info("商户平台订单号:{}", outTradeNo);
        log.info("微信支付交易号:{}", transactionId);

        //业务处理,修改订单状态、来单提醒
        orderService.paySuccess(outTradeNo);

        //给微信响应
        responseToWeixin(response);
    }

    /**
     * 读取数据
     *
     * @param request
     * @return
     * @throws Exception
     */
    private String readData(HttpServletRequest request) throws Exception {
        BufferedReader reader = request.getReader();
        StringBuilder result = new StringBuilder();
        String line = null;
        while ((line = reader.readLine()) != null) {
            if (result.length() > 0) {
                result.append("\n");
            }
            result.append(line);
        }
        return result.toString();
    }

    /**
     * 数据解密
     *
     * @param body
     * @return
     * @throws Exception
     */
    private String decryptData(String body) throws Exception {
        JSONObject resultObject = JSON.parseObject(body);
        JSONObject resource = resultObject.getJSONObject("resource");
        String ciphertext = resource.getString("ciphertext");
        String nonce = resource.getString("nonce");
        String associatedData = resource.getString("associated_data");

        AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        //密文解密
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);

        return plainText;
    }

    /**
     * 给微信响应
     * @param response
     */
    private void responseToWeixin(HttpServletResponse response) throws Exception{
        response.setStatus(200);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("code", "SUCCESS");
        map.put("message", "SUCCESS");
        response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
        response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    }
}


---------------------
//修改OrderService
//增加方法

/**
 * 支付成功,修改订单状态
 * @param outTradeNo
 */
void paySuccess(String outTradeNo);


-------------------
//修改OrderServiceImpl

/**
 * 支付成功,修改订单状态
 *
 * @param outTradeNo
 */
public void paySuccess(String outTradeNo) {
    // 根据订单号查询订单
    Orders ordersDB = orderMapper.getByNumber(outTradeNo);

    // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
    Orders orders = Orders.builder()
            .id(ordersDB.getId())
            .status(Orders.TO_BE_CONFIRMED)
            .payStatus(Orders.PAID)
            .checkoutTime(LocalDateTime.now())
            .build();

    orderMapper.update(orders);
}


------------------
//修改OrderMapper

/**
 * 根据订单号和用户id查询订单
 * @param orderNumber
 * @param userId
 */
@Select("select * from orders where number = #{orderNumber}")
Orders getByNumber(String orderNumber);

/**
 * 修改订单信息
 * @param orders
 */
void update(Orders orders);


-----------------
//OrderMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sky.mapper.OrderMapper">
    <update id="update" parameterType="com.sky.entity.Orders">
        update orders
        <set>
            <if test="cancelReason != null and cancelReason!='' ">
                cancel_reason=#{cancelReason},
            </if>
            <if test="rejectionReason != null and rejectionReason!='' ">
                rejection_reason=#{rejectionReason},
            </if>
            <if test="cancelTime != null">
                cancel_time=#{cancelTime},
            </if>
            <if test="payStatus != null">
                pay_status=#{payStatus},
            </if>
            <if test="payMethod != null">
                pay_method=#{payMethod},
            </if>
            <if test="checkoutTime != null">
                checkout_time=#{checkoutTime},
            </if>
            <if test="status != null">
                status = #{status},
            </if>
            <if test="deliveryTime != null">
                delivery_time = #{deliveryTime}
            </if>
        </set>
        where id = #{id}
    </update>
</mapper>

4 功能测试

测试过程中,可通过断点方式查看后台每一步执行情况。

5.小结

准备工作:

  1. 注册一个小程序AppID

  2. 注册一个商户号mchid

  3. 给商户号配置密钥、生成证书

  4. 把AppID和mchid进行绑定

配置参数:

  • 配置AppID相关信息:AppID,AppSecret

  • 配置商户的信息

  • 配置密钥和证书信息

  • 配置回调地址:让微信在支付结束之后,通过这个地址把结果 告诉给我们

    我们的电脑在局域网里,需要使用内网穿透工具Cpolar,获取临时域名

    把临时域名拼接回调地址,配置到application.yaml里

实现过程:

  1. 小程序提交订单信息,服务端保存订单信息,返回订单编号

  2. 小程序准备支付,提交请求

    服务端向微信支付平台发请求,创建交易单:告诉微信,接下来将会有一个交易,把付款人、收款人、金额告诉微信

    微信创建交易单之后,会给我们的服务端返回:prepay_id预支付编号

    我们的服务端需要对prepay_id进行二次签名,返回给小程序

  3. 小程序得到了预支付编号信息:用户在小程序上输入帐号、密码进行支付

  4. 当支付成功以后,微信支付平台会

    通知小程序客户端:得到支付结果,如果支付成功,再显示“支付成功”界面

    通知我们的服务端:我们的服务端接收到支付结果

    • 如果支付结果是成功,就解密支付数据,得到 订单号、openid、AppID等等一系列的交易信息

    • 根据订单号,更新数据库里的订单的状态为“已支付 ”

    • 给微信平台返回一个回应。

      微信平台收到回应,就不再通知我们了

      如果微信平台收不到回应,就认为 没能通知到我们,会持续尝试通知我们

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值