苍穹外卖08(地址簿功能,用户下单功能,订单支付全过程,内网穿透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等等一系列的交易信息

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/519500.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 学习和实践笔记(51):二分法查找(折半检索)

二分法查找&#xff08;折半检索&#xff09;又叫binary search. 要在一堆数据中查找是否存在某一个已知数&#xff0c;二分法查找的步骤&#xff1a; 第一步&#xff0c;对数据实现排序 第二步&#xff0c;将该数与排序后的数据集的中间一个数进行比较 第三步&#xff0c;…

非关系型数据库(缓存数据库)redis的性能管理

目录 一.Redis性能管理 1.Info Memory——查看Redis内存使用 2.内存碎片率 3. 内存使用率 4.内存回收key 二.缓存的穿透&#xff0c;击穿和雪崩 1.缓存的穿透 1.1 问题描述 1.2 缓存穿透发生的条件 1.3 缓存穿透发生的原因 1.4 解决方案 2 缓存的击穿 2.1 问题描…

使用SVD将图像压缩四分之一(MATLAB)

SVD压缩前后数据量减少的原因在于&#xff0c;通过奇异值分解&#xff08;SVD&#xff09;&#xff0c;我们将原始数据&#xff08;如图像&#xff09;转换成了一种更加紧凑的表示形式。这种转换依赖于数据内部的结构和相关性&#xff0c;以及数据中信息的不均匀分布。 让我们…

以 2021inCTF-DeadlyFastGraph 入门 JSC利用

前言 最近一直在入门浏览器的利用&#xff0c;然后一直都在搞 V8&#xff0c;然后接触的比较多的都是一些混淆、越界的洞&#xff0c;希望后面可以入门 jit 然后在今年的阿里云 CTF 中看到了一道 jsc 相关的题目&#xff0c;当时本来想做一做的&#xff0c;但是环境一直没有搭…

vLLM介绍

vLLM是伯克利大学LMSYS组织开源的大语言模型高速推理框架&#xff0c;旨在极大地提升实时场景下的语言模型服务的吞吐与内存使用效率。vLLM是一个快速且易于使用的库&#xff0c;用于 LLM 推理和服务&#xff0c;可以和HuggingFace 无缝集成。vLLM利用了全新的注意力算法「Page…

ZKP价值链路的垂直整合

1. ZKP proof生命周期 从ZKP&#xff08;zero-knowledge proof&#xff09;生命周期&#xff0c;先看围绕ZKP的价值链路形成&#xff1a; 1&#xff09;User intent用户意图&#xff1a;以某用户意图为起点&#xff0c;如想要在某zk-rollup上swap某token、证明其身份、执行某…

java数据结构与算法刷题-----LeetCode405. 数字转换为十六进制数

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 分组位运算 分组位运算 这道题正常来说可以用转换7进制的思想来&…

加速度:电子元器件营销网站的功能和开发周期

据工信部预计&#xff0c;到2023年&#xff0c;我国电子元器件销售总额将达到2.1万亿元。随着资本的涌入&#xff0c;在这个万亿级赛道&#xff0c;市场竞争变得更加激烈的同时&#xff0c;行业数字化发展已是大势所趋。电子元器件B2B商城平台提升数据化驱动能力&#xff0c;扩…

算法学习18:动态规划

算法学习18&#xff1a;动态规划 文章目录 算法学习18&#xff1a;动态规划前言一、线性DP1.数字三角形&#xff1a;f[i][j] max(f[i - 1][j - 1] a[i][j], f[i - 1][j] a[i][j]);2.1最长上升子序列&#xff1a;f[i] max(f[i], f[j] 1);2.2 打印出最长子序列3.最长公共子序…

[从零开始学习Redis | 第九篇] 深入了解Redis数据类型

前言&#xff1a; 在现代软件开发中&#xff0c;数据存储和处理是至关重要的一环。为了高效地管理数据&#xff0c;并实现快速的读写操作&#xff0c;各种数据库技术应运而生。其中&#xff0c;Redis作为一种高性能的内存数据库&#xff0c;广泛应用于缓存、会话存储、消息队列…

MySQL - 基础三

11、事务管理 CURD不加控制&#xff0c;会有什么问题&#xff1f; 当客户端A检查还有一张票时&#xff0c;将票卖掉&#xff0c;还没有执行更新数据库时&#xff0c;客户端B检查了票数&#xff0c;发现大于0&#xff0c;于是又卖了一次票。然后A将票数更新回数据库。这是就出现…

09 flink-sql 中基于 mysql-cdc 的 select * from test_user 的具体实现

前言 这也是最近帮一个朋友看问题 遇到的一个问题 然后 引发了一下 对于 flink-sql 里面的一些 常规处理的思考, 理解 原始问题主要是 在测试库可以使用 flink-sql 可以正常同步, 但是 在生产环境 无法正常同步数据 这个问题 我们后面单独 记录一篇文章 测试用例 下载…

设计模式总结-外观模式(门面模式)

外观模式 模式动机模式定义模式结构外观模式实例与解析实例一&#xff1a;电源总开关实例二&#xff1a;文件加密 模式动机 引入外观角色之后&#xff0c;用户只需要直接与外观角色交互&#xff0c;用户与子系统之间的复杂关系由外观角色来实现&#xff0c;从而降低了系统的耦…

携程旅行 abtest

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章…

WindowsPowerShell安装配置Vim的折腾记录

说明 vim一直以来都被称为编辑器之神一样的存在。但用不用vim完全取决于你自己&#xff0c;但是作为一个学计算机的同学来说&#xff0c;免不了会和Linux打交道&#xff0c;而大部分的Linux操作系统都预装了vim作为编辑器&#xff0c;如果是简单的任务&#xff0c;其实vim只要会…

c/c++之编译链接

了解我们写的代码是如何转变成可执行文件.exe的是很有必要的&#xff0c;我们将这些底层的东西掌握清楚才能打好基础&#xff0c;筑高楼。 编译链接的全流程 我们平时写代码的文件是.c或者.cpp文件。这里面包括我们的代码&#xff0c;还有宏定义&#xff0c;引用头文件以及注…

齐护机器人方位传感器指南针罗盘陀螺仪

一、方位传感器原理及功能说明 齐护方位传感器是一款集成了三轴磁传感器芯片的方位传感器模块。适用于无人机、机器人、移动和个人手持设备中的罗盘&#xff08;指南针&#xff09;、导航和游戏等高精度应用。模块可以感应XYZ平面角度外&#xff0c;还可实现1至2的水平面角度罗…

Python--Django--说明

Django 是基于python 的 Web 开发框架. &nsbp;   Web开发指的是开发基于B/S 架构, 通过前后端的配合, 将后台服务器上的数据在浏览器上展现给前台用户的应用. &nsbp;   在早期, 没有Web框架的时候, 使用 Python CGI 脚本显示数据库中的数据. Web框架致力于解决一些…

考古:IT架构演进之IOE架构

考古&#xff1a;IT架构演进之IOE架构 IOE架构&#xff08;IBM, Oracle, EMC&#xff09;出现在20世纪末至21世纪初&#xff0c;是一种典型的集中式架构体系。在这个阶段&#xff0c;企业的关键业务系统往往依赖于IBM的小型机&#xff08;后来还包括大型机&#xff09;、Oracle…

后端灰度发布

在软件开发中&#xff0c;"灰度"通常指的是渐进式地将新功能、更新或改进引入到生产环境中&#xff0c;但只对一小部分用户或流量进行部署和测试的过程。这种方法允许开发团队在生产环境中逐步测试新功能&#xff0c;以确保其稳定性、可靠性和用户体验&#xff0c;同…