渐进式结账(Single Page Checkout)链路说明

本文档说明 渐进式结账checkout_type = single_page)的完整业务流程:单页 UI + 分步 homeapi JSON API,创单时机早于地址步骤。含 MySQL / Redis、接口调用方式。与当前仓库代码一致。

相关:standard-checkout-flow.mdone-page-checkout-flow.md

目录


1. 背景与目标

  • 店铺配置 checkout_process = single_page 时,用户进入 渐进式结账(路由注释:渐进式结账)。
  • 前端在同一页面展示进度条,但 每一步独立 POST/GET API;与标准多页 逻辑等价,只是交互改为 JSON + 无 full page redirect。
  • 创单时机email 一步(仅邮箱,无地址)→ checkout_type = single_page
  • 与单页 one_page 的关键差异:分步写库 vs one_page 的 presave/complete 全量块。

2. 入口与页面

2.1 checkout_token

同标准:购物车 / buynow / instant → CartService::getCheckoutUrl() 等。

2.2 URL

/{storeId}-{random}/single-page-checkouts/{checkout_token}
/{storeId}-{random}/single-page-checkouts/{checkout_token}/gateway   // 支付网关页

2.3 SSR 首屏(home)

路由GET /:store_random/single-page-checkouts/:checkout_token
Controllerapp/home/controller/OrderSinglePage.php::index

动作Service
权限 / 校验checkCheckoutPermission, _checkCheckoutCart
计价CartService::getList
渲染Liquid 渐进式 checkout 模板

传入:cartshipping_address 预填、客服列表 hint、checkout_token 等。

支付网关 SSRGET .../gatewayOrderSinglePage::gateway

支付 POSTPOST /:store_random/single-page-checkouts/:checkout_tokenOrder::checkout


3. 业务流程总览

sequenceDiagram
    participant FE as 渐进式前端
    participant API as homeapi/OrderSinglePage
    participant SP as CheckoutSinglePageService
    participant OS as OrderService

    FE->>API: POST email
    API->>SP: saveEmail → createOrder
    FE->>API: POST contact-information
    API->>OS: saveContactInformation
    FE->>API: GET shippings / payments
    FE->>API: POST shipping-method
    API->>SP: saveShippingMethod + saveDiyOffer
    FE->>API: POST use-coupon(可选)
    FE->>API: POST payment-method
    API->>SP: savePaymentMethod
    FE->>API: POST gateway
    API-->>FE: checkout_url
    FE->>FE: 跳转 gateway 页支付

步骤守卫:前端根据 cart.order.has_* 控制进度;后端各 POST 不强制 redirect,返回最新 cart JSON。


4. homeapi 路由一览

前缀/:store_random/single-page-checkouts/:checkout_token
Controllerapp/homeapi/controller/OrderSinglePage.php

写操作前均调用 CheckoutService::checkCheckoutPermission()

4.1 只读 GET

方法路径后缀Service响应
GET/CartService::getList完整 cart(调试用)
GET/service-listgetServiceList客服列表
GET/shippingsgetShippingMethods物流方案数组
GET/paymentsgetPaymentLists支付方式(可 query payment_id+token 过滤快捷支付)
GET/insurance-settinggetInsuranceSetting运费险配置
GET/shipping-addongetShippingInfoAddon附属信息
GET/diy-offerOrderDiyOfferHandleService::getOfferList积分/Seel 等 offer
GET/payment-form/:payment_idgetPaymentForm支付表单

调用示例

GET /{store_random}/single-page-checkouts/{checkout_token}/shippings
Cookie: shop_global_visit_id=...; shop_checkout_visit_id=...

响应:{ "code": 0, "data": [ /* shipping methods */ ] }

4.2 写操作 POST

方法路径后缀Request 校验类Service
POST/emailsaveEmailRuleCheckoutSinglePageService::saveEmail
POST/contact-informationsaveContactInformationRuleOrderService::saveContactInformation
POST/shipping-methodsaveShippingMethodRulesaveShippingMethod
POST/payment-methodsavePaymentMethodRulesavePaymentMethod
POST/gatewaygetGatewayUrlRulesavePaymentMethod + getGatewayUrl
POST/use-couponuseCouponRuleuseCoupon
POST/cancel-couponcancelCouponHandler

Content-Typeapplication/jsonCheckoutSinglePageRequest 读 raw body)。


5. 各步骤明细

5.1 POST email(创单)

Body

{
  "email": "user@example.com",
  "is_subscribed": 1,
  "contact": "optional phone or line id"
}

流程CheckoutSinglePageService::saveEmail 44 行起):

顺序动作MySQLRedis
1查/自动注册顾客o_customerdel customer cache
2黑名单
3无 order 时 inserto_ordercheckout_type=single_page)、初值 current_*
4saveProductso_order_product
5UTM / sysinfo / tago_order_*
6saveCheckoutCartcheckoutcart type=order

事件OrderCreaterecoveryForAdmin OrderOnePageCreate)。

响应

{
  "code": 0,
  "data": {
    /* cart from getList */,
    "single_page_data": { "is_auto_register": true }
  }
}

5.2 POST contact-information(联系信息 + 地址)

Body(同标准 contact_information):

{
  "first_name": "John",
  "last_name": "Doe",
  "country_id": 840,
  "address1": "123 Main St",
  "city": "NY",
  "province": "NY",
  "zip": "10001",
  "phone": "+1...",
  "remember_me": 1
}

流程OrderService::saveContactInformation(已有 order → 更新地址;更新顾客姓名)。

MySQLRedis
o_order_shipping_address, o_order, o_customer, 可选 o_customer_addresscustomer cache del

事件AddAddressInfo(经 saveShippingAddress)。

响应:最新 cart JSON。


5.3 GET shippings / payments / insurance / diy-offer

已有 order + 地址 后调用;内部先 CartService::getList 再算物流/支付。

Redis:读 checkoutcart + cart;不写库


5.4 POST shipping-method(物流 + 附加)

Body

{
  "shipping_id": 123,
  "insurance": 1.99,
  "tip_price": 2.0,
  "note": "leave at door",
  "offer_from_name": "{\"points\":100}",
  "checkoutinfo": "{}",
  "admin_id": 0
}

流程(262–280 行,与 标准 shipping_method POST 等价):

OrderService::saveShippingMethod
→ OrderService::saveOtherInformation
→ OrderDiyOfferHandleService::saveDiyOffer(checkout_token, offer_from_name)
MySQLRedis
o_order, o_order_shipping_zone_plan, o_order_diy_offer, insurance/tip/note

事件AddShippingInfo

响应:最新 cart JSON。


5.5 POST use-coupon / cancel-coupon

use-coupon Body

{ "code": "SAVE10", "email": "user@example.com" }

流程CouponHandlerService::useCouponHandlersaveProducts 重算行价。

响应

{
  "code": 0,
  "data": {
    /* coupon handler result */,
    "price_struct": {
      "shipping_price": 0,
      "coupon_price": 10,
      ...
    }
  }
}

cancel-coupon:清券 + 返回 cart。


5.6 POST payment-method(支付方式)

BodybillingAddress=1 时仅两字段):

{
  "payment_id": 456,
  "billingAddress": 1
}

或完整账单地址字段(同标准)。

流程OrderService::savePaymentMethod + billing。

MySQLRedis
o_order, o_order_billing_address可能 clearCart guest

事件AddPaymentInfo


5.7 POST gateway(进入支付)

Body:同 payment-method(再次确认 payment + billing)。

流程

savePaymentMethod
→ getGatewayUrl → CheckoutService::generateCheckoutGatewayUrl

响应

{ "code": 0, "data": { "checkout_url": ".../single-page-checkouts/{token}/gateway" } }

前端 redirect 到 gateway 页,由 home Order::checkoutgetPaymentForm 完成支付。


6. 与标准结账的步骤对应

渐进式 API标准 step
POST email(标准无单独步;合并在 contact_information 创单)
POST contact-informationcontact_information POST(地址部分)
POST shipping-methodshipping_method POST
POST payment-methodpayment_method POST
POST gatewaypayment_gateway GET
POST use-couponGET coupon/use

7. Redis 交互

Key说明
{store}:checkoutcart:{token}全程
{store}:guestcart / customercartgetList
{store}:pre_use_coupon_*
{store}:customer:{id}email/地址更新后 del

one_page presave 锁。


8. MySQL 表(按 API)

API
emailo_order, o_order_product, o_order_customer_sysinfo, o_customer
contact-informationo_order_shipping_address, o_order, o_customer
shipping-methodo_order, o_order_shipping_zone_plan, o_order_diy_offer
payment-method / gatewayo_order, o_order_billing_address
use-coupono_order, o_order_product

9. 可用优惠(promotion / coupon / diy_offer / order_diy_offer)

总对照见 promotions-by-checkout-type.md

体系渐进式 single_page
diy_offer✅ 各 API 返回的 getList
promotion✅ 同上
couponPOST use-couponsavePreCoupon(email 后)
order_diy_offershipping-method POSToffer_from_name

顺序:与标准相同(CartService::getList);创单后每次 POST 返回最新 cart 含 reconcile 结果。

落库

API优惠相关
email 创单o_order 初值含 current_promotion_price
use-couponcoupon_* + saveProducts
shipping-methodo_order_diy_offer + 运费
任意写 API 后 GET 态getList reconcile

10. 事件一览

API事件
email 创单OrderCreate, recoveryForAdmin
contact-informationAddAddressInfo
shipping-methodAddShippingInfo
payment-method / gatewayAddPaymentInfo
进入 checkout URLBeginCheckout(与标准相同)

11. 相关文件索引

模块路径
homeapi Controllerapp/homeapi/controller/OrderSinglePage.php
home 页面app/home/controller/OrderSinglePage.php
Servicecommon/services/CheckoutSinglePageService.php
Requestapp/homeapi/req/CheckoutSinglePageRequest.php
共享订单写common/services/OrderService.php
路由app/homeapi/route/route.php(146–162 行)

12. 联调备忘

  • 必须先 POST email 才有 cart.order;否则 shippings 等 GET 可能缺 order 上下文。
  • use-couponcode 校验规则为 require|number(Request 定义),实际券码若为字母需确认前端/规则是否一致。
  • diy_offer(积分)在 shipping-methodoffer_from_name,不是单独 complete 步(对比 one_page)。
  • 网关 URL 固定为 single-page-checkouts/.../gateway,与 standard 的 ?step=payment_gateway 不同。
  • 调试可直接 GET .../single-page-checkouts/{token} 看完整 cart。

对比:one-page-checkout-flow.md · standard-checkout-flow.md