第三方金流串接 – 資料庫交易設定

更新於 2024/10/28閱讀時間約 27 分鐘

※ 在orderController.ts檔案中定義createOrder: 

public createOrder: IOrderController['createOrder'] = (req, res, _next) => {
let { paymentProvider, paymentWay, contents } = req.body
console.log(
"~ file: ordreController.ts ~ line 52 ~ OrderController ~ paymentProvider, paymentWay, contents",
paymentProvider,
paymentWay,
contents
);
res.jsonp({ status: 'success' });

};

程式碼解說:

1.方法定義:

  • public createOrder:定義一個公開的 createOrder 方法,符合 IOrderController 介面中 createOrder 方法的定義。
  • (req, res, _next):這個方法接受三個參數:req(請求對象)、res(回應對象)和 _next(下一個中介軟體函數,這裡未使用)。

2.請求主體

  • 從 req.body 中提取 paymentProvider、paymentWay 和 contents 屬性。這些屬性應該符合 CreateOrderRequestParams 介面。
  • 在後續的程式中可能會需要重新賦值這些變數,所以使用let。

3.紀錄日誌

  • 將 paymentProvider、paymentWay 和 contents 的值輸出到控制台,用於除錯。

4.回應客戶端

  • res.json 是 Express.js 提供的方法,用來向客戶端回傳 JSON 格式的資料。
  • 這裡回傳了一個物件 { status: 'success' },表示操作已成功完成。

createOrder 的主要用途

  1. 接收請求:從客戶端接收一個包含訂單詳情的 HTTP 請求。
  2. 解析數據:從請求主體中提取訂單的詳細資訊,例如支付提供者、支付方式和訂單內容。
  3. 處理邏輯:根據提取的數據執行必要的業務邏輯,例如計算總金額、檢查產品庫存等。
  4. 保存訂單:將處理後的訂單數據保存到資料庫中。
  5. 回應客戶端:返回一個成功或失敗的回應給客戶端,通常包括訂單的詳細資訊或錯誤訊息。

※ 建立order路由

在routes資料夾中,建立order.ts檔案:

raw-image

1.匯入模組:

import express from 'express';
import { ControllerContext } from "@/manager/controllerManager";

程式碼解說:

  • express:從 express 模組匯入,用來創建路由器。
  • ControllerContext:從 controllerManager 匯入,代表控制器上下文的型別。

2.定義函數 mountOrderRouter

export const mountOrderRouter = ({
controllerCtx
}: { controllerCtx: ControllerContext }) => {}

程式碼解說:

  • 定義並導出 mountOrderRouter 函數。
  • 函數接受一個參數 controllerCtx,類型為 ControllerContext,其中包含了所有的控制器。
  • 通過這個參數,可以訪問並調用 orderController 的方法來處理訂單相關的請求。

3.設置路由:

let router = express.Router();
router.post('/create', controllerCtx.orderController.createOrder);

程式碼解說:

  • 使用 express.Router() 創建一個新的路由器實例。
  • 使用 router.post('/create', controllerCtx.orderController.createOrder) 設置一個 POST 路徑 /create,當這個路徑被請求時,調用 orderController 的 createOrder 方法來處理請求。

4.返回路由器:

return router;

程式碼解說:

  • 返回已設定好的路由器,這樣可以將它掛載到應用程式的主路徑上。

※ 建立createController來製造一個新的 OrderController 實例

raw-image


1.靜態方法 createController

public static createController(
{ knexSql, orderModel, productModel }:
{ knexSql: Knex; orderModel: IOrderModel; productModel: IProductModel })

}

程式碼解說:

  • 靜態方法:定義一個靜態方法 createController,這意味著你可以直接通過類別來調用這個方法,而不需要創建類別的實例。
  • 參數:
    • knexSql:資料庫連接實例,用於資料庫操作。
    • orderModel:訂單模型,用於操作和管理訂單資料。
    • productModel:產品模型,用於操作和管理產品資料。

2.創建並返回 OrderController 實例

return new OrderController({ knexSql, orderModel, productModel });

程式碼解說:

  • 使用傳入的參數創建一個新的 OrderController 實例。
  • 將 knexSql, orderModel, 和 productModel 傳遞給 OrderController 的建構函數進行初始化。

※ controllerManager.ts中建立 OrderController 

raw-image

1.匯入 IOrderController 和 OrderController

import { IOrderController, OrderController } from "@/controller/orderController";

程式碼解說:

  • IOrderController:介面,定義了訂單控制器應該具備的方法和結構。
  • OrderController:類別,實現了 IOrderController 介面,包含處理訂單相關邏輯的方法。

2.匯入 Knex

import { Knex } from "knex";

程式碼解說:

  • 匯入 knex 模組中的 Knex 類型,用於資料庫操作。

3.定義 ControllerContext 介面

export interface ControllerContext {
orderController: IOrderController; //新增
}

程式碼解說:

  • 新增 orderController 屬性到 ControllerContext 介面,表示控制器上下文中包含了訂單控制器。

4.定義 controllerManger 函數

export const controllerManger = ({ knexSql, modelCtx }:
{ knexSql: Knex; modelCtx: ModelContext; }): ControllerContext => {}

程式碼解說:

  • 函數接受兩個參數:knexSql 和 modelCtx,分別是資料庫連接和模型上下文。

5.初始化 orderController

const orderController = OrderController.createController({
knexSql,
orderModel: modelCtx.orderModel,
productModel: modelCtx.productModel,
});

程式碼解說:

  • 使用 createController 靜態方法來創建 orderController 的實例,傳入資料庫連接、訂單模型和產品模型。

6.返回控制器上下文

return {
orderController
}

程式碼解說:

  • 返回一個包含 orderController 的物件,這個物件的類型符合 ControllerContext 介面。

※ 在app.ts新增程式碼 

class App {
private knexSql: Knex;//新增

constructor() {
this.knexSql = createDatabase();
this.controllerCtx = controllerManger({
knexSql: this.knexSql,

})

}
private routerSetup() {

    this.app.use('/orders', mountOrderRouter({ controllerCtx: this.controllerCtx }))

  }

1.類別定義和建構函數

程式碼解說:

  • knexSql 屬性:定義了一個私有屬性 knexSql,類型為 Knex,用於資料庫操作。
  • 建構函數:
    • 初始化資料庫連接:調用 createDatabase() 函數初始化 knexSql。
    • 初始化控制器上下文:使用 controllerManger 函數,傳入 knexSql 來初始化 controllerCtx。

2.路由設置方法

程式碼解說:

  • 掛載訂單路由:使用 this.app.use 將訂單路由掛載到 /orders 路徑,並調用 mountOrderRouter 函數,傳入 controllerCtx。

※ 在postman驗證 

輸入驗證資料:

raw-image
raw-image


驗證結果:

raw-image
raw-image

※ 驗證方式

在orderController.ts新增程式碼 

public createOrder: IOrderController['createOrder'] = (req, res, _next) => {

//1.資料驗證

//contents [{id,amount,price}, ...]

if (paymentProvider !== "ECPAY" && paymentProvider !== "PAYPAL")

res.json({ status: "failed", message: "paymentProvider not valid" });

};

程式碼解說:

  • 資料驗證:在處理訂單創建之前,先對請求資料進行驗證。
  • paymentProvider 驗證:
    • 檢查 paymentProvider 是否為 "ECPAY" 或 "PAYPAL"。
    • 如果 paymentProvider 不是這兩者之一,回應客戶端一個 JSON 物件,表示操作失敗,並包含錯誤訊息 "paymentProvider not valid"。

使用npm套件驗證 — express validator

安裝軟體:

npm install express validator

在orderController.ts新增程式碼 

import { isEmpty } from "lodash";
import { body } from 'express-validator';
//1.資料驗證
public createOrderValidator = () => {
const paymentProviderValidator = (value: any) => {
return [PaymentProvider.ECPAY, PaymentProvider.PAYPAL].includes(value);
}

const paymentWayValidator = (value: any) => {
return [PaymentWay.CVS, PaymentWay.PAYPAL].includes(value);
}
const contentValidator = (value: OrderContent[]) => {
if (isEmpty(value)) false;

for (const product of value) {
if ([product.productId, product.amount, product.price].some((val) => !val))
return false;
}
return true;
}

return [
//設定驗證不同參數的內容是否合法
body('paymentProvider', 'Invalid payment provider').custom(paymentProviderValidator),
body('paymentWay', 'Invalid payment way').custom(paymentWayValidator),
body('contents', 'Invalid product contents')
.isArray()
.custom(contentValidator),

]
}

程式碼解說:

1.方法定義:

public createOrderValidator = () => {}
  • 這是一個公開的方法,為了驗證訂單創建請求,返回一組驗證規則。

2.驗證函數:

const paymentProviderValidator = (value: any) => {

return [PaymentProvider.ECPAY, PaymentProvider.PAYPAL].includes(value);

}

const paymentWayValidator = (value: any) => {

return [PaymentWay.CVS, PaymentWay.PAYPAL].includes(value);

}

const contentValidator = (value: OrderContent[]) => {

if (isEmpty(value)) false;

for (const product of value) {

if ([product.productId, product.amount, product.price].some((val) => !val))

return false;

}

return true;

}
  • 驗證支付提供者是否為合法值。
  • 驗證訂單內容是否為非空陣列,且每個產品的 productId、amount 和 price 都存在。

3.返回驗證規則:

return [
body('paymentProvider', 'Invalid payment provider').custom(paymentProviderValidator),
body('paymentWay', 'Invalid payment way').custom(paymentWayValidator),
body('contents', 'Invalid product contents')
.isArray()
.custom(contentValidator),
]
  • 設定驗證不同參數的內容是否合法:
    • paymentProvider 必須是合法的支付提供者。
    • paymentWay 必須是合法的支付方式。
    • contents 必須是非空陣列且符合內容驗證器規則。

在orderController.ts新增一組驗證規則 

export interface IOrderController

{ createOrderValidator(): ValidationChain[];//新增 }

程式碼解說:

定義方法結構

  • createOrderValidator() 方法被定義在 IOrderController 介面中,強制所有實現這個介面的類別都必須實現這個方法。
  • 返回 ValidationChain[]:這表明方法會返回一組驗證規則,這些規則用於驗證訂單創建請求中的參數。

在orderController.ts新增一組驗證數據是否合法 

 if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}

程式碼解說:

  • if (!errors.isEmpty()):檢查是否有任何驗證錯誤。
  • return res.status(400).json({ errors: errors.array() }):
    • 如果有錯誤,則返回一個 HTTP 400 錯誤狀態碼,表示請求不合法。
    • 同時返回一個 JSON 對象,包含所有驗證錯誤。

驗證結果:

raw-image


※ 在router檔案中的order.ts加入中介層

raw-image


 router.post(
'/create',
//middleware中介層
controllerCtx.orderController.createOrderValidator(),//新增
//controller create Order正式的內容
controllerCtx.orderController.createOrder)

程式碼解說:

  • 中介層(middleware)使用:
    • 在設置路由時,將這個驗證方法作為中介層,確保每次收到請求時都先進行數據驗證。
  • 處理邏輯:如果驗證通過,則由 createOrder 方法處理請求。

※ transaction 資料庫交易主要流程

  1. 開始交易:使用 knex.transaction() 開始交易。
  2. 設置隔離級別:如果提供了 isolation,則設置交易隔離級別。
  3. 執行回調函數:調用回調函數執行具體的交易操作。
  4. 提交事務:如果回調函數成功,提交交易。
  5. 處理錯誤:如果回調函數失敗,回滾交易並根據錯誤類型進行重試或拋出異常。
  6. 重試機制:在特定錯誤(如死鎖)發生時,進行重試,直到達到最大重試次數。

※ 在utils檔案中的index.ts加入transaction 資料庫交易

raw-image

※ 定義了一個名為 transactionHandler 的函數,用於處理資料庫事務並提供重試機制

程式碼解說:

參數:

  1. knex: Knex:資料庫連接實例。
  2. callback: (trx: Knex.Transaction) => Promise<T>:執行事務的回調函數,接受一個事務對象 trx。
  3. options:一個包含以下可選參數的物件:
    • retryTimes?: number:最大重試次數,預設值為 100。
    • maxBackOff?: number:最大回退時間(毫秒),預設值為 1000。
    • isolation?: ISOLATION_LEVELS:事務隔離級別。

1.資料隔離性

可以讓你在設計和實現資料庫操作時,根據應用需求選擇適當的隔離級別來保證數據的一致性和完整性。

enum ISOLATION_LEVELS {
READ_UNCOMMITTED = 'READ_UNCOMMITTED',
READ_COMMITTED = 'READ_COMMITTED',
REPEATABLE_READ = 'REPEATABLE_READ',
SERIALIZABLE = 'SERIALIZABLE'
}

程式碼解說:

  1. READ_UNCOMMITTED:
    • 讀取未提交:允許一個事務讀取另一個事務尚未提交的變更。這種隔離級別風險最高,因為會有「髒讀」的問題。
  2. READ_COMMITTED:
    • 讀取已提交:只允許讀取已提交的變更,防止「髒讀」,但可能會有「不可重複讀」的問題,即同一事務中讀取的數據可能會改變。
  3. REPEATABLE_READ:
    • 可重複讀:確保同一事務中的多次讀取結果一致,防止「髒讀」和「不可重複讀」,但可能會有「幻讀」問題,即在事務期間其他事務新增的記錄會被讀取到。
  4. SERIALIZABLE:
    • 可序列化:最高級別的隔離,所有事務按順序執行,防止「髒讀」、「不可重複讀」和「幻讀」。這種隔離級別會帶來較高的性能開銷。

2.解構選項參數

const { retryTimes = 100, maxBackOff = 1000, isolation } = options;

程式碼解說:

  • retryTimes:設置重試次數,如果未提供則默認為 100。
  • maxBackOff:設置最大回退時間(毫秒),如果未提供則默認為 1000。
  • isolation:事務隔離級別,默認為未設置。

3.初始化重試計數器

let attempts = 0;

程式碼解說:

  • 初始化 attempts(追蹤或交易重試的次數) 變數為 0,表示尚未進行任何重試。
  • 當交易處理失敗並需要重試時,attempts 會自增,用來追蹤已進行的重試次數。

4.定義 execTransaction 函數: 這個內部函數用於執行事務,並在失敗時進行重試。

const execTransaction = async (): Promise<T> => {
const trx = await knex.transaction();
try {
if (isolation) await trx.raw(`SET TRANSACTION ISOLATION LEVEL ${isolation}`);
const result = await callback(trx);
await trx.commit();
return result;
} catch (err: any) {
await trx.rollback();
if (err.code !== '1205') throw err;
if (attempts > retryTimes) throw Error('[Transaction] retry times is up to max');
attempts++;
await sleep(maxBackOff);
return execTransaction();
}
};

程式碼解說:

  • execTransaction 是一個泛型異步函數,返回類型為 Promise<T>,表示該函數會返回一個解決為類型 T 的承諾。
  • 使用 knex.transaction() 開始一個新的交易,並將交易對象存儲在 trx 變數中。
  • 如果指定了 isolation(隔離級別),則設置該事務的隔離級別。
  • 執行傳入的回調函數 callback,並將 trx 作為參數傳遞。
  • 如果回調函數成功,則提交交易並返回結果。
  • 如果回調函數失敗,則回滾交易。
  • 如果錯誤代碼不是 1205(通常表示死鎖),則重新拋出錯誤。
  • 檢查重試次數是否超過 retryTimes,如果超過則拋出錯誤。
  • 增加 attempts 計數器,等待指定的回退時間後,重新執行 execTransaction。

sleep 函數:用於在重試前等待指定時間。

function sleep(maxBackOff: number) {
return new Promise((resolve) => setTimeout(() => resolve(1), maxBackOff));
}

程式碼解說:

  • maxBackOff:表示延遲的時間,單位為毫秒。
  • setTimeout 用於設置一個定時器,在 maxBackOff 毫秒後執行回調函數。
  • 回調函數中調用 resolve(1),解決(resolve)這個承諾,表示延遲結束。



    全端網頁開發專業知識分享
    留言0
    查看全部
    avatar-img
    發表第一個留言支持創作者!
    ※ 訂單開立流程 → 完結的流程 前端資料驗證。 將商品的數量寫入(預扣)→ id。 利用ID去打第三方金流的API來產生第三方金流的訂單。 當使用者繳完錢之後,第三方金流他會打我們提供的update資訊的API。 ※ 從前端需要傳入的資料: 商品ID、商品數量、使用哪個payment
    ※ 路由設定: 在routes資料夾中,建立product.ts: ※ 設置產品相關的路由: 1.匯入模組: import express from 'express'; import { ControllerContext } from '@/manager/controllerM
    ※ 建立一個管理者的資料夾,負責建立不同的管理 — manager: ※ 在manager資料夾中,建立modelManager.ts: ※ 設定資料庫模型的管理: 匯入模組: import { IProductModel, ProductModel } from "@/model/prod
    ※ 認識MVC架構: ※ 認識lodash: Lodash 是一個很實用的 JavaScript 函式庫。它提供了很多預設的實用函數,可以更容易地處理數據、操作物件和字串,並控制函數的執行。目的是為了簡化重複性的代碼,減少開發時間和錯誤。 Lodash 中常用的功能: 陣列處理:_.map
    ※ 認識Knex.js: Knex.js是一個專門串接資料庫的抽象化層,它支援多種關聯式資料庫,包括 PostgreSQL、MySQL、MariaDB、SQLite3、Oracle 和 Amazon Redshift 等。好處是連接以上的資料庫時,可以直接使用Knex.js的語法,就會自動創建出
    ※ 架構設計 ※ 資料庫規劃 id:流水號,唯一代替產品名稱的辨識代碼,AUTO_INCREMENT。 name:VARCHAR(255),NOT NULL。 amount:INTEGER(整數),UNSIGNED(不能是負數)。 description:TEXT,描述產品。 pre_
    ※ 訂單開立流程 → 完結的流程 前端資料驗證。 將商品的數量寫入(預扣)→ id。 利用ID去打第三方金流的API來產生第三方金流的訂單。 當使用者繳完錢之後,第三方金流他會打我們提供的update資訊的API。 ※ 從前端需要傳入的資料: 商品ID、商品數量、使用哪個payment
    ※ 路由設定: 在routes資料夾中,建立product.ts: ※ 設置產品相關的路由: 1.匯入模組: import express from 'express'; import { ControllerContext } from '@/manager/controllerM
    ※ 建立一個管理者的資料夾,負責建立不同的管理 — manager: ※ 在manager資料夾中,建立modelManager.ts: ※ 設定資料庫模型的管理: 匯入模組: import { IProductModel, ProductModel } from "@/model/prod
    ※ 認識MVC架構: ※ 認識lodash: Lodash 是一個很實用的 JavaScript 函式庫。它提供了很多預設的實用函數,可以更容易地處理數據、操作物件和字串,並控制函數的執行。目的是為了簡化重複性的代碼,減少開發時間和錯誤。 Lodash 中常用的功能: 陣列處理:_.map
    ※ 認識Knex.js: Knex.js是一個專門串接資料庫的抽象化層,它支援多種關聯式資料庫,包括 PostgreSQL、MySQL、MariaDB、SQLite3、Oracle 和 Amazon Redshift 等。好處是連接以上的資料庫時,可以直接使用Knex.js的語法,就會自動創建出
    ※ 架構設計 ※ 資料庫規劃 id:流水號,唯一代替產品名稱的辨識代碼,AUTO_INCREMENT。 name:VARCHAR(255),NOT NULL。 amount:INTEGER(整數),UNSIGNED(不能是負數)。 description:TEXT,描述產品。 pre_
    你可能也想看
    Google News 追蹤
    Thumbnail
    *合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
    Thumbnail
    徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
    Thumbnail
    ※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
    Thumbnail
    在前一篇我們已經成功地建立簽核表單及簽核節點並關聯回請假表單,而本篇會接著介紹如何管理簽核節點狀態並同步更新簽核表單狀態。
    Thumbnail
    本文介紹瞭如何在後端系統開發時設計不同表單的簽核流程,包括請假表單和採購表單。以及如何動態生成簽核表單,並建立簽核節點。另外還介紹瞭如何利用繼承來簡化簽核流程的設定。
    Thumbnail
    前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
    ※ Express 專案步驟筆記清單 Node.js 環境建置核對 新增專案資料夾 設定 package.json npm init -y 設定程式入口為 app.js 安裝 Express:npm install express 設定主程式 app.js 建構應用程式伺服器 設定
    Thumbnail
    Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
    Thumbnail
    Accept:用戶端能夠接收的內容類型。 Accept: text/plain, text/html Accept-Charset:瀏覽器可以接受的字元編碼集。 Accept-Charset: utf8 Accept-Encoding:指定瀏覽器可以支援的web伺服器返回內容壓縮編碼
    Thumbnail
    HTTP伺服器端 package main import ( "net/http" ) type Refer struct { handler http.Handler refer string } //實現中介軟體邏輯​ func (this *Refer) ServeHTTP(
    Thumbnail
    *合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
    Thumbnail
    徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
    Thumbnail
    ※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
    Thumbnail
    在前一篇我們已經成功地建立簽核表單及簽核節點並關聯回請假表單,而本篇會接著介紹如何管理簽核節點狀態並同步更新簽核表單狀態。
    Thumbnail
    本文介紹瞭如何在後端系統開發時設計不同表單的簽核流程,包括請假表單和採購表單。以及如何動態生成簽核表單,並建立簽核節點。另外還介紹瞭如何利用繼承來簡化簽核流程的設定。
    Thumbnail
    前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
    ※ Express 專案步驟筆記清單 Node.js 環境建置核對 新增專案資料夾 設定 package.json npm init -y 設定程式入口為 app.js 安裝 Express:npm install express 設定主程式 app.js 建構應用程式伺服器 設定
    Thumbnail
    Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
    Thumbnail
    Accept:用戶端能夠接收的內容類型。 Accept: text/plain, text/html Accept-Charset:瀏覽器可以接受的字元編碼集。 Accept-Charset: utf8 Accept-Encoding:指定瀏覽器可以支援的web伺服器返回內容壓縮編碼
    Thumbnail
    HTTP伺服器端 package main import ( "net/http" ) type Refer struct { handler http.Handler refer string } //實現中介軟體邏輯​ func (this *Refer) ServeHTTP(