2024-11-25|閱讀時間 ‧ 約 0 分鐘

綠界ECPAY平台金流串接實作

※ 安裝ECPAY SDK(軟體開發工具包):

載點:https://github.com/ECPay/ECPayAIO_Node.js

方法:

1.把code dowonload下來。


在專案中下載ECPAY SDK代碼的原因如下:

  • 參考學習:開發時,可以直接參考 SDK 內的內容。
  • 錯誤排查:SDK 內的資料夾和 JavaScript 內容,可以在開發過程中通過 console.log 方法來檢查,幫助我們發現參數中的錯誤。

操作流程如下:

  • 下載ECPayAIO_Node.js-master。
  • 將ECPayAIO_Node.js-master解壓縮。
  • ECPayAIO_Node.js-master資料夾移動到專案中的server資料夾裡面。
  • 在src資料夾中新增adapters資料夾adapters功能是將 API 包裝成一個或多個函數,方便在應用程式中使用。
  • adapters資料夾中新增ecpay資料夾新增index.ts撰寫關於ecpay內容。
  • 接著把ECPAY_Payment_node_js放進ecpay資料夾中其餘關於ECPayAIO_Node.js-master的檔案都刪除。

2.使用npm install。

※ 設計ECPAY 接口:

●定義IECPayAdapter接口:ecpay → index.js

export interface IECPayAdapter {
createCVS()
}

程式碼解說:

  • export 關鍵字讓 IECPayAdapter 接口可以在其他模組中被 import,進而確保不同的類別可以依據這個接口的定義來實現 createCVS 方法。IECPayAdapter 是用來適配 ECPay 的支付功能。

●createCVS()要搭配的參數查找步驟:

  • 打開 aio_check_out_cvs.js 文件
    • 打開專案目錄中的 aio_check_out_cvs.js 文件。
  • 查找 createCVS() 函數
    • 在文件中查找 createCVS() 函數定義和使用。通常可以使用編輯器的搜尋功能(如 Ctrl + F 或 Cmd + F)快速定位。
  • 檢查 createCVS() 函數的參數
    • 檢查這個函數的參數和相關的邏輯,了解它需要哪些資料來創建 CSV。



程式碼解說:

CVS_INFO 接口

定義了有關 CVS(便利商店)相關資訊的結構,包括商店過期日期、描述和支付訊息網址等。

interface CVS_INFO {
StoreExpireDate: string; // 商店過期日期
Desc_1: string; // 描述1
Desc_2: string; // 描述2
Desc_3: string; // 描述3
Desc_4: string; // 描述4
PaymentInfoURL: string; // 支付訊息的網址
}

CVS_PARAMS 接口

定義了與 CVS 交易相關的參數,包括商戶交易號、交易日期、總金額、交易描述、商品名稱和回傳網址等。

interface CVS_PARAMS {
MerchantTradeNo: string; // 商戶交易號
MerchantTradeDate: string; // 商戶交易日期
TotalAmount: string; // 總金額
TradeDesc: string; // 交易描述
ItemName: string; // 商品名稱
ReturnURL: string; // 回傳網址
}

CreateBillParams 接口

interface CreateBillParams {
cvsInfo?: CVS_INFO; // CVS 信息
cvsPrams: CVS_PARAMS; // CVS 參數
inv_param?: {}; // 可選的發票參數
client_redirect_url?: string; // 可選的客戶重定向網址
}
  • 定義了用於創建帳單所需的參數結構,包含 CVS_INFOCVS_PARAMS 兩個主要部分,以及可選的發票參數和客戶重定向網址。
  • cvsInfo 屬性後加上問號,表示這個屬性是可選的(optional)。

IECPayAdapter 接口

定義了一個用於適配 ECPay 的接口,包括一個名為 createCVS 的方法。這個方法接受 CreateBullParams 類型的參數,並返回一個字串。

export interface IECPayAdapter {
createCVS(createBillParams: CreateBillParams): string; // 用於創建 CVS 的方法
}


●讓ECPayAdapter使用 ECPay 提供的 API 來執行支付相關的操作。

export class ECPayAdapter implements IECPayAdapter {
private ecpayInstance;
constructor(options: IECPayAdapterOptions = defaultOptions) {
this.ecpayInstance = new ECPAY(options);
  }
}

程式碼解說:

1.導入 ECPay 模組

import ECPAY from './ECPAY_Payment_node_js';

ECPAY_Payment_node_js 文件中的內容作為模組導入,並命名為 ECPAY。這個模組應該包含了 ECPay 的支付功能實現,例如創建 CVS、查詢支付狀態等。

※ 參考資料來源ECPAY_Payment_node_js/example/aio_check_out_cvs.js

2.export class ECPayAdapter implements IECPayAdapter

  • ECPayAdapter 類別實現了 IECPayAdapter 接口,這表示它必須包含 IECPayAdapter 接口中定義的createCVS 方法。

3.private ecpayInstance;

這行程式碼宣告了一個私有變數 ecpayInstance,用來儲存 ECPay 的實例。private 關鍵字表示這個變數只能在 ECPayAdapter 類別內部使用,外部無法直接存取或修改。

4.constructor(options: IECPayAdapterOptions = defaultOptions)

  • 構造函數:這是 ECPayAdapter 類別的構造函數,用於在創建類別實例時進行初始化。
  • options 參數:這個參數的類型是 IECPayAdapterOptions,用來接收初始化配置選項。
  • 默認值 defaultOptions:options 參數設定為 defaultOptions 作為默認值,表示如果沒有傳遞具體的 options,則使用 defaultOptions。
  • 5.this.ecpayInstance = new ECPAY(options);
    • 這行程式碼在構造函數內執行,創建一個新的 ECPAY 實例並賦值給 this.ecpayInstance。這樣,ECPayAdapter 類別就可以使用 this.ecpayInstance 來調用 ECPay 的支付功能。
    • options 參數通常是一個配置物件,用來設置 ECPay 的一些初始化選項,例如商戶 ID、金鑰、API URL 等。

●定義optionsc後再依據 IECPayAdapterOptions 創建的一個具體變數或物件。

※ 參考資料來源ECPAY_Payment_node_js/conf/config-example.js


interface IECPayAdapterOptions {
OperationMode: "Test" | "Production", //Test or Production
MercProfile: {
MerchantID: string;
HashKey: string;
HashIV: string;
},
"IgnorePayment": [];
"IsProjectContractor": boolean;
}

程式碼解說:

  • OperationMode
    • 類型是 "Test" | "Production",表示運行模式可以是 "Test"(測試模式)或 "Production"(生產模式)。這個屬性用來設定 ECPay 是在測試環境還是生產環境中運行。
  • MercProfile
    • 這是一個物件,包含三個字串屬性,用於設定商戶的基本資料。
      • MerchantID:商戶 ID,用於標識商戶身份。HashKey:哈希金鑰,用於加密資料。HashIV:哈希初始化向量,用於加密資料。
  • IgnorePayment
    • 這是一個空的陣列,表示要忽略的支付方式。這個屬性可以用來設定不需要支持的支付方式。
  • IsProjectContractor
    • 類型是 boolean,表示是否是項目承包商。這個屬性用來標識商戶是否以項目承包商的身份運行。

● 設定defaultOptions

//定義一組預設的配置選項
const defaultOptions: IECPayAdapterOptions
= {}

程式碼解說:

  • 設置預設值:當用戶在初始化某個功能或類別時沒有提供具體的選項,defaultOptions 中定義的預設值將會被使用,確保系統有合理的初始設置。
  • 類型IECPayAdapterOptions意味著它必須符合這個接口中定義的結構和屬性。

● 設定defaultOptions內容:定義一組預設的配置選項

const defaultOptions: IECPayAdapterOptions = {
OperationMode: "Test", //Test or Production
MercProfile: {
MerchantID: "2000132",
HashKey: "5294y06JbISpM5x9",
HashIV: "v77hoKGq4kWxNNIS"
},
"IgnorePayment": [
// "Credit",
// "WebATM",
// "ATM",
// "CVS",
// "BARCODE",
// "AndroidPay"
],
"IsProjectContractor": false
}

程式碼解說:

※ 複製資料來源ECPAY_Payment_node_js/conf/config-example.js



● 查看HashKeyHashIV:

進入綠界科技的開發者測試後台:

登入後進入系統開發管理:

確認程式碼是否一樣:


● 設定create cvs

createCVS = (createParams: CreateBillParams) => {

const { cvsInfo, cvsPrams, inv_param, client_redirect_url } = createParams;

}

程式碼解說:

createCVS = (createParams: CreateBillParams) => { ... }

  • 這是一個箭頭函數,用於處理 createCVS 方法的邏輯。
  • createParams 是函數的參數,類型為 CreateBillParams。

const { cvsInfo, cvsPrams, inv_param, client_redirect_url } = createParams;

  • 這行程式碼使用了解構賦值語法,從 createParams 參數中提取出 cvsInfocvsPramsinv_paramclient_redirect_url 屬性。
  • 這樣做的目的是為了方便在函數內部直接使用這些變數,而不必每次都通過 createParams 物件來訪問。

● 定義了一個cvsInfo 的物件:

 cvsInfo = {
StoreExpireDate: '',
Desc_1: '',
Desc_2: '',
Desc_3: '',
Desc_4: '',
PaymentInfoURL: ''
},

程式碼解說:

※ 複製資料來源ECPAY_Payment_node_js/conf/config-example.js

  • StoreExpireDate
    • 描述:這個屬性通常用於指定便利商店的訂單過期日期。
    • 初始值:空字串。
  • Desc_1, Desc_2, Desc_3, Desc_4
    • 描述:這些屬性用於存儲額外的描述資訊,通常用於提供關於訂單或支付的詳細描述。
    • 初始值:空字串。
  • PaymentInfoURL
    • 描述:這個屬性用於指定一個 URL,該 URL 包含支付資訊,可能會將用戶重定向到相關的支付信息頁面。
    • 初始值:空字串

● 從 createParams 參數中提取 cvsPrams 屬性,並給 inv_param 和 client_redirect_url 設置默認值:

const {

cvsPrams,

inv_param = {},

client_redirect_url = ''

} = createParams;

程式碼解說:

  • cvsPrams
    • 直接從 createParams 中解構出來。
    • 這個變數用來存儲與 CVS(便利商店)相關的參數信息。
  • inv_param = {}
    • 使用解構賦值的語法,並設置默認值為空物件 {}。
    • 這個變數用來存儲發票相關的參數。如果 createParams 中沒有 inv_param 屬性,則使用默認的空物件。
  • client_redirect_url = ''
    • 使用解構賦值的語法,並設置默認值為空字串 ''。
    • 這個變數用來存儲客戶重定向的 URL。如果 createParams 中沒有 client_redirect_url 屬性,則使用默認的空字串。

● 生成的 HTML 表單輸出到控制台:

※ 複製資料來源ECPAY_Payment_node_js/conf/config-example.js

※ 將程式碼修改成以下

 //調用函數並使用返回的結果:
const html = this.ecpayInstance.payment_client.aio_check_out_cvs
(cvsInfo, cvsPrams, inv_param, client_redirect_url)
return html;

程式碼解說:

  • const html = this.ecpayInstance.payment_client.aio_check_out_cvs(cvsInfo, cvsPrams, inv_param, client_redirect_url);
    • 這行程式碼使用 ECPay 實例的 payment_client 來呼叫 aio_check_out_cvs 方法,生成 CVS 支付的 HTML 表單,這個表單可以嵌入網頁,讓用戶進行支付。
    • 參數:
      • cvsInfo:包含 CVS 支付的相關資訊,例如訂單過期日期、描述等。cvsPrams:包含 CVS 支付的具體參數,例如商品資訊、金額等。inv_param:包含發票相關的參數(如果有)。client_redirect_url:支付完成後用戶將被重定向到的 URL。
  • return html
    • 將 html 作為函數的返回值,使得調用者可以接收並使用這個結果。

● 建立dispatcher資料夾管理多種支付:dispatcher → index.js

1.定義了一個異步函數 paymentDispatcher,用來處理支付相關的邏輯

import { PaymentProvider, PaymentWay } from "@/model/order"

//建立發派function
export const paymentDispatcher = async ({
paymentProvider,
paymentWay,
payload
}: {
paymentProvider: PaymentProvider;
paymentWay: PaymentWay;
payload: PaymentPayload
}) => {

}

程式碼解說:

1.函數定義

export const paymentDispatcher =
async ({ paymentProvider, paymentWay, payload }
: { paymentProvider: PaymentProvider; paymentWay: PaymentWay; payload: any })
=> { };
  • export:這個關鍵字使得 paymentDispatcher 函數可以在其他模組中被導入和使用。
  • const:使用 const 關鍵字宣告一個常數,表示 paymentDispatcher 的值不能被重新賦值。
  • async:這個關鍵字表明 paymentDispatcher 是一個非同步函數,該函數會返回一個 Promise,並且可以使用 await 關鍵字來處理非同步操作。

2.參數解構賦值

({ paymentProvider, paymentWay, payload })
  • 從傳入的參數對象中解構出 paymentProviderpaymentWaypayload 屬性。
  • paymentProvider:支付提供商的類型,可以是如 ECPay、PayPal 等不同的金流服務。
  • paymentWay:支付方式,例如信用卡、ATM、便利商店等。
  • payload:包含支付過程中所需的其他參數和資訊。

3.參數類型定義

: { paymentProvider: PaymentProvider; paymentWay: PaymentWay; payload: PaymentPayload }

為解構賦值參數提供類型定義,確保傳入的參數符合預期的結構和類型。

2.根據指定的支付提供商 (PaymentProvider) 和支付方式 (PaymentWay),生成一個 CVS 支付的 HTML 表單

const ecpay = new ECPayAdapter();

if (paymentProvider === PaymentProvider.ECPAY) {
if (paymentWay === PaymentWay.CVS) {
const html = ecpay.createCVS({
cvsPrams: {
MerchantTradeNo: payload.billId,
MerchantTradeDate: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"),
TotalAmount: String(payload.totalPrice),
TradeDesc: payload.desc,
ItemName: payload.details.map(content =>
`${content.name} x ${content.price}`).join("#"),
ReturnURL: payload.returnUrl,

},

});
return html;
} else throw new Error('No suitable payment way.');

程式碼解說:

1.建立 ECPayAdapter 實例

const ecpay = new ECPayAdapter();

這行程式碼創建了一個新的 ECPayAdapter 實例,名為 ecpay

2.判斷支付提供商

if (paymentProvider === PaymentProvider.ECPAY) {}

判斷 paymentProvider 是否為 PaymentProvider.ECPAY,即檢查支付提供商是否為 ECPay。

3.判斷支付方式

if (paymentWay === PaymentWay.CVS) {}

判斷 paymentWay 是否為 PaymentWay.CVS,即檢查支付方式是否為 CVS。

4.生成 CVS 支付表單

const html = ecpay.createCVS({
cvsPrams: {
MerchantTradeNo: payload.billId, // 商戶交易編號
MerchantTradeDate: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"), // 交易日期
TotalAmount: String(payload.totalPrice), // 總金額(這裡似乎應該是金額,而不是交易編號,可能需要確認)
TradeDesc: payload.desc, // 交易描述
ItemName: payload.details.map(content =>
`${content.name} x ${content.price}`).join("#"), // 商品名稱及價格
ReturnURL: payload.returnUrl // 返回 URL
}
});
return html;
  • MerchantTradeNo:從 payload 中獲取的商戶交易編號。
  • MerchantTradeDate:使用 dayjs 庫格式化當前日期和時間為 "YYYY-MM-DD HH:mm:ss" 格式。
  • 安裝 dayjs
npm install dayjs
  • 匯入 dayjs
import dayjs from 'dayjs';
  • TotalAmount:總金額,這裡使用 payload.totalPrice,檢查這是否為正確的金額值。
  • TradeDesc:交易描述,從 payload.desc 獲取。
  • ItemName:商品名稱及價格,使用 map 函數將 payload.details 中每個商品的名稱和價格組合成 name x price 的格式,並用 # 分隔。
  • ReturnURL:支付完成後返回的 URL,從 payload.returnUrl 獲取。
  • 當支付提供商為 ECPay 且支付方式為 CVS 時,使用 ECPay 的 createCVS 方法生成 CVS 支付的 HTML 表單。
  • 使用 payload 中的數據來填充 CVS 支付參數(cvsPrams)。

5.處理不合適的支付方式

} else throw new Error('No suitable payment way.');
  • 如果支付方式不合適,則拋出一個錯誤,提示 "No suitable payment way."。


3.定義PaymentPayload的接口用來定義支付資料的結構,確保物件的屬性和類型符合預期。

export interface PaymentPayload {
billId: string;
totalPrice: number;
desc: string;
details: OrderDetail[];
returnURL: string;
}

程式碼解說:

  • export interface PaymentPayload
    • export:這個關鍵字表示這個接口可以被其他模組導入和使用。
    • interface:定義了一個接口,名為 PaymentPayload。
  • billId: string
    • 定義了一個名為 billId 的屬性,類型是字串。
    • 描述:這個屬性通常用於表示交易或訂單的唯一標識符。
  • totalPrice: number
    • 定義了一個名為 totalPrice 的屬性,類型是數字。
    • 描述:這個屬性用來表示交易的總金額。
  • desc: string
    • 定義了一個名為 desc 的屬性,類型是字串。
    • 描述:這個屬性用來表示交易的描述或說明。
  • details: OrderDetail[]
    • 定義了一個名為 details 的屬性,類型是 OrderDetail 類型的陣列。
    • 描述:這個屬性用來表示交易中的每個商品的詳細信息。OrderDetail 是一個自定義的類型,用來描述每個商品的詳細內容。
  • returnUrl: string
    • 定義了一個名為 returnUrl 的屬性,類型是字串。
    • 描述:這個屬性用來表示支付完成後,用戶將被重定向到的 URL。

4.定義OrderDetail的接口接口用來描述單個商品的詳細信息,包括價格、名稱和數量。

interface OrderDetail {
price: string;
name: string;
amount: number;
}

程式碼解說:

  • interface OrderDetail
    • interface:定義了一個接口,名為 OrderDetail。
    • 接口是一種用來定義物件結構的方式,確保物件符合預期的屬性和類型。
  • price: string
    • 定義了一個名為 price 的屬性,類型是字串。
    • 描述:這個屬性用來表示商品的價格。將價格定義為字串類型允許它包含貨幣符號或小數點。
  • name: string
    • 定義了一個名為 name 的屬性,類型是字串。
    • 描述:這個屬性用來表示商品的名稱。
  • amount: number
    • 定義了一個名為 amount 的屬性,類型是數字。
    • 描述:這個屬性用來表示商品的數量。

※ 設計ECPAY control:

●controller →orderController.ts:(目的在創建訂單和支付請求)

const result = await paymentDispatcher({
paymentProvider,
paymentWay,
payload: {
billId: uid,
totalPrice,
desc: `create order bill from ${uid} with ${contents
.map((content) => content.productId)
.join(",")
}`,
returnURL: `${process.env.END_POINT}/orders/update`,
details: contentInfos || [],
}
})

程式碼解說:

1.等待非同步操作完成await 關鍵字用來等待 paymentDispatcher 函數返回一個 Promise,並在該 Promise 被解決(resolved)後返回結果。

2.處理非同步函數的返回值paymentDispatcher 函數完成並給出結果時,這個結果會被存到 result 變數。就可以在後續的程式碼中使用 result 了。

3.金流 API 的呼叫使用 paymentDispatcher 函數來處理金流 API 的串接。

4.paymentProviderpaymentWay這兩個變數指定了支付提供商(如 ECPAY)和支付方式(如 CVS)。

5.payloadpayload 是傳遞給 paymentDispatcher 函數的參數物件,包含了支付的相關資料。

6.billIduid 是一個唯一的識別碼,用於標識這筆支付交易。

7.totalPrice:表示支付的總金額。

8.desc:描述欄位裡面生成的字串說明了這筆訂單,包含了 uidcontents 中每個商品的 productId

9.returnURL:使用環境變數 process.env.END_POINT作為指定支付完成後返回的URL。

※ 環境變數.env

END_POINT='http://localhost:30000'


10.details

  • 檢查 contentInfos 是否為 nullundefined:確保 details 屬性有一個預設值,避免因為 contentInfos 沒有值而引發錯誤。
  • 提供預設值:當 contentInfos 沒有值時,提供一個預設的空陣列 [],確保程式碼正常運行。

●controller →orderController.ts:(目的在保留與支付相關的商品名稱和價格)

 const contentInfos = contents.map(content => ({
name: ((products?.find(p => p.id === content.productId))?.name|| "",
price: content.price
}))

程式碼解說:

1.從 contents 陣列中提取訊息,並生成一個新的陣列 contentInfos。新的陣列包含每個商品的名稱和價格。

2.使用 map 函數迭代 contents 陣列

map 函數會為 contents 中的每個元素呼叫一次回調函數,並返回一個新的陣列。每個元素會被轉換為新陣列中的一個元素。

3.尋找商品名稱

  • 使用 find 函數在 products 陣列中查找與 content.productId 相匹配的商品。
  • 如果找到匹配的商品,find 函數會返回該商品物件;否則,返回 undefined

4.條件運算子 ?:

第一個 ?.:確保 products 為 null 或 undefined 時,不會引發錯誤。

第二個 ?.:確保 find 方法未找到匹配元素時,不會引發錯誤。

5.提供默認值

  • 當前面的鏈條返回 undefined 時,使用 || "" 提供一個默認值空字串。
  • 這樣可以確保 name 屬性永遠有一個值,即使查找失敗或 productsnullundefined值而導致的錯誤。

6.生成 contentInfos 陣列的元素

為新陣列 contentInfos 中的每個元素建立一個物件,包含商品名稱和價格。

●controller →orderController.ts:(目的在擷取產品 ID)

const products = await this.productModel.findByIds
(contents.map(product => product.productId))

程式碼解說:

1.擷取產品 ID

contents.map(product => product.productId)

使用 map 函數從 contents 陣列中提取每個商品的 productId,並生成一個新的包含所有 productId 的陣列。

2.查找產品資料

const products = await this.productModel.findByIds
(contents.map(product => product.productId));
  • 使用 findByIds 方法,將提取到的 productId 陣列作為參數傳入,從資料庫中查找所有與這些 productId 匹配的產品資料。
  • await 關鍵字表示這是一個非同步操作,會等待查詢操作完成後再繼續執行下一步。

●model →product.ts:(目的在從資料庫中查找與指定 ID 列表匹配的產品)

 findByIds(ids: string[], trx?: Knex.Transaction): Promise<Product[] | null>


程式碼解說:

1.定義函數

findByIds(ids: number[], trx?: Knex.Transaction): Promise<Product[] | null>

這個函數定義接受一個包含多個產品 ID 的字串陣列(ids)和一個可選的資料庫交易(trx)。

並返回一個 Promise,該 Promise 解析為 Product 類型的陣列或 null

2.參數

  • ids: number[]:要查找的產品 ID 列表,這個陣列包含多個產品的 ID。
  • trx?: Knex.Transaction:可選的 Knex 交易物件,用於確保所有資料庫操作在一個交易中執行。

●model →product.ts:(目的在根據 ID 列表從資料庫中查找產品。)

public findByIds: IProductModel['findByIds'] = async (ids, trx) => {
let queryBuilder = this.knexSql(this.tableName).whereIn('id', ids)

if (trx) queryBuilder = queryBuilder.transacting(trx);

const result = await queryBuilder;

if (isEmpty(result)) return null

return result.map(this.DBData2DataObject) as Product[];
}

程式碼解說:

1.函數定義

public findByIds: IProductModel['findByIds'] = async (ids, trx) => {}
  • findByIdsIProductModel 介面中的方法。
  • 它是個非同步函數(async),接受兩個參數:ids 是一個字串陣列,包含需要查找的產品 ID;trx 是一個可選的 Knex 交易物件。

2.建立查詢構建器

let queryBuilder = this.knexSql(this.tableName).whereIn('id', ids);

使用 Knex 建立一個查詢構建器(queryBuilder),從資料表中查找 ID 在 ids 列表中的產品。

3.事務處理

if (trx) queryBuilder = queryBuilder.transacting(trx);

如果傳入了 trx 參數,將查詢構建器與該交易物件關聯,確保所有操作在同一個事務中執行。

4.執行查詢

const result = await queryBuilder;

使用 await 等待查詢執行並獲取結果。

5.檢查結果是否為空

if (isEmpty(result)) return null;

使用 lodash 的 isEmpty 函數檢查查詢結果是否為空。如果結果為空,返回 null。

6.轉換結果

return result.map(this.DBData2DataObject) as Product[];

如果結果不為空,使用 map 方法將每個資料庫返回的記錄轉換為 Product 物件,並返回轉換後的陣列。

●model →base.ts:

publicprotected 的變更:目的在限制DBData2DataObject存取範圍。

●controller →orderController.ts:(目的在透過 Express 框架將 JSON 格式的回應傳送給客戶端)

 //金流API的串接
const result = await paymentDispatcher({
paymentProvider,
paymentWay,
payload: {
billId: uid,
totalPrice,
desc: `create order bill from ${uid} with ${contents
.map((content) => content.productId)
.join(",")
}`,
returnURL: `${process.env.END_POINT}/orders/update`,
details: contentInfos || [],
},
});
res.json({ status: 'success', data: result })
});
} catch (err) {
res.status(500).json({ errors: err })
throw err;
}

程式碼解說:

 res.json({ status: 'success', data: result })
  • res.json() 方法用來傳送 JSON 格式的資料作為回應。這是一種方便的方法來設定 Content-Typeapplication/json 並傳送 JSON 資料。
  • status: 顯示回應狀態。在這裡,它設為 'success',表示操作成功。
  • data: 包含操作結果。這裡,result 是之前由 paymentDispatcher 函數返回的結果。

※ 產生ECPAY 前端畫面:

●javascripts →index.js:(目的在將從 result 中獲取的 HTML 資料顯示在頁面上)

  const { data: html } = result;
this.ecpayHtml = html;

this.$nextTick(() => {
document.getElementById('_form_aiochk').submit();
})
console.log("--->", this.ecpayHtml);
},
},

程式碼解說:

1.解構賦值

const { data: html } = result; 

result 物件中的 data 屬性解構出來,並賦值給變數 html

2.設定成員變數

this.ecpayHtml = html;

html 資料賦值給 this.ecpayHtml,這樣可以在 Vue 中使用該資料。

3.使用 nextTick

this.$nextTick(() => {
document.getElementById('_form_aiochk').submit();
});
  • this.$nextTick 是 Vue 中的一個方法,確保 DOM 更新後自動提交表單。
  • 在回調函數中,通過 document.getElementById('_form_aiochk').submit(); 處理付款後自動提交具有 ID _form_aiochk 的表單。

4.除錯輸出

console.log("--->", this.ecpayHtml);

輸出 this.ecpayHtml 到瀏覽器控制台,以便檢查 html 資料的內容。

●javascripts →index.js:(目的在初始化組件的資料狀態)

return {
ecpayHtml: '',
};

程式碼解說:

  • 返回一個物件,其中包含 ecpayHtml 屬性,初始值設為空字串 ''
  • 在 Vue 組件中,這通常是在 data 方法中使用,用來初始化組件的資料屬性。
  • 初始化 ecpayHtml 為空字串是為了在組件創建時有個預設值,這樣後面可以正確地更新和使用這個屬性。

●views →index.ejs:(目的在插入動態的 HTML 內容)

<div id="ecpay" v-html="ecpayHtml"></div>

程式碼解說:

1.v-html 指令:

v-html 是 Vue.js 提供的一個指令,用來在元素內部動態插入 HTML 內容。

2.ecpayHtml 變數:

ecpayHtml 是一個包含 HTML 字串的變數。Vue.js 會自動更新 div 元素內部的內容,使其顯示最新的 HTML。

3.id="ecpay" 可以在 JavaScript 中使用 document.getElementById('ecpay') 精確定位這個 div 元素,便於進行後續操作。

●測試結果

1.按下按鈕

2.畫面結果

3.取得繳費代碼

※ 確認支付流程:

●廠商後台管理

1.登入綠界科技的開發者測試後台

2.確認MySQL的訂單

3.進入一般訂單查詢全方位金流訂單,付款方式選擇超商代碼,然後按查詢

4.出現訂單結果

●驗證支付流程是否正確:按下模擬付款





分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.