訂單建單支付統一 — API 整合手冊

Gateway 前綴:/transaction
登入:使用者 session(StpUtil userInfo
成功:code=1000data 見各情境

標準驗證流程(成功情境)

  1. 1. POST /transaction/v1/user/order/create → 記下 data.orderId{OID}
  2. 2. POST /transaction/v1/user/order/item → 確認金額、點數與建單請求一致
  3. 3. POST /transaction/v1/user/order/{OID}/pay → 確認預扣點數、DDPay 建單、狀態流轉(建單時金額已定案,pay 仍須驗證租戶上下文、點數預扣、金流設定等執行期例外)
變數說明
{ENT_TID}企業租戶 businessType=0
{MCH_TID}商家租戶 businessType=1
{ACCOUNT_SESSION}付款人 session
{G_ENT_COMB}企業商品:方案 80 點 + 80 元/件,paymentOptionId=DEFAULT
{G_ENT_PT}企業商品:純 40 點
{G_ENT_CASH}企業商品:純 80 元
{G_MCH}商家商品:RATE_CONVERTED,單價 40 元;點數由後端依 1:1 換算(1 元 = 1 點,見 PointSystem.getRate

狀態碼對照(斷言請用數字 code,勿與名稱混淆)

名稱code常見時機
orderStatusCREATED0建單成功
orderStatusPAYMENT_PENDING20pay 後有 DDPay 待付
orderStatusDONE30純點 pay 完成,或 webhook 成功
pointsStatusNONE0無點數/純現金
pointsStatusRESERVED10pay 後 Redis 預扣中
pointsStatusCOMMITTED30扣點完成(totalMoney=0 的 pay 或 webhook)
paymentStatusNONE0未走金流
paymentStatusPENDING10DDPay 待付

注意:orderStatus=10POINTS_UNKNOWN(異常),不是 CREATED。

原始服務文件參考:新Order_建立到支付_流程與檔案說明.md §名詞與狀態碼。


API 參數說明

POST /transaction/v1/user/order/create

建立訂單(可無商品)。金額由後端依租戶 businessType、商品定價、pointsPlan 計算後落庫。

欄位必填說明
tenantId訂單所屬租戶 ID
title訂單標題
description訂單描述
moneyAmount非商品必填最後應付現金(折抵後剩餘,= 訂單 totalMoney)。須與 grossMoneyAmount 成對;商品單可省略由 server 計算
grossMoneyAmount非商品必填折抵前現金總額(企業=現金軸 cashLeg;商家=合併池 gross)。商家須 > 0;企業純點或僅券可省略
pointsPlan視情境點數支付規劃,見下表
items商品單必填商品明細,見下表
carrierId手機條碼載具,格式 / + 7 碼
carrierType保留欄位,server 固定 3J0002
npoban捐贈碼(3–7 位愛心碼或 8 位社福統編)
buyerIdentifier買方統編(8 位數字)
buyerName買方名稱

pointsPlan 子欄位

欄位說明
tenantPoints企業點(AiPool)。企業商品 PAYMENT_OPTIONS 時須等於 Σ(單件點數 × qty);商家合併池折抵 gross,可 ≤ gross(1:1 時折抵上限 = 商品現金總額)
districtPoints商圈點。企業商品訂單不可 > 0(90015)。商家 RATE_CONVERTED 合併池折抵 gross,本手冊 D 章以 tenantPoints/UUPON 為主
uuponPointsUUPON(AiCoin),折抵現金軸,1 TWD = 4 UUPON;不可與 tenantPoints 加總混算
couponIds優惠券 ID 陣列
earnLimitInfos賺屬性條件(有屬性賺時必填)

items[] 子欄位(商品訂單)

欄位必填說明
goodsId商品 ID
qty數量,預設 1,最小 1
paymentOptionId視商品多方案時必填;單一方案可省略

計價規則摘要

租戶類型點數軸(tenantPoints leg)現金軸(cashLeg/gross)totalMoney(DDPay 應付)
企業 businessType=0 非商品與現金軸獨立(不折抵 gross)grossMoneyAmountmax(0, cashLeg − uupon/4) = moneyAmount
企業 businessType=0 商品商品點數 leg 加總(雙軸獨立)商品現金 leg 加總max(0, cashLeg − uupon/4)
商家 businessType=1合併池:tenantPoints 折抵 grossgrossMoneyAmount(>0)max(0, gross − tenantPoints − uupon/4) = moneyAmount

非商品金額雙欄位(企業 A 章、商家 B 章;tenantPoints 企業不折現金軸)

欄位語意關係式(須前端帶入並自洽)企業範例(A2)商家範例(B1)
grossMoneyAmount折抵前現金總額商家非商品須 > 0;企業含現金軸時須 > 0100(現金軸)100(gross)
moneyAmount最後應付現金(DDPay)企業:gross − uupon/4;商家:gross − tenantPoints − uupon/450(100 − UUPON 50)60(100 − 30 tenant − 10 UUPON)

非商品含現金軸時,grossMoneyAmountmoneyAmount 必須成對帶入;後端驗證關係式與落庫值一致,供前端/APP 稽核客戶所見應付、實付。企業純租戶點(A3)或僅券(F2)可不帶雙欄位。

建單成功回傳 data.orderIddata.orderNo;訂單狀態 orderStatus=0(CREATED)、pointsStatus=0(NONE)、paymentStatus=0(NONE)。


POST /transaction/v1/user/order/{id}/pay

對已建單訂單發起支付:預扣點數(若有)+ 建立 DDPay(若 totalMoney>0)。金額以訂單落庫值為準,body 不帶金額。

欄位必填說明
tenantId須與訂單 tenantId 一致,供 loadMyOrder 查單;未帶或錯誤則 90200
reserveTimeoutSeconds點數預扣 TTL,60–86400 秒;不帶用系統預設
resultURLDDPay 付款成功導向(Web 金流)
cancelURLDDPay 付款取消導向

pay 行為摘要

條件行為
totalMoney=0 且有點數/UUPON/券預扣 → commit → orderStatus=30pointsStatus=30(COMMITTED)
totalMoney>0預扣點數/UUPON(若有)→ 檢查訂單租戶 DDPay 設定 → 建 DDPay → orderStatus=20pointsStatus=10(RESERVED)、paymentStatus=10(PENDING)
totalMoney>0 且無點數不預扣 → 建 DDPay → orderStatus=20pointsStatus=0(NONE)、paymentStatus=10
商品單含 tenantPoints預扣租戶為商品所屬租戶OrderPointTenantResolver.resolve),非 necessarily 訂單 tenantId
已完成(DONE)冪等回傳
付款中(PENDING)且已有 paymentUrl冪等回傳原連結

成功回傳 data.paymentUrl(純點為 null)、data.orderStatusdata.pointsStatusdata.paymentStatus


企業 — 非商品

A1 雙軸(tenantPoints + 現金)

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "A1",
    "grossMoneyAmount"
: 80,
    "moneyAmount"
: 80,
    "pointsPlan"
: {
        "tenantPoints"
: 40,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

企業雙軸:tenantPoints 不折現金,故 grossMoneyAmountmoneyAmount 相同(皆 80)。

/item 驗證:totalMoney=80pointsPlan.tenantPoints=40orderStatus=0(CREATED)

POST /transaction/v1/user/order/{OID}/pay


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "reserveTimeoutSeconds"
: 600,
    "resultURL"
: "https://example.com/ok",
    "cancelURL"
: "https://example.com/cancel"
}

/pay 驗證:pointsStatus=10(RESERVED);paymentUrl 非空;orderStatus=20(PAYMENT_PENDING);paymentStatus=10(PENDING);DDPay 金額 = 80


A2 現金 + UUPON 折抵

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "A2",
    "grossMoneyAmount"
: 100,
    "moneyAmount"
: 50,
    "pointsPlan"
: {
        "tenantPoints"
: 0,
        "districtPoints"
: 0,
        "uuponPoints"
: 200,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=50pointsPlan.uuponPoints=200

POST /transaction/v1/user/order/{OID}/pay(body 同 A1,tenantId={ENT_TID}

/pay 驗證:paymentUrl 非空,orderStatus=20;預扣 uuponPoints=200tenantPoints=0);pointsStatus=10(RESERVED);paymentStatus=10(PENDING)


A3 純租戶點

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "A3",
    "moneyAmount"
: 0,
    "pointsPlan"
: {
        "tenantPoints"
: 40,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=0pointsPlan.tenantPoints=40

POST /transaction/v1/user/order/{OID}/pay


    
    
    
  { "tenantId": "{ENT_TID}", "reserveTimeoutSeconds": 600 }

/pay 驗證:paymentUrl=nullorderStatus=30(DONE),pointsStatus=30(COMMITTED),paymentStatus=0(NONE)


A4 純現金

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "A4",
    "grossMoneyAmount"
: 100,
    "moneyAmount"
: 100,
    "pointsPlan"
: {
        "tenantPoints"
: 0,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=100

POST /transaction/v1/user/order/{OID}/pay(body 同 A1)

/pay 驗證:paymentUrl 非空,orderStatus=20pointsStatus=0(NONE),paymentStatus=10(PENDING)


A5 失敗 — 金額與點數皆 0

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "A5",
    "moneyAmount"
: 0,
    "pointsPlan"
: {
        "tenantPoints"
: 0,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

回傳:code=90041(不進 pay)


A6 失敗 — UUPON 超過現金軸

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "A6",
    "grossMoneyAmount"
: 50,
    "moneyAmount"
: 0,
    "pointsPlan"
: {
        "tenantPoints"
: 0,
        "districtPoints"
: 0,
        "uuponPoints"
: 204,
        "couponIds"
: []
    }

}

回傳:code=90207uuponPoints=204 折抵 51 元 > cashLeg 50)。若 UUPON 非 4 的倍數則 90208


A7 失敗 — 雙欄位關係不符(gross=0、money>0)

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "A7",
    "grossMoneyAmount"
: 0,
    "moneyAmount"
: 10,
    "pointsPlan"
: {
        "tenantPoints"
: 0,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

回傳:code=90203moneyAmount 須等於 grossMoneyAmount 折抵後金額;gross=0 時不可聲稱應付 10 元)

變體:僅帶 moneyAmount、省略 grossMoneyAmount(非純點)→ 同樣 90203


商家 — 非商品

B1 合併池(tenantPoints + UUPON + 現金)

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{MCH_TID}",
    "title"
: "B1",
    "grossMoneyAmount"
: 100,
    "moneyAmount"
: 60,
    "pointsPlan"
: {
        "tenantPoints"
: 30,
        "districtPoints"
: 0,
        "uuponPoints"
: 40,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=60(gross 100 − tenant 30 − UUPON 40/4=10)

POST /transaction/v1/user/order/{OID}/paytenantId={MCH_TID},含 resultURL / cancelURL

/pay 驗證:預扣 tenantPoints=30uuponPoints=40pointsStatus=10(RESERVED);paymentUrl 非空;DDPay 金額 = 60;orderStatus=20


B2 合併池 — 純點付清

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{MCH_TID}",
    "title"
: "B2",
    "grossMoneyAmount"
: 100,
    "moneyAmount"
: 0,
    "pointsPlan"
: {
        "tenantPoints"
: 100,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=0

POST /transaction/v1/user/order/{OID}/paytenantId={MCH_TID}

/pay 驗證:paymentUrl=nullorderStatus=30(DONE),pointsStatus=30(COMMITTED),paymentStatus=0(NONE)


B3 失敗 — 折抵超過 gross

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{MCH_TID}",
    "title"
: "B3",
    "grossMoneyAmount"
: 50,
    "moneyAmount"
: 0,
    "pointsPlan"
: {
        "tenantPoints"
: 60,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

回傳:code=90207


企業 — 商品(PAYMENT_OPTIONS)

C1 雙軸(租戶點 + 現金 + UUPON)

{G_ENT_COMB} 80 點 + 80 元/件,qty=2:

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "C1",
    "moneyAmount"
: 140,
    "items"
: [
        {
 "goodsId": "{G_ENT_COMB}", "qty": 2, "paymentOptionId": "DEFAULT" }
    ]
,
    "pointsPlan"
: {
        "tenantPoints"
: 160,
        "districtPoints"
: 0,
        "uuponPoints"
: 80,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=140pointsPlan.tenantPoints=160items[0].point=80items[0].points=160items[0].moneyAmount=160

POST /transaction/v1/user/order/{OID}/pay(body 同 A1,tenantId={ENT_TID}

/pay 驗證:


C2 純租戶點商品

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "C2",
    "moneyAmount"
: 0,
    "items"
: [{ "goodsId": "{G_ENT_PT}", "qty": 1 }],
    "pointsPlan"
: {
        "tenantPoints"
: 40,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=0pointsPlan.tenantPoints=40

POST /transaction/v1/user/order/{OID}/paytenantId={ENT_TID}

/pay 驗證:paymentUrl=nullorderStatus=30(DONE),pointsStatus=30(COMMITTED),預扣並 commit 40 點


C3 純現金商品 + UUPON

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "C3",
    "moneyAmount"
: 60,
    "items"
: [{ "goodsId": "{G_ENT_CASH}", "qty": 1 }],
    "pointsPlan"
: {
        "tenantPoints"
: 0,
        "districtPoints"
: 0,
        "uuponPoints"
: 80,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=60pointsPlan.uuponPoints=80

POST /transaction/v1/user/order/{OID}/pay(body 同 A1)

/pay 驗證:預扣 UUPON 80;pointsStatus=10(RESERVED);paymentUrl 非空,金額 = 60;orderStatus=20


C4 失敗 — tenantPoints 與商品不符

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "C4",
    "items"
: [{ "goodsId": "{G_ENT_PT}", "qty": 1 }],
    "pointsPlan"
: {
        "tenantPoints"
: 39,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

回傳:code=90212


C5 失敗 — moneyAmount 與後端不符

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "C5",
    "moneyAmount"
: 139,
    "items"
: [
        {
 "goodsId": "{G_ENT_COMB}", "qty": 2, "paymentOptionId": "DEFAULT" }
    ]
,
    "pointsPlan"
: {
        "tenantPoints"
: 160,
        "districtPoints"
: 0,
        "uuponPoints"
: 80,
        "couponIds"
: []
    }

}

回傳:code=90203(點數已正確,僅測現金不符)


C6 失敗 — 庫存不足

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "C6",
    "items"
: [{ "goodsId": "{G_ENT_CASH}", "qty": 99999 }],
    "pointsPlan"
: {
        "tenantPoints"
: 0,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

回傳:code=90039


C7 企業 RATE_CONVERTED — 可少付 tenantPoints(選測)

前置:企業商品 pricingType=RATE_CONVERTED(非 {G_ENT_COMB} 固定方案)。

建單時 tenantPoints 超過上限 → 90212少於上限且 moneyAmounttotalMoney 一致 → 成功。


C8 失敗 — 企業商品不可帶商圈點

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "C8",
    "items"
: [{ "goodsId": "{G_ENT_PT}", "qty": 1 }],
    "pointsPlan"
: {
        "tenantPoints"
: 40,
        "districtPoints"
: 1,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

回傳:code=90015(此訂單不可使用商圈點)


商家 — 商品(RATE_CONVERTED)

商家商品定價類型為 RATE_CONVERTED:gross 由 paymentOptions[].cashAmount × qty 加總;可折抵點數由後端 固定 1:1 換算(items[].point = cashAmount,不依租戶 transferToMoney)。商品單只需帶 moneyAmount(= 折抵後 totalMoney),不需 grossMoneyAmount

D1 合併池付清(僅點)

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{MCH_TID}",
    "title"
: "D1",
    "moneyAmount"
: 0,
    "items"
: [{ "goodsId": "{G_MCH}", "qty": 2, "paymentOptionId": "DEFAULT" }],
    "pointsPlan"
: {
        "tenantPoints"
: 80,
        "districtPoints"
: 0,
        "uuponPoints"
: 0,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=0(gross 80 = 40×2;tenantPoints=80 合併池 1:1 折抵完畢);items[0].point=40items[0].points=80

POST /transaction/v1/user/order/{OID}/paytenantId={MCH_TID}

/pay 驗證:orderStatus=30(DONE),pointsStatus=30(COMMITTED),預扣 80 點後 commit;paymentUrl=null


D2 合併池 + 剩餘現金

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{MCH_TID}",
    "title"
: "D2",
    "moneyAmount"
: 5,
    "items"
: [{ "goodsId": "{G_MCH}", "qty": 2, "paymentOptionId": "DEFAULT" }],
    "pointsPlan"
: {
        "tenantPoints"
: 0,
        "districtPoints"
: 0,
        "uuponPoints"
: 300,
        "couponIds"
: []
    }

}

/item 驗證:totalMoney=5(gross 80 − UUPON 300/4=75);moneyAmount 須與折抵後剩餘一致

POST /transaction/v1/user/order/{OID}/paytenantId={MCH_TID},含 URL)

/pay 驗證:預扣 UUPON 300;pointsStatus=10(RESERVED);paymentUrl 非空,金額 = 5;orderStatus=20


建單後支付 — 專項與失敗

以下可獨立執行,或作為各情境 pay 步驟的補充說明。{OID} = 對應建單回傳之 orderId

E1 冪等 — 已完成訂單重複 pay

前置:A3 或 C2 已 pay 至 orderStatus=30

POST /transaction/v1/user/order/{OID}/paytenantId 正確)

回傳:code=1000orderStatus=30,不重複扣點


E2 冪等 — 付款中重複 pay

前置:A4 已 pay 一次,orderStatus=20,已有 paymentUrl

再次 POST /transaction/v1/user/order/{OID}/pay

回傳:相同 paymentUrl,不重複建 DDPay


E3 商品單扣點租戶

前置:C1 建單;商品 tenantId ≠ 訂單 {ENT_TID}

POST /transaction/v1/user/order/{OID}/pay 後查 Mongo pointReservation 或點數異動:


E4 失敗 — pay 租戶錯誤

前置:任意建單成功

POST /transaction/v1/user/order/{OID}/pay


    
    
    
  { "tenantId": "WRONG_TENANT_ID" }

回傳:code=90200(查無訂單)


E5 失敗 — DDPay 設定缺失

前置:A4 建單;訂單租戶未設定 DDPAY_shopId

POST /transaction/v1/user/order/{OID}/paytenantId 正確)

回傳:code=90204

自治商圈:HQ session 代 MEMBER 付時,檢查的是訂單租戶設定,非 HQ 自身設定


E6 失敗 — 訂單狀態不可 pay

前置:訂單已 FAILEDTIMEOUT

POST /transaction/v1/user/order/{OID}/pay

回傳:code=90202


發票 / 其他校驗

F1 載具格式錯誤

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "F1",
    "grossMoneyAmount"
: 100,
    "moneyAmount"
: 100,
    "carrierId"
: "ABC123"
}

回傳:code=90044


F2 僅 coupon、無金額無點

POST /transaction/v1/user/order/create


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "title"
: "F2",
    "moneyAmount"
: 0,
    "pointsPlan"
: { "couponIds": ["{COUPON_ID}"] }
}

回傳:code=1000(有 coupon 允許建單)


訂單明細查詢(共用驗證)

POST /transaction/v1/user/order/item


    
    
    
  {
    "tenantId"
: "{ENT_TID}",
    "orderId"
: "{OID}"
}

成功回傳欄位對照:

欄位說明
totalMoneyDDPay 應付現金
pointsPlan與建單請求一致(null 補 0 後)
orderStatus建單後 0(CREATED);有 DDPay 時 pay 後 20;純點 pay 完成 30
pointsStatus建單後 0(NONE);pay 預扣後 10(RESERVED);純點 pay 完成 30(COMMITTED)
paymentStatus建單後 0(NONE);DDPay 待付 10(PENDING)
orderNo顯示用編號
items[].point單件點數(商品單;商家 RATE_CONVERTED 快照 = cashAmount,1:1)
items[].points合計點數(單件 × qty)
items[].moneyAmount合計現金 leg(單件現金 × qty)