載點:https://github.com/ECPay/ECPayAIO_Node.js
方法:
1.把code dowonload下來。
2.使用npm install。
export interface IECPayAdapter {
createCVS()
}
程式碼解說:
export
關鍵字讓 IECPayAdapter
接口可以在其他模組中被 import
,進而確保不同的類別可以依據這個接口的定義來實現 createCVS
方法。IECPayAdapter
是用來適配 ECPay 的支付功能。aio_check_out_cvs.js
文件:createCVS()
函數:createCVS()
函數的參數:CVS_INFO
接口: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 接口
: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_INFO
和 CVS_PARAMS
兩個主要部分,以及可選的發票參數和客戶重定向網址。cvsInfo
屬性後加上問號,表示這個屬性是可選的(optional)。IECPayAdapter 接口
:createCVS
的方法。這個方法接受 CreateBullParams
類型的參數,並返回一個字串。export interface IECPayAdapter {
createCVS(createBillParams: CreateBillParams): string; // 用於創建 CVS 的方法
}
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)
:
5.this.ecpayInstance = new ECPAY(options);
:this.ecpayInstance
。這樣,ECPayAdapter
類別就可以使用 this.ecpayInstance
來調用 ECPay 的支付功能。options
參數通常是一個配置物件,用來設置 ECPay 的一些初始化選項,例如商戶 ID、金鑰、API URL 等。※ 參考資料來源: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
:MercProfile
:IgnorePayment
:IsProjectContractor
: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
和
HashIV:create cvs
:createCVS = (createParams: CreateBillParams) => {
const { cvsInfo, cvsPrams, inv_param, client_redirect_url } = createParams;
}
createCVS = (createParams: CreateBillParams) => { ... }
:
const { cvsInfo, cvsPrams, inv_param, client_redirect_url } = createParams;
:
createParams
參數中提取出 cvsInfo
、cvsPrams
、inv_param
和 client_redirect_url
屬性。createParams
物件來訪問。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
:const {
cvsPrams,
inv_param = {},
client_redirect_url = ''
} = createParams;
cvsPrams
:inv_param = {}
:client_redirect_url = ''
:※ 複製資料來源: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);
:return html
: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 })
paymentProvider
、paymentWay
和 payload
屬性。paymentProvider
:支付提供商的類型,可以是如 ECPay、PayPal 等不同的金流服務。paymentWay
:支付方式,例如信用卡、ATM、便利商店等。payload
:包含支付過程中所需的其他參數和資訊。3.參數類型定義:
: { paymentProvider: PaymentProvider; paymentWay: PaymentWay; payload: PaymentPayload }
為解構賦值參數提供類型定義,確保傳入的參數符合預期的結構和類型。
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"
格式。npm install dayjs
import dayjs from 'dayjs';
TotalAmount
:總金額,這裡使用 payload.totalPrice
,檢查這是否為正確的金額值。TradeDesc
:交易描述,從 payload.desc
獲取。ItemName
:商品名稱及價格,使用 map
函數將 payload.details
中每個商品的名稱和價格組合成 name x price
的格式,並用 #
分隔。ReturnURL
:支付完成後返回的 URL,從 payload.returnUrl
獲取。createCVS
方法生成 CVS 支付的 HTML 表單。5.處理不合適的支付方式:
} else throw new Error('No suitable payment way.');
PaymentPayload
的接口:用來定義支付資料的結構,確保物件的屬性和類型符合預期。export interface PaymentPayload {
billId: string;
totalPrice: number;
desc: string;
details: OrderDetail[];
returnURL: string;
}
export interface PaymentPayload
:billId: string
:totalPrice: number
:desc: string
:details: OrderDetail[]
:returnUrl: string
:OrderDetail
的接口:接口用來描述單個商品的詳細信息,包括價格、名稱和數量。interface OrderDetail {
price: string;
name: string;
amount: number;
}
interface OrderDetail
:price: string
:name: string
:amount: number
: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.paymentProvider
和 paymentWay
:這兩個變數指定了支付提供商(如 ECPAY)和支付方式(如 CVS)。
5.payload
:payload
是傳遞給 paymentDispatcher
函數的參數物件,包含了支付的相關資料。
6.billId
:uid
是一個唯一的識別碼,用於標識這筆支付交易。
7.totalPrice
:表示支付的總金額。
8.desc
:描述欄位裡面生成的字串說明了這筆訂單,包含了 uid
和 contents
中每個商品的 productId
。
9.returnURL
:使用環境變數 process.env.END_POINT
作為指定支付完成後返回的URL。
※ 環境變數.env
END_POINT='http://localhost:30000'
10.details
:
contentInfos
是否為 null
或 undefined
:確保 details
屬性有一個預設值,避免因為 contentInfos
沒有值而引發錯誤。contentInfos
沒有值時,提供一個預設的空陣列 []
,確保程式碼正常運行。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
屬性永遠有一個值,即使查找失敗或 products
為 null
或 undefined
值而導致的錯誤。6.生成 contentInfos
陣列的元素:
為新陣列 contentInfos
中的每個元素建立一個物件,包含商品名稱和價格。
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
關鍵字表示這是一個非同步操作,會等待查詢操作完成後再繼續執行下一步。findByIds(ids: string[], trx?: Knex.Transaction): Promise<Product[] | null>
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 交易物件,用於確保所有資料庫操作在一個交易中執行。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[];
}
public findByIds: IProductModel['findByIds'] = async (ids, trx) => {}
findByIds
是 IProductModel
介面中的方法。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
物件,並返回轉換後的陣列。
從 public
到 protected
的變更:目的在限制DBData2DataObject存取範圍。
//金流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-Type
為 application/json
並傳送 JSON 資料。 status
: 顯示回應狀態。在這裡,它設為 'success'
,表示操作成功。data
: 包含操作結果。這裡,result
是之前由 paymentDispatcher
函數返回的結果。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
資料的內容。
return {
ecpayHtml: '',
};
程式碼解說:
ecpayHtml
屬性,初始值設為空字串 ''
。data
方法中使用,用來初始化組件的資料屬性。ecpayHtml
為空字串是為了在組件創建時有個預設值,這樣後面可以正確地更新和使用這個屬性。<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
元素,便於進行後續操作。