商品订单从加购到支付成功,沿下面这条链走。活动商品不另开下单接口:加购时带上活动编号,后面仍进同一个 StoreOrderController。
路由文件:app/api/route/v1.php、app/api/route/v2.php。
购物车、确认订单、创建订单、支付都要登录。商品详情和多数活动列表不用登录。支付回调 ANY /api/pay/notify/:type 不登录。
库存在创建订单时扣,不在支付成功时扣。
主链路
看商品/活动
→ POST /api/cart/add 写入购物车或立即购买缓存,返回 cartId
→ POST /api/order/confirm 用 cartId 生成确认页,缓存订单,返回 key
→ POST /api/order/computed/:key 重算金额
→ POST /api/order/create/:key 落库并扣库存,返回 orderId
→ GET /api/order/cashier/:orderId
→ POST /api/order/pay
余额 / 0 元 → 当场 paySuccess
微信 / 支付宝 / 通联 → 只返回支付参数
→ ANY /api/pay/notify/:type 渠道回调后再 paySuccess| 步骤 | 接口 | 控制器 |
|---|---|---|
| 1 加购 | POST /api/cart/add | app/api/controller/v1/store/StoreCartController.php → add |
| 2 确认 | POST /api/order/confirm | app/api/controller/v1/order/StoreOrderController.php → confirm |
| 3 算价 | POST /api/order/computed/:key | 同文件 → computedOrder |
| 4 提交 | POST /api/order/create/:key | 同文件 → create |
| 5 收银台 | GET /api/order/cashier/:orderId/[:type] | 同文件 → cashier |
| 6 支付 | POST /api/order/pay | 同文件 → pay |
| 7 回调 | ANY /api/pay/notify/:type | app/api/controller/v1/PayController.php → notify |
cartId 和 key 的交接:
new=1或is_new=1时,add不写eb_store_cart。购物车放进缓存,返回的cartId就是缓存键。confirm读这份数据,再写入user_order_{uid}{key}。computed和create都用这个key读缓存。创建订单加锁键是
orderCreate{key}。
1. 活动商品(下单前)
列表和详情多数不用登录。购买都回到 POST /api/cart/add,用不同字段决定购物车类型。
| 活动 | 看商品 | 下单前的参与动作 | cart/add 字段 | 购物车 type |
|---|---|---|---|---|
| 普通商品 | GET /api/product/detail/:id/[:type] → app/api/controller/v1/store/StoreProductController.php → detail | 无 | 只传 productId | 0 |
| 秒杀 | app/api/controller/v1/activity/StoreSeckillController.php | 无,详情进加购 | secKillId | 1 |
| 砍价 | app/api/controller/v1/activity/StoreBargainController.php | 先 start,好友 help,砍成再加购 | bargainId | 2 |
| 拼团 | app/api/controller/v1/activity/StoreCombinationController.php | 参团前看 pink;团已结束时 add 直接失败 | combinationId,参团再带 pinkId | 3 |
| 预售 | app/api/controller/v1/activity/StoreAdvanceController.php → detail | 无 | advanceId | 6 |
积分商品不走这条购物车,见文末「积分订单」。
秒杀
文件:app/api/controller/v1/activity/StoreSeckillController.php
| 接口 | 方法 | 作用 |
|---|---|---|
GET /api/seckill/index | index | 秒杀时间段 |
GET /api/seckilllist | getskill | 最近开始的两条活动 |
GET /api/seckill/list/:time | lst | 某个时间段的商品 |
POST /api/seckill_activity_list | activity_lst | 按活动查商品 |
GET /api/seckill/detail/:id | detail | 按时段的商品详情 |
GET /api/seckill_activity_detail/:id | activity_detail | 不按时段的商品详情 |
GET /api/seckill/code/:id | code | 海报,不参与下单 |
秒杀加购后,价格、标题、规格留在缓存的 cartInfo.productInfo 上。confirm 不重新加载秒杀商品。create 若请求里的 seckill_id 为 0,用缓存购物车第一行的 seckill_id。StoreOrderCreateServices::createOrder 再用这个 id 查出 activity_id 写入订单。
库存校验:StoreCartServices::checkProductStock 的 type 1 调用 StoreSeckillServices::checkSeckillStock。
砍价
文件:app/api/controller/v1/activity/StoreBargainController.php
| 接口 | 方法 | 作用 |
|---|---|---|
GET /api/bargain/config | config | 列表配置,未登录 |
GET /api/bargain/list | lst | 商品列表,未登录 |
GET /api/bargain/detail/:id | detail | 详情,需登录 |
POST /api/bargain/start | start | 开启砍价 |
POST /api/bargain/start/user | start_user | 开启人信息 |
POST /api/bargain/help | help | 帮砍 |
POST /api/bargain/help/price | help_price | 本次砍掉的金额 |
POST /api/bargain/help/count | help_count | 进度 |
POST /api/bargain/help/list | help_list | 帮砍记录 |
GET /api/bargain/user/list | user_list | 我参与的砍价 |
POST /api/bargain/user/cancel | user_cancel | 取消砍价 |
share、poster、posterInfo 是分享和海报,不参与下单。
拼团
文件:app/api/controller/v1/activity/StoreCombinationController.php
| 接口 | 方法 | 作用 |
|---|---|---|
GET /api/combination/list | lst | 商品列表,未登录 |
GET /api/combination/banner_list | banner_list | 轮播,未登录 |
GET /api/combination/detail/:id | detail | 详情,未登录 |
GET /api/combination/pink/:id | pink | 开团信息,需登录 |
POST /api/combination/remove | remove | 取消开团 |
poster、posterInfo、code 是海报,不参与下单。拼团记录在支付成功后由 StorePinkServices::createPink 创建。
预售
| 接口 | 文件 | 方法 |
|---|---|---|
GET /api/advance/list | app/api/controller/v1/store/StoreProductController.php | advanceList |
GET /api/advance/detail/:id | app/api/controller/v1/activity/StoreAdvanceController.php | detail |
同文件还有 index,当前 v1.php 未给预售首页单独配这条路由。到期下架走定时任务:GET /api/crontab/advance_off → app/api/controller/v1/CrontabController.php → downAdvance。
2. 购物车
文件:app/api/controller/v1/store/StoreCartController.php。路由在 app/api/route/v1.php,都要登录。
| 接口 | 方法 | 作用 |
|---|---|---|
POST /api/cart/add | add | 加购或立即购买。new=1 或 is_new=1 为立即购买 |
GET /api/cart/list | lst | 购物车列表 |
POST /api/cart/num | num | 改数量 |
POST /api/cart/del | del | 删除 |
GET /api/cart/count | count | 数量和金额 |
add 的入参:
| 字段 | 含义 |
|---|---|
productId | 普通商品编号,必填 |
cartNum | 数量,默认 1 |
uniqueId | 规格唯一值 |
new / is_new | 1 立即购买,0 加入购物车 |
secKillId | 秒杀编号,有值则 type = 1 |
bargainId | 砍价编号,有值则 type = 2 |
combinationId | 拼团编号,有值则 type = 3 |
pinkId | 拼团团队 id。团已结束则 add 失败 |
advanceId | 预售编号,有值则 type = 6 |
判断顺序是秒杀、砍价、拼团、预售。都没有时 type = 0,并调用 StoreCartServices::checkVipGoodsBuy。
接着进入 app/services/order/StoreCartServices.php:
setCart:写入eb_store_cart,或在立即购买时写入缓存并返回缓存键。checkProductStock:按 type 校验活动库存。
同一控制器还有 reChange(重选规格)。当前 v1.php 没有这条路由。改规格走 v2:
| 接口 | 文件 | 方法 |
|---|---|---|
POST /api/v2/reset_cart | app/api/controller/v2/store/StoreCartController.php | resetCart |
GET /api/v2/cart_list | 同文件 | getCartList |
POST /api/v2/set_cart_num | 同文件 | setCartNum |
GET /api/v2/get_attr/:id/:type | app/api/controller/v2/store/StoreProductController.php | getProductAttr |
PC 购物车是另一条:GET /api/pc/get_cart_list → app/api/controller/pc/CartController.php → getCartList。下单仍回到 v1 的确认和创建接口。
3. 提交订单
文件:app/api/controller/v1/order/StoreOrderController.php。
| 顺序 | 接口 | 方法 | 接着看 |
|---|---|---|---|
| 配送方式 | POST /api/order/check_shipping | checkShipping | app/services/order/StoreOrderServices.php → checkShipping |
| 确认页 | POST /api/order/confirm | confirm | 同文件 → getOrderConfirmData |
| 算价 | POST /api/order/computed/:key | computedOrder | app/services/order/StoreOrderComputedServices.php → computedOrder |
| 创建 | POST /api/order/create/:key | create | 先 OrderService::preOrder 冻结积分,再 app/services/order/StoreOrderCreateServices.php → createOrder |
| 再次购买 | POST /api/order/again | again | StoreOrderServices::againOrder,把旧单商品重新放进购物车 |
确认页 confirm
入参:cartId、new、addressId、shipping_type(默认 1)、is_gift。
cartId 不是字符串或为空时失败。运费模板 id 1 不存在时失败。
getOrderConfirmData 读购物车(立即购买读缓存,普通加购读 eb_store_cart),算出确认页数据,并把购物车快照写入 user_order_{uid}{key}。返回体里的 key 就是后面两步的路径参数。
这里不接收 seckill_id。活动身份已经在购物车缓存里。
算价 computedOrder
入参:addressId、couponId、payType、useIntegral、mark、combinationId、pinkId、seckill_id、bargainId、shipping_type、is_gift。
seckill_id、combinationId、bargainId 在这里用来把订单当成活动单,跳过优惠券和商城积分抵扣,不负责把活动 id 写入订单表。
算完商城价格后,结果写入缓存 user_order_integral_{key}。
key 若已经是该用户的订单号,直接返回已有订单,不再重算。
创建 create
入参包括地址、优惠券、支付方式、备注、拼团、秒杀、砍价、预售、配送方式、自提人、门店、发票、自定义表单、礼物标记。方法内把 useIntegral 固定写成 1。
顺序:
用
key读user_order_{uid}{key}。缓存不存在则失败。StoreOrderCreateServices::getNewOrderId('cp')预生成订单号。CacheService::lock('orderCreate' . $key)里调用createOrder。创建抛错且已经冻结积分时,
OrderService::cancelOrder解冻。成功返回
orderId和key。
createOrder 里写入 eb_store_order。秒杀订单的 seckill_id 在这里落库:请求值为 0 时,取缓存购物车第一行的 seckill_id,后面的行不覆盖。再用 StoreSeckillServices::value(['id' => $seckillId], 'activity_id') 取活动 id。
购物车快照由 StoreOrderCartInfoServices::setCartInfo 另存,不负责写订单上的活动字段。
扣库存:StoreOrderCreateServices::decGoodsStock。秒杀再进 StoreSeckillServices::decSeckillStock,同时减秒杀 SKU、秒杀库存和关联的普通商品库存。退款回库存在 StoreOrderRefundServices::regressionStock,秒杀对应 incSeckillStock。
确认页还会调用
| 接口 | 文件 | 方法 |
|---|---|---|
GET /api/address/list | app/api/controller/v1/user/UserAddressController.php | address_list |
GET /api/address/default | 同文件 | address_default |
POST /api/address/edit | 同文件 | address_edit |
GET /api/coupons/order/:price | app/api/controller/v1/store/StoreCouponsController.php | order |
GET /api/pay/config | app/api/controller/v1/PayController.php | config |
pay/config 返回微信支付、支付宝、余额、线下支付、好友代付,是否开启看系统配置。
4. 支付和回调
POST /api/order/pay → StoreOrderController::pay。
入参:uni(订单号)、paytype、quitUrl、type。type = 1 时付款人取当前登录用户,否则取订单上的 uid(好友代付)。
方法内先写 2 秒的 PAY_LOCK_{uni},避免重复支付。已删除、已支付、拼团已结束的订单在这里拒绝。应付金额先改写订单的 is_channel、pay_uid,代付时还会换成新的 cp 订单号。
四条去向:
| 情况 | 控制器之后 | 是否进渠道回调 |
|---|---|---|
| 应付金额小于等于 0 | app/services/order/StoreOrderSuccessServices.php → zeroYuanPayment → paySuccess | 否 |
yue 余额 | app/services/pay/YuePayServices.php → yueOrderPay → 商品单走 paySuccess | 否 |
offline 线下 | StoreOrderServices::setOrderTypePayOffline,并通知管理员 | 否,等后台确认收款 |
| 微信 / 支付宝 / 通联 | app/services/pay/OrderPayServices.php → beforePay | 是 |
余额不足时 yueOrderPay 返回 pay_deficiency,不改支付状态。会员卡余额支付走 OtherOrderServices::paySuccess,不进商品订单的 paySuccess。
beforePay 按订单有没有 member_type 决定回调业务名:商品订单是 product,会员卡是 member。然后 Pay::pay 向渠道下单,把前端调起支付需要的参数返回给 pay。
渠道回调
渠道付完后请求这条,不需要登录:
ANY /api/pay/notify/:type
→ app/api/controller/v1/PayController.php → notify:type:
| type | 处理 |
|---|---|
alipay | Pay('ali_pay')->handleNotify() |
v3wechat | Pay('v3_wechat_pay')->handleNotify() |
wechat | 配置了 pay_wechat_type 走 v3,否则走默认微信支付 |
routine | MiniProgramService::handleNotify() |
路径中含 allin | Pay('allin_pay')->handleNotify($type) |
控制器只按渠道拆报文。支付成功后的业务不在控制器里:
crmeb\services\pay\Pay::handleNotify
→ 事件 NotifyListener(注册在 app/event.php)
→ app/listener/pay/NotifyListener.php → handle
→ crmeb/utils/Hook.php
类 PayNotifyServices,前缀 wechat,再加上 attach
→ app/services/pay/PayNotifyServices.php → wechatProduct
→ app/services/order/StoreOrderSuccessServices.php → paySuccessHook 把前缀 wechat 和 attach 拼成方法名。attach = product 才是 wechatProduct。
PayNotifyServices 另外两个方法不是商品订单:
| 方法 | 业务 |
|---|---|
wechatUserRecharge | 余额充值,UserRechargeServices::rechargeSuccess |
wechatMember | 会员卡,OtherOrderServices::paySuccess |
商品订单已支付或订单不存在时,wechatProduct 直接返回成功,避免渠道重复通知。
paySuccess 做什么
文件:app/services/order/StoreOrderSuccessServices.php,方法 paySuccess。
订单写成已支付:
paid = 1,写入pay_type、pay_time,有渠道单号时写入trade_no。拼团订单且未退款时,
StorePinkServices::createPink。非线下支付时,
LuckLotteryServices::setCacheLotteryNum缓存抽奖次数。触发事件:
OrderPaySuccessListenerNoticeListener(用户支付成功、管理员支付成功)OutPushListener(对外推送)CustomNoticeListener、CustomEventListener
OrderPaySuccessListener 在 app/listener/order/OrderPaySuccessListener.php 的 handle:
写订单状态「用户付款成功」
普通商品赠送购买优惠券(秒杀、砍价、拼团不赠)
发票标为已支付,满足配置时延迟自动开票
虚拟商品自动发货(拼团除外)
微信、支付宝、通联写入资金流水
小票打印、支付成功消息、分销等级、商品支付日志
不要当成商品支付回调
| 接口 | 文件 | 方法 | 实际用途 |
|---|---|---|---|
ANY /api/order_call_back | StoreOrderController.php | callBack | 商家寄件回调 |
ANY /api/transfer/notify/:type | PayController.php | transferNotify | 商户转账回调 |
POST /api/sms/pay/notify | app/api/controller/v1/PublicController.php | sms_pay_notify | 短信购买通知 |
5. 支付之后
仍在 app/api/controller/v1/order/StoreOrderController.php。
| 接口 | 方法 | 作用 |
|---|---|---|
GET /api/order/data | data | 订单数量统计 |
GET /api/order/list | lst | 订单列表 |
GET /api/order/detail/:uni/[:cartId] | detail | 订单详情 |
GET /api/order/express/:uni/[:type] | express | 查看物流 |
POST /api/order/take | take | 确认收货,进 StoreOrderTakeServices::takeOrder |
POST /api/order/comment | comment | 评价 |
POST /api/order/product | product | 按购物车唯一值取订单商品,评价页使用 |
POST /api/order/cancel | cancel | 未支付取消,进 StoreOrderServices::cancelOrder |
POST /api/order/del | del | 用户删除订单 |
GET /api/order/friend_detail | friendDetail | 好友代付详情 |
GET /api/order/gift_detail/:oid | giftDetail | 礼物详情 |
POST /api/order/receive_gift/:oid | receiveGift | 领取礼物 |
GET /api/order/refund_detail/:uni/[:cartId] | refund_detail | 退款详情(旧) |
GET /api/order/refund/reason | refund_reason | 退款理由 |
GET /api/order/refund/cart_info/:id | refundCartInfo | 退款中间页商品 |
POST /api/order/refund/cart_info | refundCartInfoList | 退款商品列表 |
POST /api/order/refund/apply/:id | applyRefund | 申请退款,进 StoreOrderRefundServices |
同文件还有 refund_express。正在使用的退货物流接口不是它,见下一节。
POST /api/order/refund/verify 路由指向 StoreOrderController/refund_verify,这个类里没有该方法。
未支付超时取消不走用户操作:GET /api/crontab/order_cancel → app/api/controller/v1/CrontabController.php → orderUnpaidCancel → StoreOrderServices::orderUnpaidCancel。
退款单
文件:app/api/controller/v1/order/StoreOrderRefundController.php。
| 接口 | 方法 | 作用 |
|---|---|---|
GET /api/order/refund/list | refundList | 退款单列表 |
GET /api/order/refund/detail/:uni | refundDetail | 退款单详情 |
POST /api/order/refund/cancel/:uni | cancelApply | 取消退款申请 |
POST /api/order/refund/express | applyExpress | 填写退货物流 |
GET /api/order/refund/del/:uni | delRefund | 删除退款单 |
申请退款先走 StoreOrderController::applyRefund,之后的列表、详情、取消、退货物流走这个控制器。
发票(v2)
文件:app/api/controller/v2/order/StoreOrderInvoiceController.php。
| 接口 | 方法 |
|---|---|
POST /api/v2/order/make_up_invoice | makeUp |
GET /api/v2/order/invoice_list | list |
GET /api/v2/order/invoice_detail/:uni | detail |
GET /api/v2/order/down_invoice/:id | downInvoice |
用户发票抬头在 app/api/controller/v2/user/UserInvoiceController.php:invoiceList、invoice、saveInvoice、setDefaultInvoice、getDefaultInvoice、delInvoice。
6. 不走商品订单控制器的两条线
积分订单
商品浏览:app/api/controller/v1/activity/StoreIntegralController.php
| 接口 | 方法 |
|---|---|
GET /api/store_integral/index | index |
GET /api/store_integral/list | lst |
GET /api/store_integral/detail/:id | detail |
下单和售后:app/api/controller/v1/order/StoreIntegralOrderController.php。不经过 cart/add,也不进 StoreOrderController。
| 接口 | 方法 | 服务 |
|---|---|---|
POST /api/store_integral/order/confirm | confirm | StoreIntegralOrderServices::getOrderConfirmData |
POST /api/store_integral/order/create | create | StoreIntegralOrderServices::createOrder |
GET /api/store_integral/order/detail/:uni | detail | |
GET /api/store_integral/order/list | lst | |
GET /api/store_integral/order/express/:uni | express | |
POST /api/store_integral/order/take | take | |
POST /api/store_integral/order/del | del |
会员卡和线下收款
文件:app/api/controller/v1/order/OtherOrderController.php。
| 接口 | 方法 | 作用 |
|---|---|---|
POST /api/user/member/card/create | create | 购买会员卡 |
POST /api/order/offline/check/price | computed_offline_pay_price | 计算线下付款金额 |
POST /api/order/offline/create | create | 创建线下付款单 |
GET /api/order/offline/pay/type | pay_type | 线下付款方式 |
创建后若走微信或支付宝,回调 attach 是 member,进入 PayNotifyServices::wechatMember → OtherOrderServices::paySuccess。余额支付在 YuePayServices::yueOrderPay 的 pay_member 分支,同样进 OtherOrderServices::paySuccess。
7. 手机端店员订单
类名也叫 StoreOrderController,文件是 app/api/controller/v1/admin/StoreOrderController.php。路径前缀 /api/admin/order/,给店员发货、改价、退款、核销,不是用户下单。
| 接口 | 方法 |
|---|---|
GET /api/admin/order/statistics | statistics |
GET /api/admin/order/data | data |
GET /api/admin/order/list | lst |
GET /api/admin/refund_order/list | refundOrderList |
GET /api/admin/order/detail/:orderId | detail |
GET /api/admin/refund_order/detail/:uni | refundOrderDetail |
GET /api/admin/order/delivery/gain/:orderId | delivery_gain |
POST /api/admin/order/delivery/keep/:id | delivery_keep |
POST /api/admin/order/price | price |
POST /api/admin/order/remark | remark |
POST /api/admin/order/agreeExpress | agreeExpress |
POST /api/admin/refund_order/remark | refundRemark |
POST /api/admin/order/offline | offline |
POST /api/admin/order/refund | refund |
POST /api/order/order_verific | order_verific |
GET /api/admin/order/express/:uni/[:type] | express |
8. 按文件打开的顺序
看一笔普通商品或活动商品订单时,按这个顺序打开:
app/api/controller/v1/store/StoreCartController.php→addapp/services/order/StoreCartServices.php→setCart、checkProductStockapp/api/controller/v1/order/StoreOrderController.php→confirmapp/services/order/StoreOrderServices.php→getOrderConfirmDataStoreOrderController.php→computedOrderapp/services/order/StoreOrderComputedServices.php→computedOrderStoreOrderController.php→createapp/services/order/StoreOrderCreateServices.php→createOrder、decGoodsStockStoreOrderController.php→cashier、payapp/services/pay/OrderPayServices.php→beforePay(微信、支付宝、通联)app/services/pay/YuePayServices.php→yueOrderPay(余额)app/services/order/StoreOrderSuccessServices.php→zeroYuanPayment(0 元)app/api/controller/v1/PayController.php→notifyapp/listener/pay/NotifyListener.php→handleapp/services/pay/PayNotifyServices.php→wechatProductapp/services/order/StoreOrderSuccessServices.php→paySuccessapp/listener/order/OrderPaySuccessListener.php→handle
非特殊说明,本文版权归 Code. 所有,转载请注明出处.
本文标题: CRMEB 订单流程