<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      支付中心的錢包類業務應該怎么設計

      錢包類業務在支付行業里有一些比較固定的模式(無論是支付寶余額寶、微信零錢,還是 Stripe Balance / Paytm Wallet),基本設計目標是:

      1. 余額和資金安全:必須有嚴格的賬實一致、冪等和防篡改能力。

      2. 高并發讀寫:充值/消費/退款頻繁,要求快速的扣減和回滾能力。

      3. 清晰的流水:任何一筆資金變動必須有對應的流水,支持對賬和審計。


      ?? 表設計(MySQL 示例)

      1. 錢包賬戶表(核心賬戶信息)

      CREATE TABLE wallet_account (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          user_id BIGINT NOT NULL UNIQUE COMMENT '用戶ID',
          balance DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '可用余額',
          frozen_balance DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '凍結余額',
          status TINYINT NOT NULL DEFAULT 1 COMMENT '賬戶狀態 1=正常 0=凍結',
          -- 棄用 version BIGINT NOT NULL DEFAULT 0 COMMENT '樂觀鎖版本號',
          hash_sign VARCHAR(128) COMMENT '數據校驗簽名(防篡改水印)',
          created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
          updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
          UNIQUE KEY uk_user (user_id)
      ) ENGINE=InnoDB;

      要點:

      • balancefrozen_balance 一般放在 同一個表,保證原子性更新。

      • version 做樂觀鎖,避免并發扣減錯誤。

      • hash_sign 是水印字段(例如 MD5(user_id + balance + frozen_balance + secret_salt)),在數據校驗任務中比對,防止 DBA/黑客直接篡改。


      2. 錢包流水表(賬務流水)

      CREATE TABLE wallet_ledger (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          wallet_id BIGINT NOT NULL,
          txn_id VARCHAR(64) NOT NULL COMMENT '關聯的交易單號/支付單號',
          biz_type VARCHAR(32) NOT NULL COMMENT '業務類型: RECHARGE, PAY, REFUND, FREEZE, UNFREEZE',
          change_amount DECIMAL(18,2) NOT NULL COMMENT '變動金額(正=增加,負=減少)',
          balance_after DECIMAL(18,2) NOT NULL COMMENT '變動后的可用余額',
          frozen_after DECIMAL(18,2) NOT NULL COMMENT '變動后的凍結余額',
          remark VARCHAR(255),
          created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
          INDEX idx_wallet (wallet_id),
          UNIQUE KEY uk_txn (txn_id, biz_type) -- 保證冪等
      ) ENGINE=InnoDB;

      要點:

      • 每筆操作必須落流水,賬隨流轉。

      • biz_type 用來區分充值、支付、退款、凍結、解凍。

      • uk_txn 保證冪等(相同交易單號不會重復入賬)。


      3. 錢包交易訂單表(業務訂單)

      CREATE TABLE wallet_transaction (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          txn_id VARCHAR(64) NOT NULL UNIQUE COMMENT '錢包交易單號',
          user_id BIGINT NOT NULL,
          type VARCHAR(32) NOT NULL COMMENT '類型: RECHARGE, CONSUME, REFUND',
          amount DECIMAL(18,2) NOT NULL COMMENT '訂單金額',
          status VARCHAR(16) NOT NULL COMMENT 'INIT, SUCCESS, FAILED, PROCESSING',
          channel VARCHAR(32) COMMENT '支付通道(僅充值時有)',
          related_order_id VARCHAR(64) COMMENT '業務訂單ID, 如電商訂單號',
          created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
          updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
      ) ENGINE=InnoDB;

      要點:

      • 錢包交易訂單表流水表 是分開的:

        • 訂單表:對外業務維度(如一筆充值、一筆消費)。

        • 流水表:資金原子動作(可能一筆訂單拆成多條流水,比如凍結→支付→解凍)。

      • 狀態機驅動(INIT → PROCESSING → SUCCESS/FAILED)。


      ?? 流程邏輯

      1. 充值(RECHARGE)

      1. 用戶提交充值請求,生成 wallet_transaction(type=RECHARGE, status=INIT)。

      2. 調用支付通道(如銀行卡/支付寶充值),異步回調成功后:

        • 開啟事務:

          • 更新 wallet_account.balance = balance + amount

          • 寫入 wallet_ledger

          • 更新 wallet_transaction.status=SUCCESS


      2. 消費(CONSUME)

      1. 用戶下單支付,生成 wallet_transaction(type=CONSUME, status=INIT)。

      2. 開啟事務:

        • 校驗余額夠不夠。

        • 更新 wallet_account.balance = balance - amount

        • 寫入 wallet_ledger(change_amount = -amount)。

        • 更新 wallet_transaction.status=SUCCESS


      3. 凍結 & 解凍(如押金、待支付訂單)

      • 凍結balance - X,同時 frozen_balance + X,寫流水。

      • 解凍:反向操作,寫流水。

      • 消費凍結金額frozen_balance - X,直接扣減,落賬。


      4. 退款(REFUND)

      1. 生成 wallet_transaction(type=REFUND, related_order_id=原消費單)。

      2. 開啟事務:

        • balance = balance + refund_amount

        • 寫入 wallet_ledger

        • 更新 wallet_transaction.status=SUCCESS


      ?? 數據庫邏輯

      條件更新示例(不需要 version)

      比如扣款:

      UPDATE wallet_account 
      SET balance = balance - 100 
      WHERE user_id = 123 
        AND balance - frozen_balance >= 100;
      • 保證了賬戶可用余額足夠再扣。

      • 如果余額不足,更新行數 = 0,表示失敗,業務層處理異常。

      • 不存在并發覆蓋的問題,因為每條語句都是 原子執行


      3. 凍結/解凍邏輯擴展

      凍結金額需要同時更新 balancefrozen_balance,例如:

      凍結資金(比如下單時鎖定 200 元):

      UPDATE wallet_account
      SET balance = balance - 200,
          frozen_balance = frozen_balance + 200
      WHERE user_id = 123
        AND balance - frozen_balance >= 200;

      解凍資金(比如訂單取消):

      UPDATE wallet_account
      SET balance = balance + 200,
          frozen_balance = frozen_balance - 200
      WHERE user_id = 123
        AND frozen_balance >= 200;

      消費凍結資金(比如訂單支付成功):

      UPDATE wallet_account
      SET frozen_balance = frozen_balance - 200
      WHERE user_id = 123
        AND frozen_balance >= 200;

       


      ?? java代碼實現(不使用version)

        1 import java.sql.*;
        2 import javax.sql.DataSource;
        3 
        4 public class WalletServiceConditional {
        5     private final DataSource ds;
        6     private final int MAX_RETRIES = 5;
        7 
        8     public WalletServiceConditional(DataSource ds) { this.ds = ds; }
        9 
       10     // 直接消費(不通過凍結)
       11     public boolean deductAvailable(long userId, long amount, String txnId) throws SQLException {
       12         // txnId 用于 ledger 的冪等約束
       13         for (int attempt=0; attempt<MAX_RETRIES; attempt++) {
       14             try (Connection conn = ds.getConnection()) {
       15                 conn.setAutoCommit(false);
       16                 try {
       17                     // 1) 原子更新余額:保證可用余額充足 (balance - frozen_balance >= amount)
       18                     String updSql = "UPDATE wallet_account " +
       19                                     "SET balance = balance - ? " +
       20                                     "WHERE user_id = ? AND (balance - frozen_balance) >= ?";
       21                     try (PreparedStatement upd = conn.prepareStatement(updSql)) {
       22                         upd.setLong(1, amount);
       23                         upd.setLong(2, userId);
       24                         upd.setLong(3, amount);
       25                         int affected = upd.executeUpdate();
       26                         if (affected == 0) {
       27                             conn.rollback();
       28                             return false; // 余額不足或其它原因
       29                         }
       30                     }
       31 
       32                     // 2) 寫流水(注意冪等)
       33                     String insertLedger = "INSERT INTO wallet_ledger(txn_id, wallet_user_id, change_amount, balance_after, frozen_after, biz_type, created_at) " +
       34                                           "VALUES (?, ?, ?, (SELECT balance FROM wallet_account WHERE user_id=?), (SELECT frozen_balance FROM wallet_account WHERE user_id=?), ?, NOW())";
       35                     try (PreparedStatement ps = conn.prepareStatement(insertLedger)) {
       36                         ps.setString(1, txnId);
       37                         ps.setLong(2, userId);
       38                         ps.setLong(3, -amount);
       39                         ps.setLong(4, userId);
       40                         ps.setLong(5, userId);
       41                         ps.setString(6, "CONSUME");
       42                         ps.executeUpdate();
       43                     }
       44 
       45                     conn.commit();
       46                     return true;
       47                 } catch (SQLException ex) {
       48                     conn.rollback();
       49                     // 死鎖重試:MySQL 錯誤碼1213 或 SQLState "40001"
       50                     if (isDeadlock(ex) && attempt < MAX_RETRIES-1) {
       51                         backoffSleep(attempt);
       52                         continue;
       53                     }
       54                     throw ex;
       55                 } finally {
       56                     conn.setAutoCommit(true);
       57                 }
       58             }
       59         }
       60         throw new SQLException("deductAvailable failed after retries");
       61     }
       62 
       63     // 凍結資金(下單時)
       64     public boolean freezeAmount(long userId, long amount, String txnId) throws SQLException {
       65         for (int attempt=0; attempt<MAX_RETRIES; attempt++) {
       66             try (Connection conn = ds.getConnection()) {
       67                 conn.setAutoCommit(false);
       68                 try {
       69                     String sql = "UPDATE wallet_account SET balance = balance - ?, frozen_balance = frozen_balance + ? " +
       70                                  "WHERE user_id = ? AND balance >= ?";
       71                     try (PreparedStatement ps = conn.prepareStatement(sql)) {
       72                         ps.setLong(1, amount);
       73                         ps.setLong(2, amount);
       74                         ps.setLong(3, userId);
       75                         ps.setLong(4, amount);
       76                         int affected = ps.executeUpdate();
       77                         if (affected == 0) { conn.rollback(); return false; }
       78                     }
       79 
       80                     // ledger: freeze record
       81                     String lsql = "INSERT INTO wallet_ledger(txn_id, wallet_user_id, change_amount, balance_after, frozen_after, biz_type, created_at) " +
       82                                   "VALUES (?, ?, ?, (SELECT balance FROM wallet_account WHERE user_id=?), (SELECT frozen_balance FROM wallet_account WHERE user_id=?), ?, NOW())";
       83                     try (PreparedStatement ps2 = conn.prepareStatement(lsql)) {
       84                         ps2.setString(1, txnId);
       85                         ps2.setLong(2, userId);
       86                         ps2.setLong(3, -amount); // 把可用余額減少
       87                         ps2.setLong(4, userId);
       88                         ps2.setLong(5, userId);
       89                         ps2.setString(6, "FREEZE");
       90                         ps2.executeUpdate();
       91                     }
       92 
       93                     conn.commit();
       94                     return true;
       95                 } catch (SQLException ex) {
       96                     conn.rollback();
       97                     if (isDeadlock(ex) && attempt < MAX_RETRIES-1) {
       98                         backoffSleep(attempt);
       99                         continue;
      100                     }
      101                     throw ex;
      102                 } finally {
      103                     conn.setAutoCommit(true);
      104                 }
      105             }
      106         }
      107         throw new SQLException("freezeAmount failed after retries");
      108     }
      109 
      110     private boolean isDeadlock(SQLException ex) {
      111         return ex.getErrorCode() == 1213 || "40001".equals(ex.getSQLState());
      112     }
      113     private void backoffSleep(int attempt) {
      114         try { Thread.sleep(50L * (attempt+1)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
      115     }
      116 }

       


      ?? 防篡改與對賬

      • hash_sign 字段:每次更新賬戶時,重新計算并寫入。每天跑 一致性校驗任務,比對簽名是否正確。

      • 雙錄:流水和訂單表都必須記錄,且可相互對賬。

      • 異地冷備:賬務數據需要跨機房復制,防止單點損壞。

      • 定期對賬:錢包系統內部對賬(賬戶余額 = 所有流水累加),與支付通道對賬(充值/提現)。


      ?? 錢包訂單表的合并問題

      • 充值訂單消費訂單 可以放在一個 wallet_transaction 表里(用 type 區分)。

      • 優點:查詢用戶交易歷史簡單。

      • 缺點:表會很大,可能需要分表(按 user_id/hash 分庫分表)。

      大廠(支付寶、微信、Paytm)一般也是用 一個訂單表 + 一張流水表,而不是分成兩張。

      ? 總結:

      • 余額、凍結金額 → 放在 wallet_account,原子更新。

      • 流水 → 必須單獨表,保證賬隨流轉。

      • 交易訂單 → 一張表統一管理充值/消費/退款,配合狀態機。

      • 防篡改 → 用 hash_sign 或日志落地校驗。

      • 定期對賬:錢包系統內部對賬(賬戶余額 = 所有流水累加),與支付通道對賬(充值/提現)。


      支付行業的錢包資金類場景里,更推薦使用 數據庫事務 + 條件更新(悲觀鎖/行級鎖/原子更新語句)來保障資金一致性,而不是依賴表里單獨的 version 字段來做樂觀鎖。原因如下:

      ?? 為什么事務比 version 更適合資金類場景

      • 資金安全 > 并發性能
        錢包、支付賬戶類場景,最重要的是 不能錯扣、不能多扣。事務+原子條件更新(如 update ... set balance = balance - ? where balance >= ?)可以天然保證一致性。
        樂觀鎖(version)的設計適合“沖突少,讀多寫少”的業務,比如商品資料修改。
        但在資金類業務中,沖突概率高、金額精確要求極嚴,用 version 反而會導致:

        • 大量失敗重試(因為 version 不匹配)

        • 并發下吞吐下降

        • 邏輯復雜(尤其凍結/解凍/退款等鏈路)

      • 數據庫本身提供的 ACID 能力已經能保證數據正確性:

        • InnoDB 支持行級鎖

        • 原子條件更新語句保證只有滿足條件的數據能被修改

        • 事務回滾可以保障失敗時不產生“臟扣款”

      所以在資金類系統中,更常見的方式是依賴事務,不依賴 version。


      ?? 凍結字段是必須的么

      ?? 下單和支付在一個事務內時,凍結字段其實不是必須

        1. 一個事務內完成

          • 下單寫訂單表

          • 扣減余額(UPDATE wallet_account SET balance = balance - 200 WHERE balance >= 200)

          • 寫支付單/流水表

          • 提交事務
            ?? 在事務提交前,其他并發事務看不到扣減結果,自然不會發生余額超支。

        2. 資金狀態明確

          • 因為下單和支付合并了,訂單一旦創建,就已經完成扣款,不存在“鎖定但沒付款”的狀態。

      ?? 那么凍結字段什么時候才是必須的?

      凍結字段的存在,是為了應對 下單和支付不是同時完成 的情況,比如:

      1. 下單和支付解耦(絕大多數電商)

        • 用戶下單時 → 只是鎖定商品和優惠券,不強制立即支付。

        • 如果不凍結資金,用戶下單多次可能超額。

        • 所以需要凍結余額,等支付成功再核銷,支付失敗則解凍。

      2. 預授權場景(打車、酒店、理財等)

        • 用戶占用一筆資金,但最后實際消費金額不確定(比如押金/預估車費)。

        • 必須凍結,否則最后要扣款時,可能用戶余額不足。

      3. 提現申請

        • 用戶點了提現申請,但實際清算到賬可能要 1~2 天。

        • 必須凍結,避免提現期間用戶再消費導致資金不夠。

      ?? 對比方案

      • 方案 1:下單 + 支付合并(事務內扣款)

        • 不需要凍結字段

        • 風險:靈活性不足,用戶沒法“先下單、后付款”

        • 適用:快捷支付、小額消費、錢包余額支付場景

      • 方案 2:下單與支付解耦(凍結字段必需)

        • 需要凍結字段,保證資金占用

        • 靈活性強,支持購物車下單后選擇支付方式

        • 適用:電商購物、預授權場景、大多數真實支付鏈路

       

      轉載請注明出處:http://www.rzrgm.cn/fnlingnzb-learner/p/19092096

      posted @ 2025-09-15 00:56  Boblim  閱讀(42)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 中国熟妇毛多多裸交视频| 中文字幕国产精品综合| 国产午夜福利不卡在线观看| 成人午夜激情在线观看| 欧美xxxx做受欧美| 麻豆精品久久精品色综合| 日本阿v片在线播放免费| 中文字幕乱码熟女人妻水蜜桃| 日韩国产精品无码一区二区三区| 国产在线午夜不卡精品影院 | 亚洲欧美偷国产日韩| 国产精品黄在线观看免费| 天天做天天爱夜夜爽女人爽| 丰满无码人妻热妇无码区| 国产一区二区三区综合视频| 国产精品亚洲av三区色| 欧美成人精品一级在线观看| 亚洲一区二区三区自拍高清| 精品久久精品午夜精品久久| 亚洲人成人伊人成综合网无码| 国产精品三级中文字幕| 曰韩无码av一区二区免费 | 国产精品视频白浆免费视频| 午夜男女爽爽影院免费视频下载| 亚洲日本欧美日韩中文字幕| 元码人妻精品一区二区三区9| 成人无码影片精品久久久| 国产精品久久蜜臀av| 人妻av中文字幕无码专区 | 国产视频一区二区| 加勒比久久综合网天天| 国产高跟黑色丝袜在线| 澳门永久av免费网站| 日本国产精品第一页久久| 国产太嫩了在线观看| 亚洲午夜理论无码电影| 男女扒开双腿猛进入爽爽免费看| 景宁| 色伦专区97中文字幕| 亚洲人成网站18禁止无码| 国产美女午夜福利视频|