[TOC]
# 外卖接口(前端)
## 1. 简述
### 1.1 常规说明
* 接口使用 RESTful 风格。
* URL中的 `<id>` / `<slug>` 替换为实际的数值。
* **状态码**一般都是指 HTTP 状态码。
* 身份验证信息放在 `Authorization` 请求头,示例 `Authorization: Bearer <token>`
* 带有 **公开** 字样的接口是可以匿名访问的,为了便于封装,所有接口都可以带上身份验证信息,只是公开的接口会忽略身份验证信息
### 1.2 HTTP 状态码及返回内容说明
**HTTP 状态码说明:**
* 200 - 请求成功
* 201 - 请求成功, 并创建了一个对象. 一般是执行 **POST** 动作创建一个新对象时返回.
* 204 - 请求成功, 没有内容返回. 一般是执行 **DELETE** 动作时返回.
* 400 - 客户端请求无效. 一般是指客户端提交的参数不符合要求.
* 401 - 需要身份验证. 当需要身份验证, 而未提供身份验证信息或者身份验证信息无效.
* 403 - 禁止访问. 一般情况是权限不足.
* 404 - 资源不存在
* 405 - 动作不允许. 例如接口只接受 **POST** 动作, 而客户端使用了 **GET** 动作.
* 500 - 服务器内部错误
**返回内容说明:**
返回内容是 JSON 格式。除了 2xx, 400 状态码之外, 其他状态码的出错信息都出现在 `detail` 字段中, 例如:
```
{
"detail": "身份认证信息未提供。"
}
```
**400状态码返回内容说明:**
客户端请求参数检查不通过的话,返回状态码都是 400,参数出错的话,返回结果中带有对应参数的出错信息列表(数组),一般情况下数组只有一个元素,对于不是参数出错的错误信息,通过 `non_field_errors` 给出,格式与参数的出错信息相同(都是数组)。
例如:
```
{
"context": [
"使用场景(context)不能为空"
]
}
```
### 1.3 国际手机号码说明
1. 国内手机号码不存储**国家/地区代码**,仅存储 11 位数字,如:`18682265887`
2. 国际手机号码存储**国家/地区代码**(包含前置的加号(`+`))和**手机号码**,如:`+79118064728`
3. 客户端提交手机号码参数时,将**国家/地区代码**和**手机号码**组合成一个字符串,如:`+79118064728`,`+8618682265887`。后端接收到之后自动进行处理,对于`+86`开头的手机号码,自动去掉`+86`标识。
### 1.4 桌台二维码说明
桌台二维码分为 3 类:堂食桌台、外卖桌台、等取桌台。
二维码 URL 规则
* 堂食 `http://xxx.com/?rid=1&tid=2&ver=1`
* 堂食(自选) `http://xxx.com/?rid=1&tid=-99&ver=1`
* 外卖 `http://xxx.com/?rid=1&tid=-1&ver=1`
* 等取 `http://xxx.com/?rid=1&tid=-2&ver=1`
查询参数说明:
* `rid` - 表示外卖后端的餐厅id
* `tid` - 表示桌台id(以收银端桌台table_id为准)
* `ver` - 表示二维码版本
扫描二维码进入手机外卖前端页面(H5)之后,前端从外卖后端获取到对应餐厅的桌台数据(在餐厅信息中),并对比 URL 传过来的 `ver` 是否与后端的(`qrcode_version`)一致,若不一致,则可以告知二维码失效,并禁用点餐功能。
## 2. 餐厅接口
### 2.1 餐厅信息
`GET /api/v1/frontend/restaurants/<id>?table_id=<table_id>`
查询参数:
* `table_id` - 桌台ID(二维码中的`tid`),`paid_types` 数据与桌台(用餐类型)有关
返回数据结构如下:
```
{
"id": 1, // 餐厅id
"categories": [ // 分类
{
"id": 2, // 分类id
"name": "Esppresso" // 分类名称
},
{
"id": 4,
"name": "Brewed"
},
{
"id": 6,
"name": "Hot"
},
{
"id": 8,
"name": "Iced"
},
{
"id": 10,
"name": "Blended Ice"
},
{
"id": 12,
"name": "Food Items"
}
],
"takeout": { // 外卖信息
"slides": [ // 餐厅图片列表
{
"image": "http://www.baidu.com",
"link": "http://gicater.com",
},
{
"image": "http://www.baidu.com", // 图片URL
"link": "", // 链接URL,值为空则不跳转
}
],
"phones": [ // 餐厅电话列表
"0755-81234567"
],
"eat_types_enable": [ // 用餐类型状态(是否可接单),以 eat_type 值作为索引(下标)
true, // 下标0,堂食是否接单,true-是,false-否
true, // 下标1,外卖(配送)是否接单,true-是,false-否
false, // 下标2,外卖(自取/来取)是否接单,true-是,false-否
true // 下标3,等取是否接单,true-是,false-否
],
"cny_exrate": 0.0, // 餐厅计价货币对人民币汇率,用于在线支付时转换成人民币金额;人民币金额=餐厅计价货币金额*cny_exrate,只有 cny_exrate 大于0时进行换算,否则 人民币金额=餐厅计价货币金额。
"address": "", // 餐厅地址
"is_user_address_reversed": false, // 用户地址是否反向,true-反向(从小到大),false-不反向(从大到小)
"region": "深圳市", // 餐厅所在城市,只有中文版收银有值,可用在百度地图接口 region 字段
"delivery_times": [ // 外卖配送可选的时间列表(单位分钟),超过60分钟的,前端自行转换成时分格式
30,
45,
60,
75,
90,
105,
120,
135
],
"collect_times": [ // 外卖自取可选的时间列表(单位分钟)
15,
30,
45,
60,
75,
90
],
"longitude": 0.0, // 餐厅经度
"latitude": 0.0, // 餐厅纬度
"notice": "公告", // 公告
"summary": "餐厅简介", // 简介
"attention": "注意事项", // 注意事项
"show_total": 1, // 是否显示总价
"show_zero": 0, // 是否显示价格为0的菜
"enable_service": 1,
"pay_mch": "qf", // 支付服务商标识,qf-钱方,sqb-收钱吧。钱方需要走获取用户 openid 流程,参考 8 钱方微信支付
"language_type": 1, // 语言类型,1-系统默认,2-自定义(可自选)
"country_code": "+86", // 国家地区代码,发送国际短信时需要
"takeaway_use_takeout_price": true // 等取是否使用外志价格,true-是,false-否
},
"takeout_delivery_fee": { // 外卖费信息
"delivery_scope": "0.0~20.0", // 配送范围,单位公里,只做显示,不做计算依据
"min_amount": 5.0 // 起送金额
},
"takeout_periods": { // 外卖营业时间,0周日,1-6周一至周六,每天有多个时间段(目前最多2个)
"open": [ // 是否营业,true-营业,false-停业;下标 0~6 表示周日至周六
false,
false,
true,
true,
true,
true,
true
],
"0": [
{ // 营业时间段
"start_time": "12:00", // 开始时间
"end_time": "14:00" // 结束时间
}
],
"1": [
{
"start_time": "00:00",
"end_time": "23:59"
}
],
"2": [
{
"start_time": "00:00",
"end_time": "23:59"
}
],
"3": [
{
"start_time": "00:00",
"end_time": "23:59"
}
],
"4": [
{
"start_time": "00:00",
"end_time": "23:59"
}
],
"5": [
{
"start_time": "00:00",
"end_time": "23:59"
}
],
"6": [
{
"start_time": "00:00",
"end_time": "23:59"
}
]
},
"tables": [ // 桌台列表
{
"table_id": 1, // 桌台ID
"table_name": "A1", // 桌台名称
"seat_num": 1, // 座位数
"qrcode_version": 1 // 桌台二维码版本号
},
{
"table_id": 2,
"table_name": "A2",
"seat_num": 1,
"qrcode_version": 1
},
{
"table_id": 5,
"table_name": "A5",
"seat_num": 1,
"qrcode_version": 1
}
],
"paid_types": [ // 可用的支付类型列表(下单接口会用到)
{ // 默认线下支付
"value": 1,
"label": "线下支付"
},
{ // 微信支付需要餐厅支持,餐厅支持的情况下,前端需要判断是不是微信浏览器,若不是,则不可用
"value": 2,
"label": "微信支付"
},
{ // 需要餐厅支持并启用
"value": 3,
"label": "会员卡"
}
],
"order_default": { // 开台消费,没有的话返回 null
"periods": [ // 有开台消费的时间段,如果开始时间大于结束时间,比较方法 start_time <= nowTime 或者 end_time >= nowTime
{
"start_time": "04:00", // 开始时间
"end_time": "14:00" // 结束时间
},
{
"start_time": "14:00",
"end_time": "04:00"
}
],
"products": [ // 开台消费必选的商品(菜品)
{
"id": 480, // 商品id
"item_id": 6005, // 商品item_id
"item_name": "Garden Salad", // 商品名称
"item_type": 0,
"specs": [ // 规格,只有1个
{
"pu": 0, // 规格索引
"price": 4.2, // 价格(堂食)
"unit": "", // 规格名称(单位)
"takeout_price": 4.2
}
],
"thumbnail": "//pos-cn-node.oss-cn-beijing.aliyuncs.com/1.jpg", // 缩略图URL
"description": "Refreshing ingredient with savory Thousand Island Sauce",
"num": 1, // 单份包含商品的数量
"is_by_seat": false // 是否按位消费
}
]
},
"name": "aaa", // 餐厅名称
"location_name_1": "", // 餐厅地址
"slug": "",
"is_support_tax": false, // 是否支持计税功能,若不支持,下单时可不调用税费计算接口
"show_privacy_policy": false, // 是否显示隐私政策,true-显示,false-不显示
"product_include_tax": true, // 菜品是否含税
"currency_name": "¥", // 货币符号
"decimal_places": 2,
"decimal_char": ".",
"edition": "cn", // 收银软件版本,cn-中文版,en-英文版,优先判断是不是中文版,其他情况可当作英文版
"start_time": "04:00:00",
"is_member_card_enable": true, // 会员卡服务是否可用,true-可用,false-不可用
"tz_offset": 0,
"tz_string": "Z",
"today": "2019-04-18", // 餐厅当前日期
"weekday": 4 // 餐厅当前周几,0周日,1-6周一至周六
}
```
营业状态判断(至少要做 3 个判断):
1. 用餐类型(`eat_type`)是否**可接单**,根据 `takeout.eat_types_enable` 判断
2. 当天**是否营业**,根据 `takeout_periods.open` 判断
3. 当前时间是否在营业时间段内,根据 `takeout_periods.X` 判断
## 3. 用户接口
### 3.1 发送短信验证码
`POST /api/v1/frontend/users/send-phone-token`
提交参数:
* `res_id` - 餐厅id
* `phone` - 手机号码
* `context` - 短信场景,`login`-登录(验证码快捷登录),`register`-注册/绑定手机
请求成功返回状态码200,返回数据结构如下:
```
{
"send": true // true 表示发送成功, false 表示发送失败
}
```
### 3.2 登录
`POST /api/v1/frontend/users/login`
提交参数:
* `res_id` - 餐厅id
* `username` - 手机号码 / 电子邮件,目前只支持手机号码
* `password` - 密码,至少 6 位;若使用手机短信快捷登录,这里传手机验证码,短信发送接口 `context=login`
身份验证成功的话返回状态码200,返回数据结构如下:
```
{
"id": 2,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjIsImlhdCI6MTU0ODAzNjI2NCwiZXhwIjoxNTQ4MTIyNjY0fQ.thercE0EgPEWDtwF-YhSRUw8zu2UTOO0sBuaTI1yM_g", // token
"ttl": 86400 // token 有效期(单位秒)
}
```
若用户名或密码错误,返回状态码403。
### 3.3 获取用户信息
`GET /api/v1/frontend/users/me`
```
{
"id": 1,
"username": "digwtx01", // 用户名
"phone": "", // 电话号码
"email": "",
"avatar": "http://www", // 头像URL
"nick_name": "digwtx", // 昵称
"addresses": [{ // 送餐地址列表
"id": 1,
"name": "iwerwer", // 姓名
"gender": 0, // 性别,0-女性,1-男性
"phone": "15389781522", // 电话
"address": "iwierew", // 地址
"distance": 2, // 与餐厅的距离(单位千米)
"is_default": false // 是否为默认地址
}, {
"id": 2,
"name": "iwerwer",
"gender": 0, // 性别,0-女性,1-男性
"phone": "18682265887",
"address": "iwierew",
"distance": 3,
"is_default": false
}
],
"member_card": { // 会员卡信息,若没有则为 null
"card_id": "wx001" // 会员卡号
}
}
```
### 3.4 修改用户信息
`POST /api/v1/frontend/users/me`
可修改的信息(可任意修改一项或多项):
* `avatar` - 头像URL
* `nick_name` - 昵称
修改成功返回状态码200,数据数据结构与 **3.3 获取用户信息** 相同。
### 3.5 Facebook 登录
假设前端使用 Facebook JavaScript JDK 进行开发,可以获取到用户的 `access_token`。
当拿到 FB 用户的 `access_token` 之后,调用 `3.2 登录` 接口进行登录。
提交参数(JSON):
```
{
"res_id": 1, // 餐厅id,按实际传值
"username": "user_id", // 可随意传,只要不为空即可
"password": "access_token", // FB 用户的 access_token,非常重要
"user_type": 2 // 固定传2
}
```
返回及异常参考 `3.2 登录` 接口。
### 3.6 绑定手机号码
`POST /api/v1/frontend/users/bind-phone`
此接口需要提供身份信息。
此接口需要发送短信验证码:向需要绑定的手机号码发送一个注册验证码,短信发送接口 `context=register`
提交参数示例(JSON):
```
{
"phone": "13811115555", // 需要绑定的手机号码
"token": "138532" // 手机验证码
}
```
绑定成功的话返回状态码200。
若验证码无效,则返回状态码400。
若用户已绑定手机号码,则返回状态码403,返回信息**用户已绑定手机号码**。
## 4. 用户地址接口
此部分接口需要登录。
### 4.1 地址列表
`GET /api/v1/frontend/user-addresses`
返回数据结构如下:
```
{
"count": 3,
"code": 0,
"next": null, // 下一页URL
"previous": null, // 上一页URL
"results": [ // 结果集
{
"id": 2,
"name": "iewirwe", // 姓名
"gender": 0, // 性别,0-女性,1-男性
"phone": "irweirweiriwe", // 电话
"address": "iweirweir", // 地址
"longitude": 0, // 经度
"latitude": 0, // 纬度
"distance": 1, // 地址与餐厅的距离(单位千米)
"is_default": true // 是否为默认地址
},
// ...
]
}
```
### 4.2 地址详情
`GET /api/v1/frontend/user-addresses/<id>`
数据结构参与列表接口。
### 4.3 增加地址
`POST /api/v1/frontend/user-addresses`
提交数据结构(JSON):
```
{
"name": "iewirwe", // 姓名
"gender": 0, // 性别,0-女性,1-男性
"phone": "15388882222", // 联系电话
"address": "iweirweir", // 地址
"longitude": 0, // 经度(可不传,建议传)
"latitude": 0, // 纬度(可不传,建议传)
"distance": 1, // 地址与餐厅的距离(单位千米),负数表示距离未知
"is_default": false // 是否为默认地址
}
```
### 4.4 修改地址
* `PUT /api/v1/frontend/user-addresses/<id>` 要提交所有需要字段
* `PATCH /api/v1/frontend/user-addresses/<id>` 可只提交部分字段
### 4.5 删除地址
`DELETE /api/v1/frontend/user-addresses/<id>`
删除成功的话返回状态码204。
### 4.6 设为默认地址
`POST /api/v1/frontend/user-addresses/<id>/set-default`
设置成功的话返回状态码200,返回地址详情。
## 5. 商品(菜品)接口
普通菜品、套餐、调味品本质上都是商品,都存储在同一个数据库表中,数据结构也大致相同。
1. 普通商品有**多规格**,套餐目前暂定单规格。多规格单选。
2. 普通商品有**可选调味品**,套餐本身的**可选调味品**无效,套餐内菜品有**可选调味品**。**可选调味品**多选。调味品有可能有**规格**(单位),单规格的。
3. 套餐**必选组**内的菜品全部必选。
3. 套餐**可选组**中的菜品可能有**多规格**,如果处理比较麻烦,当有多规格时,可考虑只使用第一规格。
### 5.1 商品列表
`GET /api/v1/frontend/products/simple?res_id=<res_id>&table_id=<table_id>`
参数说明:
* `res_id` - (必填)餐厅id
* `table_id` - (必填)桌台ID(二维码中的`tid`),菜品跟用餐类型有关,有些菜品只在指定的用餐类型中可用
* `category_id` - (选填)分类id,分类id从餐厅信息 `categories` 中获取
返回数据结构如下(默认带分页,可针对需求取消分页):
```
{
"count": 3,
"next": null, // 下一页URL
"previous": null, // 上一页URL
"results": [] // 结果集,单个数据结构参见商品详情
}
```
### 5.2 商品详情
`GET /api/v1/frontend/products/<id>?res_id=<res_id>`
查询参数说明:
* `res_id` - (必填)餐厅id
返回数据结构如下:
```
{
"id": 30, // 商品id
"specs": [ // 规格,至少会有1个,如果只有一个,则不需要选择
{
"pu": 0, // 规格索引,值范围0~4
"price": 5.0, // 价格(堂食)
"takeout_price": 5.0, // 外卖价格
"unit": "Dozen" // 规格(单位)
}
],
"condiments": [ // 需要选择的调味品
{
"id": 33, // 调味品(商品)id
"item_id": 6002, // 调味品(商品)item_id
"item_name": "no sugar", // 调味品(商品)名称
"price": 0.0 // 价格,有些调味味需要额外收费
},
{
"id": 34,
"item_id": 6003,
"item_name": "cream",
"price": 3.0
}
],
"thumbnail": "", // 商品缩略图URL
"images": [
{
"url": "xxxx"
}
],
"tax": 0,
"courses": [ // 套餐组信息,只有当商品是套餐时才会有
{
"id": 1, // 分组id
"group_name": "必选组", // 分组名称
"is_must": 1, // 是否必选,1-必选,0-可选,必选的话则该分组下所有商品都要选择
"choose_num": 1, // 需要选择的数量,必选组的话,则这个数量忽略
"entries": [ // 分组内的商品
{
"id": 21, // 商品id
"item_id": 1021, // 商品item_id
"item_name": "Iced Caffe Mocha", // 套餐中的商品名称
"num": 1.0, // 包含数量
"specs": [{ // 套餐组中的规格,至少有一组规格,也可能有多组规格
"pu": 1, // 规格索引
"unit": "C", // 规格,显示时需要将规格一并显示
"price": 0, // 加价价格,大于0表示加价,如果选择了的话,则需要计入套餐总价
"origin_price": 4.0, // 原始价格,可用在某些需要显示的场景
}],
"condiments": []
}
]
},
{
"id": 2,
"group_name": "可选组",
"is_must": 0, // 是否必选
"choose_num": 2,
"entries": [
{
"id": 17,
"item_id": 1017,
"item_name": "Bottle of Water",
"num": 1.0,
"specs": [{
"pu": 0,
"unit": "C",
"price": 1.0,
"origin_price": 2.0
}],
"condiments": [ // 套餐中的商品也是有可能需要选择调味品的
{
"id": 33,
"item_id": 6002,
"item_name": "no sugar",
"price": 0.0
},
{
"id": 34,
"item_id": 6003,
"item_name": "cream",
"price": 3.0
}
]
},
{
"id": 12,
"item_id": 1012,
"item_name": "Starbucks Coffee",
"num": 1.0,
"specs": [{
"pu": 0,
"unit": "T",
"price": 0.0,
"origin_price": 2
}],
"condiments": []
}
]
},
{
"id": 3,
"group_name": "可选组2",
"is_must": 0,
"choose_num": 1,
"entries": [
{
"id": 13,
"item_id": 1013,
"item_name": "Shot In The Dark ",
"num": 1,
"specs": [ // 套餐组中的菜品的多个规格
{
"pu": 0,
"unit": "T",
"price": 0,
"origin_price": 3.25
},
{
"pu": 1,
"unit": "C",
"price": 0,
"origin_price": 3.5
},
{
"pu": 2,
"unit": "V",
"price": 0,
"origin_price": 3.75
}
],
"condiments": []
},
{
"id": 9,
"item_id": 1009,
"item_name": "Espresso",
"num": 1.0,
"specs": [
"pu": 0,
"unit": "D",
"price": 0.5,
"origin_price": 3,
],
"condiments": []
}
]
}
],
"item_id": 6004, // 商品item_id
"item_name": "abc23ccccc", // 商品名称
"item_type": 3, // 商品类型,0-普通菜品,3-套餐
"box_fee": 0.0, // 餐盒费
"category": 8,
"tax_group": null
}
```
## 6. 订单
### 6.1 订单列表
* 外卖订单列表 `GET /api/v1/frontend/orders?res_id=<res_id>&table_id=-1` [需要登录]
* 堂食订单列表 `GET /api/v1/frontend/orders?res_id=<res_id>&table_id=<tabel_id>`
查询参数:
* `res_id` - (必填)餐厅id
* `table_id` - (必填)桌台id
* `status` - (选填)订单状态,`9`-已完成的,`unfinished`-未完成的
返回数据结构:
```
{
"count": 15,
"code": 0,
"next": null, // 下一页URL
"previous": null, // 上一页URL
"results": [ // 结果集
{ // 订单
"id": 1, // 订单id
"products": [ // 订单产品(菜品)
{
"product_item_id": 6000,
"product_item_name": "milk", // 菜品名称
"pu": 0,
"unit": "C", // 菜品规格
"desc": "C", // 描述信息,如果是套餐,则描述套餐内容,如果是普通菜品,则描述规格
"price": 3.0, // 菜品单价
"origin_price": 0.0,
"qty": 1, // 菜品数量
"box_fee": 0.0, // 单个菜品总的餐盒费
"total_price": 3.0 // 总价
},
{
"product_item_id": 1016,
"product_item_name": "Hot Chocolate",
"pu": 0,
"unit": "D",
"desc": "D",
"price": 25.0,
"origin_price": 0.0,
"qty": 1,
"box_fee": 0.0,
"total_price": 25.0
}
],
"customer_name": "", // 客户姓名
"customer_phone": "", // 客户电话
"customer_address": "", // 客户地址
"customer_count": 1, // 客户人数或者餐具数
"eat_type": 0, // 0-堂食,1-外卖配送,2-外卖自取,3-等取
"paid_type": 1, // 付款方式,1-线下支付,2-微信支付,3-会员卡
"is_paid": false, // 是否支付,支付状态和订单状态分开
"paid_time": null, // 支付时间
"plan_time": null, // 计划时间(预计时间)
"total_price": 29.0, // 菜品总价
"total_box_fee": 0.0, // 总餐盒费
"total_tax_fee": 0.0,
"delivery_fee": 0.0, // 配送费
"total_fee": 35.0, // 订单总金额
"remark": "", // 备注信息
"trade_no": "11556070571001", // 订单号
"take_code": "15", // 取餐号,eat_type值为0、2、3时才会有
"take_code_datauri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARwAAABSCAIAA", // 取餐号条形码图片(DataURI,Base64格式)
"status": 1, // 订单状态,0-用户下单,1-待商家确认,2-商家制作中,3-商家制作完成,4-配送中,5-已送达,9-已完成,-1-用户取消,-2-商家取消
"create_time": "2019-04-24T09:49:31.543714+08:00", // 下单时间
"restaurant": 1,
"table": { // 如果没有会是 null
"table_id": 1,
"table_name": "1",
}
},
// ...
]
}
```
### 6.2 订单详情
`GET /api/v1/frontend/orders/<id>?res_id=<res_id>`
数据结构参考列表接口。
### 6.3 计算配送费
`POST /api/v1/frontend/orders/calc-delivery-fee?res_id=<res_id>`
提交参数:
```
{
"distance": 2 // 距离(单位千米)
}
```
返回数据结构:
```
{
"delivery_fee": 0 // 配送费,负数表示不在配送范围
}
```
### 6.4 计算税费
`POST /api/v1/frontend/orders/calc-tax-fee?res_id=<res_id>`
提交数据结构(为了方便,可直接提交**下单**接口的数据结构):
```
{
"restaurant": 1, // res_id
"delivery_fee": 0, // 配送费
"total_box_fee": 0, // 总餐盒费
"discount_fee": 0, // 折扣金额,正数
"products": [{
"product": 549, // 菜品id
"price": 4.2, // 菜品价格
"qty": 1, // 数量
"item_id": 6005, // 菜品item_id
"item_name": "Garden Salad", // 菜品名称
"total_price": 4.2, // 菜品总价
}
]
}
```
返回数据结构:
```
{
"tax_fee": "1.06", // 税费
"is_tax_included": false // 是否已含税,若已含税,则税费不计入 total_fee
}
```
### 6.5 下单
`POST /api/v1/frontend/orders?res_id=<res_id>`
提交数据结构(JSON):
```
{
"restaurant": 1, // 餐厅id
"table_id": 5, // 桌台id,参考桌台信息
"eat_type": 1, // 0-堂食,1-外卖配送,2-外卖自取,3-等取
"paid_type": 1, // 支付类型,1-线下支付(默认),2-微信支付,3-会员卡
"user_address": 18, // 用户收货地址id,没有可不传或者传null
"customer_name": "", // 客户姓名
"customer_phone": "", // 客户电话
"customer_address": "", // 客户地址
"customer_count": 1, // 客户人数或者餐具数
"total_price": 28, // 商品总价
"total_box_fee": 0, // 总餐盒费
"total_tax_fee": 0, // 总税费,调用税费计算接口获得
"delivery_fee": 0, // 配送费
"discount_fee": 0, // 用正数表示,计算 total_fee 时要减去,调用折扣计算接口获得
"service_fee": 0, // 服务费
"total_fee": 34, // 总金额
"total_fee_cny": -1, // 总金额(人民币计价),只有 在线支付 时需要换算并传入,否则传-1值或者该字段不传。当 takeout.cny_exrate>0 时 total_fee_cny=total_fee * cny_exrate,否则 total_fee_cny=total_fee。在线支付目前只支持人民币结算。
"remark": "", // 整单备注信息
"plan_time": "2019-04-28T15:22+0800", // 计划时间,ISO8601格式,对于配送,是预计(期望)送达时间;对于来取,是预计到店时间;若没有则不传。
"ignore_extra_price": true, // 是否忽略菜品中的加价菜、调味品的价格
"products": [{ // 订单商品列表
"product": 1, // 菜品id
"item_id": 3, // 菜品item_id
"price": 3, // 菜品价格
"unit": "C", // 菜品规格
"pu": 0, // 菜品规格索引
"req": "", // 备注,需求
"qty": 1, // 菜品数量
"box_fee": 0, // 餐盒费=单个菜品餐盒费*数量。(目前前端传的是单份菜的餐盒费,后端也按单份验证)
"total_price": 3, // 商品总价=单个菜品价格*数量
"condiments": [{ // 调味品,如果没有可不传或传空列表[]
"product": 33, // 调味品id
"price": 0, // 调味品价格
"item_name": " no sugar" // 调味品名称
}, {
"product": 34,
"price": 3,
"item_name": "cream"
}
]
}, {
"product": 30,
"item_id": 6004,
"price": 25,
"unit": "D",
"pu": 0,
"qty": 1, // 套餐数量(份数)
"total_price": 25,
"box_fee": 0,
"children": [{ // 套餐中选择的菜品,套餐需要有 children,
"product": 21,
"item_id": 53,
"price": 0,
"unit": "X",
"pu": 0,
"qty": 1, // 单份套餐中包含的数量
"total_price": 0,
"condiments": []
}, {
"product": 17,
"item_id": 1017,
"price": 1,
"unit": "C",
"pu": 1,
"qty": 1,
"total_price": 1,
"condiments": [{ // 套餐中的菜品的调味品,如果没有可不传或传空列表[]
"product": 34,
"price": 3,
"item_name": "cream"
}
]
}
]
}
],
"discounts": [], // 折扣明细,参考 6.9 接口
"services": [] // 服务费明细,参考 6.9 接口
}
```
提交数据有问题的话,返回状态码400。
下单成功返回状态码200,返回订单详情(数据结构参考列表接口)。
---
1. `product` / `product.children` 数据结构基本相同。
总价计算方式:
1. 菜品/套餐的 `price` 使用单价,加价菜品、调味品 `price` 单独表示,计算 `total_price` / `total_fee` / 单个菜品/套餐总价 时再加上。
2. 菜品/套餐的 `price` 使用总价(包含加价菜品、调味品的 `price`),下单时增加 `ignore_extra_price` 进行声明。[目前采用这种方式]
### 6.6 获取支付URL
* 在线支付(微信) `GET /api/v1/frontend/orders/<id>/pay-url?res_id=<res_id>&openid=<openid>&method=wechat`
* 会员卡支付 `GET /api/v1/frontend/orders/<id>/pay-url?res_id=<res_id>&method=member`
在线支付目前对接两种:
* 钱方 (`qf`)
* 收钱吧 (`sqb`)
当餐厅使用钱方支付方式(`takeout.pay_mch == 'qf'`)时,需要获取用户 `openid`,并传入。
以下几种情况会返回 HTTP 状态码 403:
* 使用钱方支付方式 且 `openid` 为空
* 订单下单时传的 **支付类型** 不是微信支付
* 订单已支付、订单已取消(关闭)、订单已完成
没有异常的情况下,返回数据结构:
```
{
"url": "http://xxxxx"
}
```
若 `url` 为空字符串,表示获取支付URL失败(或异常),需要重新获取。
若 `url` 不为空,则跳转到这个 `url`,会进入 钱方/收钱吧 提供的支付页面,这个页面会唤醒微信支付。
支付成功的话,会跳转到前端的一个支付结果页面(携带 `rid` 和 `oid` (订单id)两个查询参数,例如`?rid=1&oid=3`),支付结果页需要根据下面的接口查询最终的**支付状态**。
### 6.7 查询支付结果
`GET /api/v1/frontend/orders/<id>/is-paid?res_id=<res_id>`
返回数据结构:
```
{
"is_paid": true // 是否支付
}
```
### 6.8 计算折扣金额(优惠金额)
`POST /api/v1/frontend/orders/calc-discount-fee?res_id=<res_id>`
提交数据结构:
```
{
"restaurant": 1, // res_id
"products": [{
"product": 549, // 菜品id
"price": 4.2, // 菜品价格
"qty": 1, // 数量
"item_id": 6005, // 菜品item_id
"item_name": "Garden Salad", // 菜品名称
"total_price": 4.2, // 菜品总价
}
]
}
```
返回数据结构:
```
{
"discount_fee": 1.5 // 用正数表示,0表示没有折扣
}
```
### 6.9 统一计算费用
`POST /api/v1/frontend/orders/calc-fee?res_id=<res_id>`
提交数据结构:
```
{
"restaurant": 1, // res_id,必传
"table_id": 1, // 桌台ID,必传
"eat_type": 0, // 用餐类型,参考下单接口,一定要传
"distance": 0, // 配送距离,非配送订单可不传,或者传0值
"total_box_fee": 0, // 总餐盒费
"products": [{
"product": 549, // 菜品id
"price": 4.2, // 菜品价格
"qty": 1, // 数量
"item_id": 6005, // 菜品item_id
"item_name": "Garden Salad", // 菜品名称
"total_price": 4.2, // 菜品总价
}
]
}
```
返回数据结构:
```
{
"delivery_fee": 0, // 配送费,负数表示不在配送范围,仅限配送订单使用
"discount_fee": 1.5, // 用正数表示,0表示没有折扣。包括开台折扣、会员折扣。
"service_fee": 0.6, // 额外的服务费
"tax_fee": "1.06", // 税费
"is_tax_included": false, // 是否已含税,若已含税,则 tax_fee 不计入 total_fee
"discounts": [ // 折扣明细,下单时原样传回
{
"name": "XXX 1 元",
"value": 1
},
{
"name": "5% off",
"value": 0.68
},
{
"name": "Discount 0.8",
"is_member": true,
"value": 2.38
}
],
"services": [ // 服务费明细,下单时原样传回
{
"name": "5% Service",
"value": 0.6
}
]
}
```
## 7. 图片上传
### 7.1 Base64 形式
`POST /api/v1/frontend/image-upload-base64`
提交参数:
```
{
"base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAK0lEQVRIiWNUUNP7z0AHwEQPS0YtGrVo1KJRi0YtGrVo1KJRi0YtGj4WAQCQSgGnEEr+twAAAABJRU5ErkJggg==" // base64 图片数据
}
```
上传成功返回图片URL:
```
{
"url": "http://localhost:8040/media/orgs/gicater/wx_onvmr1aXGcOaACz-_JFfM_Eoophc.jpg"
}
```
## 8. 钱方微信支付
目前通过钱方支付对接微信支付。(暂不使用官方微信支付)
微信支付相关接口需要在微信浏览器内使用。
**特别说明**:接口前缀 `/api/v1/frontend/weixin` 改为 `/api/v1/frontend/qfpay`。
### 8.1 [公开] 获取微信授权登录 URL
`GET /api/v1/frontend/qfpay/authorize-url?res_id=<res_id>&table_id=<table_id>`
* `res_id` <- `rid`
* `table_id` <- `tid`
返回数据结构:
```
{
"url": "https://openapi-test.qfpay.com/tool/v1/get_weixin_oauth_code?app_code=2DAB13A0AF4D4031820149BCD58188D0&redirect_uri=https%3A%2F%2Fapi.digwtx.com%3Frid%3D1&mchid=2w1exh5NlY&sign=4A6CBC119E5F948F6525EAF1E202156A"
}
```
得到 `url` 之后在微信浏览器中打开, 然后会跳转到 `redirect_uri` 对应的页面, 并且随带 `code` 查询参数, `code` 参数用于获取用户的 `openid`。
`redirect_uri` 对应的前端页面需要读取 `code` 参数,并通过下面的接口获取用户的 `openid`。
### 8.2 [公开] 获取微信 openid
`POST /api/v1/frontend/qfpay/get-weixin-openid?res_id=<res_id>`
提交参数:
```
{
"code": "iweirwieriweriweirwei" // 上一步获取的 code
}
```
若 `code` 无效或已使用,则返回 HTTP 状态码 400。
返回数据结构:
```
{
"openid": "olaIk1gMClTA5_RnkC7hvmhVpChE"
}
```
`openid` 在微信支付时需要使用,`openid` 有可能会变化(钱方可能会有多个公众号),前端需要保存 `openid`。
## 9. 微信
### 9.1 获取 JSSDK 配置信息
### 9.2 获取微信授权登录 URL
`GET /api/v1/frontend/weixin/authorize-url?res_id=<res_id>&table_id=<table_id>`
* `res_id` <- `rid`
* `table_id` <- `tid`
返回状态码 200, 返回数据结构如下:
```
{
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxe1224df14973c3c4&redirect_uri=http%3A%2F%2F192.168.199.149%3A8099%2F%3Frid%3D1%26tid%3D-1&response_type=code&scope=snsapi_userinfo&state=biz:1haIYC:UXDWpHpJ6kyV4YFgbBvOsw_EFyk#wechat_redirect"
}
```
得到 `url` 之后在微信浏览器中打开. 若用户授权登录, 则跳转到 `redirect_uri`, 并且随带 `code` / `state`参数, `code` 参数用于获取 `access_token`.
`redirect_uri` 对应的页面需要读取 `code` / `state` 参数。
### 9.3 获取微信 access_token
`POST /api/v1/frontend/weixin/access-token`
提交参数:
```
{
"res_id": 1, // 店铺标识
"invite_code": "邀请码", // 用户的邀请码
"code": "081by3xF1rgqj803NFuF17abxF1by3xD", // 跳转到 redirect_uri 附带的 code
"state": "biz%3A1h10MA%3AKSlANGsFNq96Qd2GvVTn7LDsE1A" // 跳转到 redirect_uri 附带的 state
}
```
说明:
* 在未登录状态(或登录已失效)调用时,请求中不要带 `Authorization` 请求头。
* 若请求后返回401状态码,需要重试。
* 若在系统中没有跟微信用户对应用户,则会创建一个新的用户(根据微信用户微信)。
* 在有效登录状态下调用此接口,会将微信用户与当前登录用户进行绑定。
若成功获取 `access_token`, 则返回状态码 200, 返回数据结构如下(与 `3.2 登录` 接口相同):
```
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjM0LCJpYXQiOjE1NTE3NTUwMTQsImV4cCI6MTU1MTg0MTQxNH0.NiT1uN_OKTYlTxn9TvZBTMyQKR8UBYg4g2tciKVyMIo", // 用户 token
"openid": "wieriweirweir", // 在微信公众号下的openid
"id": 34, // 用户ID
"ttl": 86400 // token 有效期(单位秒)
}
```
否则返回状态码 400. 主要是提示 `code` / `state` 无效.
## 10. Facebook
### 10.1 验证 access_token
`POST /api/v1/frontend/facebook/debug-token`
提交参数:
```
{
"res_id": 1, // 餐厅ID
"user_id": 23423424234, // facebook登录后得到的用户id
"access_token": "081by3xF1rgqj803NFuF17abxF1by3xD", // facebook 登录后得到的 accessToken
"name": "xxxx" // facebook 用户昵称
}
```
若验证成功,则返回状态码 200, 返回数据结构如下(与 `3.2 登录` 接口相同):
```
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjM0LCJpYXQiOjE1NTE3NTUwMTQsImV4cCI6MTU1MTg0MTQxNH0.NiT1uN_OKTYlTxn9TvZBTMyQKR8UBYg4g2tciKVyMIo", // 用户 token
"openid": "wieriweirweir", // 在微信公众号下的openid
"id": 34, // 用户ID
"ttl": 86400 // token 有效期(单位秒)
}
```
验证失败,返回状态码400。
## 11. 会员卡
说明:
* 若用户没有开通(或绑定)会员卡,则调用这些接口会返回状态码403.
### 11.1 开卡/领卡
`POST /api/v1/frontend/user-member-card/open`
若开卡成功,返回状态码200.
异常:
* 若已有会员卡,则返回状态码403.
### 11.2 绑定已有卡片
`POST /api/v1/frontend/user-member-card/bind`
提交数据:
```
{
"card_id": "xxx", // 会员卡号
"card_pwd": "", // 会员卡密码,允许为空密码
}
```
若卡号、密码验证成功且绑定成功,则返回状态码200.
异常:
* 若已有会员卡,则返回状态码403.
* 若会员卡已被绑定,则返回状态码400.
* 若卡号或密码错误,则返回状态码400.
### 11.3 查询会员信息
`GET /api/v1/frontend/user-member-card/query`
返回数据结构:
```
{
"cur_score": 480, // 当前积分
"status": 1,
"card_level": "", // 卡等级名称
"discount": 0.8, // 当前折扣,0.8表示8折
"discount_desc": "", // 当前折扣描述
"balance": "361.79", // 可用余额
"card_id": "wx001" // 卡号
}
```
### 11.4 查询消费明细(余额明细)
`GET /api/v1/frontend/user-member-card/consume-logs`
返回数据结构:
```
{
"count": 20,
"next": "/api/v1/frontend/user-member-card/consume-logs?page=2&page_size=20", // 下一页URL,没有则为 null
"previous": null, // 上一页URL,没有则为 null
"results": [ // 结果集
{
"ori_money": 13.6,
"act_money": 13.6, // 实际金额,若打折或赠送,则与 ori_money 值会不同
"description": "消费13.6,获得积分13\n", // 说明,只有中文,不支持多语言
"org_name": "DIGWTX01", // 餐厅名称
"amount": 361.79, // 余额,最多显示2位小数
"time": "2019-08-20T11:52:40+08:00", // 时间
"type": 2 // 1-充值,2-消费
},
// ...
]
}
```
### 11.5 查询积分明细
`GET /api/v1/frontend/user-member-card/score-logs`
返回数据结构:
```
{
"count": 20,
"next": "/api/v1/frontend/user-member-card/score-logs?page=2&page_size=20", // 下一页URL,没有则为 null
"previous": null, // 上一页URL,没有则为 null
"results": [ // 结果集
{
"org_name": "DIGWTX01", // 餐厅名称
"type": 7, // 见下面描述
"description": "消费13.6,获得积分13\n", // 说明,只有中文,不支持多语言
"save_score": 13, // 积分变化,正数表示增加,负数表示扣减
"time": "2019-08-20T11:52:40+08:00" // 时间
},
// ...
]
}
```
积分类型(`type`)说明:
* `6`/`9` - 充值赠送
* `7` - 消费获得
* `71` - 抵扣积分
* 其他数值 - 其他
### 11.6 解绑会员卡
`POST /api/v1/frontend/user-member-card/unbind`
若解绑成功,则返回状态码200. 前端需要刷新数据,因为会员卡没有了。
异常:
* 若解绑时用户无会员卡,则返回状态码403.
## 12. 会员充值
### 12.1 获取充值金额列表
`GET /api/v1/frontend/deposit-orders/prices`
返回数据结构:
```
{
"can_order": true, // 是否可以进行充值操作
"wechat": [ // 微信支付充值金额
{
"money_price": "1.00", // 计价货币金额,即实际充到会员卡的金额
"price": "5.00" // 对应的人民币金额,即实际需要支付的金额
},
{
"money_price": "10.00",
"price": "70.00"
},
{
"money_price": "100.00",
"price": "700.00"
}
],
"alipay": [] // 支付宝支付充值金额
}
```
目前只支持微信支付。
### 12.2 订单列表
`GET /api/v1/frontend/deposit-orders`
返回数据结构:
```
{
"count": 42,
"code": 0,
"next": "/api/v1/frontend/deposit-orders?page=2",
"previous": null,
"results": [
{
"id": 50,
"pay_type": "wechat", // 支付方式
"money_price": "1.00", // 要充值的金额
"price": "5.00", // 要支付的金额
"status": "success", // 状态,new-待支付,expired-已过期,success-已支付/已完成
"pay_time": "2019-08-13T11:49:48.590637+08:00", // 支付时间,未支付时为 null
"expiry_time": "2019-08-13T12:04:08.912040+08:00", // 过期时间
"create_time": "2019-08-13T11:49:08.912040+08:00" // 下单时间
},
// ...
]
}
```
### 12.3 下单
`POST /api/v1/frontend/deposit-orders`
提交参数:
```
{
"pay_type": "wechat", // 支付方式
"money_price": "10", // 要充值的金额
"price": "70" // 要支付的金额
}
```
`money_price` / `price` 由 `12.1` 接口提供。
下单成功返回状态码201,返回数据结构参考 列表 接口。
### 12.4 获取支付链接
`GET /api/v1/frontend/deposit-orders/<id>/pay-url`
返回数据结构:
```
{
"url": "http://xxx/xxxx" // 支付URL,跳转过去
}
```
异常:
* 若订单已过期或已支付,则返回状态码403.
支付成功后,会跳转到前端的结果页,同时在 URL 上附带餐厅ID和订单ID参数(示例 `?rid=1&doid=108` )查询参数。
### 12.5 查询支付结果
`GET /api/v1/frontend/deposit-orders/<id>/is-paid`
返回数据结构:
```
{
"is_paid": true, // 是否支付,true-已支付,false-未支付
"order": {
"id": 101,
"pay_type": "wechat",
"money_price": "1.00",
"price": "5.00",
"status": "success",
"pay_time": "2019-08-20T09:16:08.344958+08:00",
"deposit_time": null,
"expiry_time": "2019-08-20T09:30:52.853616+08:00",
"create_time": "2019-08-20T09:15:52.853616+08:00"
}
}
```
## 13 异常充值订单
### 13.1 异常订单列表
`GET /api/v1/frontend/deposit-orders/exceptions`
返回数据结构:
```
{
"count": 64,
"code": 0,
"next": "/api/v1/frontend/deposit-orders/exceptions?page=2", // 下一页URL,没有则为null
"previous": null, // 上一页URL,没有则为null
"results": [ // 结果集
{
"id": 251, // 订单id
"order_id": "D251", // 订单编号
"feedback": { // 申诉状态,未申诉则为null
"status": "new", // 申诉状态,new-待处理,success-已处理
"has_images": false
},
"pay_type": "wechat", // 支付类型
"money_price": "1.00", // 充值金额
"price": "5.00", // 付款金额(¥)
"status": "expired", // 状态,new-待支付,expired-已过期,success-已支付/已完成
"pay_time": null, / 支付时间
"deposit_time": null,
"expiry_time": "2019-09-02T18:35:52.392855+08:00",
"create_time": "2019-09-02T18:20:52.392855+08:00" // 下单时间
},
// ...
]
}
```
已处理/未处理 根据 `feedback.status` 判断:
* `new` 或者 `feedback`为空 - 未处理
* `success` - 已处理
已到账/未到账根据 **充值订单** 的 `status` 判断:
* `new` / `expired` - 未到账
* `success` - 已到账
### 13.2 充值订单申诉
`POST /api/v1/frontend/deposit-order-feedback`
提交数据:
```
{
"order": 251, // 充值订单id
"phone": "15389781522", // 联系电话
"reason": "xxx", // 申诉原因
"image_1": "", // 凭证图片1 URL
"image_2": "", // 凭证图片2 URL
"image_3": "", // 凭证图片3 URL
}
```
图片上传参考 `7.1` 接口。图片至少要1张。
**申诉原因** 前端固定传。
提交成功返回状态码 200.
返回状态码 400 的情况:
* 订单已申诉的
* 订单已完成的
## 14 异常收款订单
### 14.1 异常订单列表
`GET /api/v1/frontend/orders/exceptions`
返回数据结构(主要结构参考 `6.1` 接口):
```
{
"count": 1,
"code": 0,
"next": null, // 下一页URL,没有则为null
"previous": null,
"results": [ // 订单列表,数据结构参考 6.1 接口
{
"id": 1073,
"products": [
{
"product_item_id": 6009,
"product_item_name": "Spaghetti Americana",
"desc": "",
"pu": 0,
"unit": "",
"price": 7.5,
"origin_price": 0.0,
"qty": 1,
"box_fee": 0.0,
"total_price": 7.5,
"thumbnail": "//pos-cn-node.oss-cn-beijing.aliyuncs.com//{90063812-9CAF-11E9-99B5-FCAA144EED3C}/6009-1.png?x-oss-process=image%2Fauto-orient%2C1%2Fresize%2Cm_fill%2Cw_256%2Ch_256%2Fquality%2Cq_85"
},
{
"product_item_id": 6005,
"product_item_name": "Garden Salad",
"desc": "",
"pu": 0,
"unit": "",
"price": 4.2,
"origin_price": 0.0,
"qty": 1,
"box_fee": 0.0,
"total_price": 4.2,
"thumbnail": "//pos-cn-node.oss-cn-beijing.aliyuncs.com//{90063812-9CAF-11E9-99B5-FCAA144EED3C}/6005-1.png?x-oss-process=image%2Fauto-orient%2C1%2Fresize%2Cm_fill%2Cw_256%2Ch_256%2Fquality%2Cq_85"
}
],
"table": {
"table_id": -1,
"table_name": "Quick Service"
},
"feedback": { // 申诉状态,未申诉则为null
"status": "new", // 申诉状态,new-待处理,success-已处理
"has_images": false
},
"customer_name": "234",
"customer_phone": "234234234234",
"customer_address": "中山市兴中道28号中山市兴中体育场|||123456",
"customer_count": 1,
"eat_type": 1,
"paid_type": 2,
"is_paid": false,
"paid_time": null,
"paid_mch": "caijinbao",
"plan_time": "2019-09-19T17:13:47.666000+08:00",
"total_price": 11.7,
"total_box_fee": 0.0,
"delivery_fee": 1.0,
"discount_fee": 2.34,
"is_tax_included": false,
"total_tax_fee": 0.07,
"total_fee": 10.43,
"total_fee_cny": 1.27,
"total_fee_cny_real": -1.0,
"take_code": "",
"take_code_datauri": "",
"lang": "zh-hans",
"remark": "",
"trade_no": "11568882596073",
"out_trade_no": "11568882596073X97833",
"out_order_id": "",
"order_head_id": 0,
"status": 0,
"create_time": "2019-09-19T16:43:16.698245+08:00",
"restaurant": 1,
"user_address": 18
}
]
}
```
已处理/未处理 根据 `feedback.status` 判断:
* `new` 或者 `feedback`为空 - 未处理
* `success` - 已处理
已收款/未收款根据 **is_paid** 判断:
* `true` - 已收款
* `false` - 未收款
### 14.2 订单申诉
`POST /api/v1/frontend/order-feedback`
提交数据:
```
{
"order": 251, // 订单id
"phone": "15389781522", // 联系电话
"reason": "xxx", // 申诉原因
"image_1": "", // 凭证图片1 URL
"image_2": "", // 凭证图片2 URL
"image_3": "", // 凭证图片3 URL
}
```
图片上传参考 `7.1` 接口。图片至少要1张。
**申诉原因** 前端固定传。
提交成功返回状态码 200.
返回状态码 400 的情况:
* 订单已申诉的
* 订单已完成的