☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
iot 中級

金流與支付整合

台灣常見金流平台

💡 比喻:收銀台和銀行之間的橋梁 你的網站就像一家商店,客人要付錢時不會直接把鈔票塞進電腦螢幕。 金流平台就是那座「橋梁」——幫你把客人的錢從他的銀行帳戶, 安全地搬到你的銀行帳戶,中間還幫你處理信用卡、超商代碼等等。

金流平台比較

台灣主流金流平台比較:
┌──────────────┬──────────┬──────────┬──────────────┐
│   平台名稱   │  手續費  │  撥款週期 │   適合對象   │
├──────────────┼──────────┼──────────┼──────────────┤
│ 綠界 ECPay   │ 2.75%    │ 月結     │ 中小企業     │
│ 藍新 NewebPay│ 2.6%     │ 月結     │ 中大型企業   │
│ LINE Pay     │ 3%       │ 月結     │ 行動支付     │
│ 街口支付     │ 2%       │ 月結     │ 小店家       │
│ Apple Pay    │ 需搭配   │ 依金流   │ iOS 用戶     │
│ PayPal       │ 3.4%     │ 即時     │ 跨國交易     │
└──────────────┴──────────┴──────────┴──────────────┘

金流串接架構

標準金流流程

金流串接完整流程:
                    ┌─────────┐
                    │  客 人   │
                    └────┬────┘
                         │ ① 按下結帳按鈕
                    ┌────▼────┐
                    │ 你的網站 │
                    └────┬────┘
                         │ ② 建立訂單 + 產生付款參數
                    ┌────▼────┐
                    │ 金流平台 │  (ECPay / 藍新)
                    └────┬────┘
                         │ ③ 顯示付款頁面(信用卡/ATM)
                    ┌────▼────┐
                    │ 銀 行   │
                    └────┬────┘
                         │ ④ 扣款成功
                    ┌────▼────┐
                    │ 金流平台 │
                    └────┬────┘
                         │ ⑤ 通知你的網站(回呼 URL)
                    ┌────▼────┐
                    │ 你的網站 │  更新訂單狀態
                    └─────────┘

ECPay SDK 整合

設定金流參數

// ECPay 設定模型 // 綠界金流設定
public class ECPaySettings // 金流設定類別
{
    public string MerchantID { get; set; } = ""; // 特店編號
    public string HashKey { get; set; } = ""; // 加密金鑰
    public string HashIV { get; set; } = ""; // 加密向量
    public string PaymentApiUrl { get; set; } = ""; // 付款 API 網址
    public string ReturnUrl { get; set; } = ""; // 付款結果回傳網址
    public string NotifyUrl { get; set; } = ""; // 付款通知回呼網址
}

// appsettings.json 設定範例 // 在設定檔中加入金流設定
// "ECPay": {
//     "MerchantID": "3002607",        // 測試特店編號
//     "HashKey": "pwFHCqoQZGmho4w6",  // 測試金鑰
//     "HashIV": "EkRm7iFT261dpevs",   // 測試向量
//     "PaymentApiUrl": "https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5" // 測試 API
// }

建立付款請求

// ECPay 金流服務 // 處理綠界金流串接
public class ECPayService // 綠界金流服務類別
{
    private readonly ECPaySettings _settings; // 金流設定
    private readonly ILogger<ECPayService> _logger; // 日誌記錄器

    // 建構函式 // 注入設定和日誌
    public ECPayService( // 建構金流服務
        IOptions<ECPaySettings> settings, // 注入設定
        ILogger<ECPayService> logger) // 注入日誌
    {
        _settings = settings.Value; // 取得設定值
        _logger = logger; // 儲存日誌記錄器
    }

    // 建立付款訂單 // 產生送往 ECPay 的表單參數
    public Dictionary<string, string> CreatePayment( // 建立付款方法
        string orderId, // 訂單編號
        int amount, // 金額
        string description) // 商品描述
    {
        // 組合付款參數 // ECPay 要求的欄位
        var parameters = new Dictionary<string, string> // 建立參數字典
        {
            ["MerchantID"] = _settings.MerchantID, // 特店編號
            ["MerchantTradeNo"] = orderId, // 特店交易編號
            ["MerchantTradeDate"] = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), // 交易時間
            ["PaymentType"] = "aio", // 付款類型(全方位)
            ["TotalAmount"] = amount.ToString(), // 交易金額
            ["TradeDesc"] = description, // 交易描述
            ["ItemName"] = description, // 商品名稱
            ["ReturnURL"] = _settings.NotifyUrl, // 付款結果通知網址
            ["OrderResultURL"] = _settings.ReturnUrl, // 付款完成導回網址
            ["ChoosePayment"] = "ALL", // 付款方式(全部)
            ["EncryptType"] = "1" // 加密類型(SHA256)
        };

        // 計算檢查碼 // 防止參數被竄改
        var checkMacValue = GenerateCheckMacValue(parameters); // 產生檢查碼
        parameters["CheckMacValue"] = checkMacValue; // 加入檢查碼

        _logger.LogInformation("建立 ECPay 付款:{OrderId}, ${Amount}", // 記錄付款
            orderId, amount); // 傳入參數

        return parameters; // 回傳付款參數
    }

    // 產生檢查碼 // SHA256 雜湊驗證
    private string GenerateCheckMacValue( // 產生 CheckMacValue 方法
        Dictionary<string, string> parameters) // 付款參數
    {
        // 依照 ECPay 規則排序 // 參數名稱字母排序
        var sorted = parameters.OrderBy(p => p.Key); // 排序參數

        // 組合字串 // HashKey + 參數 + HashIV
        var raw = $"HashKey={_settings.HashKey}&"; // 開頭加 HashKey
        raw += string.Join("&", sorted.Select(p => $"{p.Key}={p.Value}")); // 串接所有參數
        raw += $"&HashIV={_settings.HashIV}"; // 結尾加 HashIV

        // URL Encode 後轉小寫 // ECPay 規定
        raw = System.Net.WebUtility.UrlEncode(raw)?.ToLower() ?? ""; // 編碼並轉小寫

        // SHA256 雜湊 // 產生最終檢查碼
        using var sha256 = System.Security.Cryptography.SHA256.Create(); // 建立 SHA256
        var bytes = sha256.ComputeHash( // 計算雜湊
            System.Text.Encoding.UTF8.GetBytes(raw)); // UTF-8 編碼
        return BitConverter.ToString(bytes) // 轉為十六進位字串
            .Replace("-", "").ToUpper(); // 去除破折號並轉大寫
    }

    // 驗證回呼 // 確認付款通知來自 ECPay
    public bool VerifyCallback( // 驗證回呼方法
        Dictionary<string, string> formData) // 表單資料
    {
        if (!formData.ContainsKey("CheckMacValue")) // 檢查是否有檢查碼
            return false; // 沒有檢查碼就是偽造的

        var receivedMac = formData["CheckMacValue"]; // 取得收到的檢查碼
        var paramsWithoutMac = formData // 排除 CheckMacValue
            .Where(p => p.Key != "CheckMacValue") // 過濾掉檢查碼
            .ToDictionary(p => p.Key, p => p.Value); // 重建字典

        var expectedMac = GenerateCheckMacValue(paramsWithoutMac); // 重新計算檢查碼

        return receivedMac == expectedMac; // 比對是否一致
    }
}

信用卡刷卡機串接

串口通訊刷卡

// 刷卡機串口通訊服務 // 透過 RS232 與刷卡機通訊
public class CardReaderService : IDisposable // 刷卡機服務
{
    private readonly SerialPort _port; // 串口物件
    private readonly ILogger<CardReaderService> _logger; // 日誌記錄器

    // 建構函式 // 初始化刷卡機連線
    public CardReaderService( // 建構刷卡機服務
        ILogger<CardReaderService> logger) // 注入日誌
    {
        _logger = logger; // 儲存日誌記錄器
        _port = new SerialPort // 建立串口
        {
            PortName = "/dev/ttyUSB1", // 刷卡機串口
            BaudRate = 115200, // 鮑率 115200
            DataBits = 8, // 資料位元
            Parity = Parity.None, // 無同位元
            StopBits = StopBits.One // 停止位元
        };
    }

    // 發送刷卡請求 // 告訴刷卡機要刷多少錢
    public async Task<CardTransactionResult> ProcessPaymentAsync( // 刷卡方法
        decimal amount) // 刷卡金額
    {
        try // 嘗試刷卡
        {
            _port.Open(); // 開啟串口

            // 組合刷卡指令 // 依刷卡機協定格式化
            var command = FormatCommand(amount); // 格式化刷卡指令
            _port.Write(command, 0, command.Length); // 發送指令

            _logger.LogInformation("發送刷卡請求:${Amount}", amount); // 記錄金額

            // 等待刷卡機回應 // 最多等 60 秒
            var response = await WaitForResponseAsync( // 等待回應
                TimeSpan.FromSeconds(60)); // 超時時間

            return ParseResponse(response); // 解析回應結果
        }
        catch (Exception ex) // 捕捉例外
        {
            _logger.LogError(ex, "刷卡處理失敗"); // 記錄錯誤
            return new CardTransactionResult // 回傳失敗結果
            {
                Success = false, // 標記失敗
                ErrorMessage = ex.Message // 錯誤訊息
            };
        }
        finally // 最終處理
        {
            if (_port.IsOpen) _port.Close(); // 確保關閉串口
        }
    }

    private byte[] FormatCommand(decimal amount) => // 格式化指令方法
        System.Text.Encoding.ASCII.GetBytes( // 轉為位元組陣列
            $"SALE:{amount:F2}\r\n"); // 刷卡指令格式

    private Task<byte[]> WaitForResponseAsync( // 等待回應方法
        TimeSpan timeout) => // 超時時間
        Task.FromResult(Array.Empty<byte>()); // 回傳空陣列(待實作)

    private CardTransactionResult ParseResponse( // 解析回應方法
        byte[] response) => // 回應位元組
        new() { Success = true }; // 回傳結果(待實作)

    public void Dispose() => _port.Dispose(); // 釋放串口資源
}

// 刷卡交易結果 // 記錄刷卡結果
public class CardTransactionResult // 交易結果類別
{
    public bool Success { get; set; } // 是否成功
    public string? AuthCode { get; set; } // 授權碼
    public string? CardNumber { get; set; } // 卡號末四碼
    public string? ErrorMessage { get; set; } // 錯誤訊息
}

QR Code 行動支付

產生 QR Code

// QR Code 支付服務 // 產生行動支付用的 QR Code
public class QrPaymentService // QR 支付服務類別
{
    private readonly ILogger<QrPaymentService> _logger; // 日誌記錄器

    // 建構函式 // 注入日誌
    public QrPaymentService( // 建構 QR 支付服務
        ILogger<QrPaymentService> logger) // 注入 Logger
    {
        _logger = logger; // 儲存日誌記錄器
    }

    // 產生 LINE Pay QR Code 網址 // 客人掃碼付款
    public string GenerateLinePayUrl( // 產生 LINE Pay 網址方法
        string orderId, // 訂單編號
        int amount) // 金額
    {
        // LINE Pay API 付款請求 // 取得付款網址
        var paymentUrl = $"https://sandbox-api-pay.line.me/v3/payments/request"; // API 端點

        _logger.LogInformation( // 記錄產生 QR Code
            "產生 LINE Pay QR:訂單 {OrderId}, ${Amount}", // 格式化訊息
            orderId, amount); // 傳入參數

        return paymentUrl; // 回傳付款網址
    }
}

電子發票串接

電子發票服務

// 電子發票服務 // 串接財政部電子發票 API
public class InvoiceService // 電子發票服務類別
{
    private readonly HttpClient _httpClient; // HTTP 客戶端
    private readonly ILogger<InvoiceService> _logger; // 日誌記錄器

    // 發票資料模型 // 開立發票所需資訊
    public class InvoiceRequest // 發票請求類別
    {
        public string BuyerIdentifier { get; set; } = ""; // 買方統編(空白為個人)
        public string BuyerName { get; set; } = ""; // 買方名稱
        public List<InvoiceItem> Items { get; set; } = new(); // 商品明細
        public int TotalAmount { get; set; } // 總金額
        public string CarrierType { get; set; } = ""; // 載具類型
        public string CarrierNum { get; set; } = ""; // 載具號碼
    }

    // 發票商品明細 // 每一筆商品資訊
    public class InvoiceItem // 發票商品類別
    {
        public string Name { get; set; } = ""; // 商品名稱
        public int Quantity { get; set; } // 數量
        public decimal UnitPrice { get; set; } // 單價
        public decimal Amount { get; set; } // 小計
    }

    // 建構函式 // 注入 HttpClient
    public InvoiceService( // 建構發票服務
        HttpClient httpClient, // 注入 HTTP 客戶端
        ILogger<InvoiceService> logger) // 注入日誌
    {
        _httpClient = httpClient; // 儲存 HTTP 客戶端
        _logger = logger; // 儲存日誌記錄器
    }

    // 開立電子發票 // 呼叫財政部 API
    public async Task<string?> IssueInvoiceAsync( // 開立發票方法
        InvoiceRequest request) // 發票請求
    {
        _logger.LogInformation( // 記錄開立發票
            "開立電子發票:{Amount} 元", request.TotalAmount); // 傳入金額

        // 呼叫財政部 API // 取得發票號碼
        // 實際實作需依照財政部規格 // 這裡是框架示範
        var invoiceNumber = $"AB-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid():N}"; // 模擬發票號碼

        return invoiceNumber; // 回傳發票號碼
    }
}

退款流程處理

// 退款服務 // 處理各種退款情境
public class RefundService // 退款服務類別
{
    private readonly ECPayService _ecpay; // 綠界金流服務
    private readonly ILogger<RefundService> _logger; // 日誌記錄器

    // 建構函式 // 注入金流服務
    public RefundService( // 建構退款服務
        ECPayService ecpay, // 注入綠界服務
        ILogger<RefundService> logger) // 注入日誌
    {
        _ecpay = ecpay; // 儲存金流服務
        _logger = logger; // 儲存日誌記錄器
    }

    // 處理退款 // 依付款方式執行退款
    public async Task<RefundResult> ProcessRefundAsync( // 退款方法
        string orderId, // 訂單編號
        decimal amount, // 退款金額
        string reason) // 退款原因
    {
        _logger.LogInformation( // 記錄退款請求
            "處理退款:訂單 {OrderId}, ${Amount}, 原因:{Reason}", // 格式化訊息
            orderId, amount, reason); // 傳入參數

        // 驗證退款金額 // 不能超過原始金額
        if (amount <= 0) // 金額必須大於零
        {
            return new RefundResult // 回傳失敗
            {
                Success = false, // 標記失敗
                Message = "退款金額必須大於零" // 錯誤訊息
            };
        }

        // 呼叫金流平台退款 API // 實際執行退款
        await Task.CompletedTask; // 非同步退款(待實作)

        return new RefundResult // 回傳成功
        {
            Success = true, // 標記成功
            Message = "退款處理中", // 成功訊息
            RefundId = Guid.NewGuid().ToString() // 退款編號
        };
    }
}

// 退款結果 // 退款處理回傳
public class RefundResult // 退款結果類別
{
    public bool Success { get; set; } // 是否成功
    public string Message { get; set; } = ""; // 結果訊息
    public string? RefundId { get; set; } // 退款編號
}

金流安全

防重複付款

// 冪等性付款服務 // 防止重複扣款
public class IdempotentPaymentService // 冪等付款服務類別
{
    private readonly IDistributedCache _cache; // 分散式快取
    private readonly ILogger<IdempotentPaymentService> _logger; // 日誌記錄器

    // 建構函式 // 注入快取和日誌
    public IdempotentPaymentService( // 建構冪等付款服務
        IDistributedCache cache, // 注入分散式快取
        ILogger<IdempotentPaymentService> logger) // 注入日誌
    {
        _cache = cache; // 儲存快取
        _logger = logger; // 儲存日誌記錄器
    }

    // 處理付款(防重複) // 同一筆訂單只會扣一次
    public async Task<PaymentResult> ProcessPaymentAsync( // 防重複付款方法
        string idempotencyKey, // 冪等鍵(通常用訂單編號)
        Func<Task<PaymentResult>> paymentAction) // 實際付款動作
    {
        // 檢查是否已處理過 // 從快取查詢
        var cached = await _cache.GetStringAsync(idempotencyKey); // 查詢快取
        if (cached != null) // 如果已經處理過
        {
            _logger.LogWarning("偵測到重複付款:{Key}", idempotencyKey); // 記錄重複
            return System.Text.Json.JsonSerializer // 反序列化快取結果
                .Deserialize<PaymentResult>(cached)!; // 回傳之前的結果
        }

        // 執行付款 // 第一次處理
        var result = await paymentAction(); // 執行實際付款動作

        // 快取結果(24小時) // 防止重複
        await _cache.SetStringAsync(idempotencyKey, // 儲存到快取
            System.Text.Json.JsonSerializer.Serialize(result), // 序列化結果
            new DistributedCacheEntryOptions // 快取選項
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24) // 24 小時後過期
            });

        return result; // 回傳付款結果
    }
}

// 付款結果 // 付款處理回傳
public class PaymentResult // 付款結果類別
{
    public bool Success { get; set; } // 是否成功
    public string? TransactionId { get; set; } // 交易編號
    public string? Message { get; set; } // 結果訊息
}

對帳系統設計

// 對帳服務 // 每日自動對帳
public class ReconciliationService // 對帳服務類別
{
    private readonly ILogger<ReconciliationService> _logger; // 日誌記錄器

    // 建構函式 // 注入日誌
    public ReconciliationService( // 建構對帳服務
        ILogger<ReconciliationService> logger) // 注入 Logger
    {
        _logger = logger; // 儲存日誌記錄器
    }

    // 每日對帳 // 比對系統訂單和金流平台紀錄
    public async Task<ReconciliationReport> DailyReconcileAsync( // 每日對帳方法
        DateTime date) // 對帳日期
    {
        _logger.LogInformation("開始對帳:{Date:yyyy-MM-dd}", date); // 記錄開始

        var report = new ReconciliationReport // 建立對帳報告
        {
            Date = date, // 對帳日期
            TotalOrders = 0, // 系統訂單數
            TotalPayments = 0, // 金流平台筆數
            MatchedCount = 0, // 對帳成功筆數
            MismatchedCount = 0 // 對帳失敗筆數
        };

        // 實際對帳邏輯 // 比對系統和金流資料
        await Task.CompletedTask; // 非同步對帳(待實作)

        return report; // 回傳對帳報告
    }
}

// 對帳報告 // 對帳結果統計
public class ReconciliationReport // 對帳報告類別
{
    public DateTime Date { get; set; } // 對帳日期
    public int TotalOrders { get; set; } // 系統訂單總數
    public int TotalPayments { get; set; } // 金流筆數
    public int MatchedCount { get; set; } // 成功比對筆數
    public int MismatchedCount { get; set; } // 失敗比對筆數
}

🤔 我這樣寫為什麼會錯?

❌ 錯誤:金流 HashKey 寫在程式碼裡

// 錯誤寫法 // 把機密金鑰寫死在程式碼裡
public class BadPaymentService // 錯誤的金流服務
{
    private const string HashKey = "pwFHCqoQZGmho4w6"; // 金鑰寫死在程式碼 ← 超危險!
    private const string HashIV = "EkRm7iFT261dpevs"; // 向量也寫死 ← 會被 Git 追蹤到!
}

✅ 正確:使用環境變數或 Secret Manager

// 正確寫法 // 金鑰放在安全的地方
public class SecurePaymentService // 安全的金流服務
{
    private readonly string _hashKey; // 金鑰(不寫死)

    public SecurePaymentService( // 建構函式
        IConfiguration config) // 注入設定
    {
        // 從環境變數或 User Secrets 讀取 // 不會出現在原始碼
        _hashKey = config["ECPay:HashKey"] // 從設定讀取金鑰
            ?? throw new InvalidOperationException( // 找不到就拋例外
                "ECPay:HashKey 未設定"); // 錯誤訊息
    }
}

❌ 錯誤:沒有驗證回呼來源

// 錯誤寫法 // 任何人都能偽造付款成功通知
[HttpPost("payment/callback")] // 付款回呼端點
public IActionResult PaymentCallback( // 回呼方法
    [FromForm] Dictionary<string, string> data) // 表單資料
{
    // 直接信任回呼資料 ← 沒有驗證! // 任何人都能偽造
    var orderId = data["MerchantTradeNo"]; // 直接取訂單編號
    UpdateOrderStatus(orderId, "paid"); // 直接更新為已付款
    return Ok(); // 回傳成功
}

private void UpdateOrderStatus(string id, string s) { } // 更新訂單狀態

✅ 正確:驗證 CheckMacValue

// 正確寫法 // 驗證回呼是否來自金流平台
[HttpPost("payment/callback")] // 付款回呼端點
public IActionResult PaymentCallback( // 回呼方法
    [FromForm] Dictionary<string, string> data) // 表單資料
{
    // 先驗證 CheckMacValue // 確認是 ECPay 發的
    if (!_ecpayService.VerifyCallback(data)) // 驗證檢查碼
    {
        _logger.LogWarning("收到偽造的付款回呼!"); // 記錄可疑行為
        return BadRequest("驗證失敗"); // 拒絕偽造請求
    }

    // 驗證通過才更新 // 確保資料真實性
    var orderId = data["MerchantTradeNo"]; // 取得訂單編號
    UpdateOrderStatus(orderId, "paid"); // 安全地更新狀態
    return Ok("1|OK"); // ECPay 要求回傳此格式
}

private ECPayService _ecpayService = null!; // 金流服務
private ILogger _logger2 = null!; // 日誌記錄器
private void UpdateOrderStatus(string id, string s) { } // 更新訂單

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊