diff --git a/assets/image_1751630881799_0.png b/assets/image_1751630881799_0.png new file mode 100644 index 0000000..f98d134 Binary files /dev/null and b/assets/image_1751630881799_0.png differ diff --git a/pages/Payment Service.md b/pages/Payment Service.md index d243808..7d4bf59 100644 --- a/pages/Payment Service.md +++ b/pages/Payment Service.md @@ -1,2 +1,886 @@ +- 金融牌照 - 平台跳转 -- 商家 \ No newline at end of file + - 商家-收款方 + - 用户-持卡人 + - 中间-卡组织 + - Visa + - MasterCard +- Card Network + - 中间转发各种各样的支付请求 + - 无需额外对接 + - 用户有各种各样的银行卡 + - 商家有作为收单方也有不同银行帐号 + - 减少对接以及合规的负担 +- Payment Service Plantform(PSP) + - 提供一个简单的API + - 调用API,PSP处理这些支付请求 + - 收取手续费0.5%-1% +- 支付系统 + - 记账+内部清算 + - 用来记录支付事实 + - 支付流程 + - 商家发起一个支付请求 + logseq.order-list-type:: number + - 调用外部PSP的API,执行支付的请求 + logseq.order-list-type:: number + - PSP执行完成,记录这笔账 + logseq.order-list-type:: number + - 通知对应的用户支付结果 + logseq.order-list-type:: number + - 每天定时对账目进行清算 + logseq.order-list-type:: number +- Function Requirement + - merchant send payment request + - client can pay + - merchant can query payment status +- NonFunction Requirement + - Sercutiy + - Reliability-handle failure + - consistency + - correctness + - scaleability-PSP API +- ![image.png](../assets/image_1751630881799_0.png) +- Initiation + logseq.order-list-type:: number +- Execution + logseq.order-list-type:: number +- Recording + logseq.order-list-type:: number +- Notification + logseq.order-list-type:: number +- Clearing + logseq.order-list-type:: number +- Payment integration + - serve-to-serve/PCI审计 + logseq.order-list-type:: number + - SDK,如果崩溃就会无法付款,存在供应链攻击的分享 + logseq.order-list-type:: number + - 隔离用户卡号 + - iframe + logseq.order-list-type:: number + - hosted redirect + logseq.order-list-type:: number + - 跳转到第三方的页面 +- data model + - single center table + logseq.order-list-type:: number + - 审计要求 + - 不能够修改 + - three table: order+payment+ledger + logseq.order-list-type:: number + - payment { + charge_id + order_id + payment_method + PSP + status{created, start, processing, successed, failed} + amount + currency + created_at + updated_at + idem_unique_key + } + - retry + - timeout + logseq.order-list-type:: number + - exponential backoff + logseq.order-list-type:: number + - jitter(+-15%)控制请求分布更加均匀 + logseq.order-list-type:: number + - DLQ(exhaust) + logseq.order-list-type:: number + - PSP调用callback URL + - 失败后,service主动轮询PSP + - 采用复式记帐 + - 写入到append-only数据库 + - AMAZON QLDB(Quantum Ledger Database) + - Alibaba ledgerDB + - 专门为金融优化的数据库 +- 对账 + - 银行发送settlement file +- Turning the database inside-out +- +- Text + - 今天我们来讲一个支付系统 + 这种系统你还是需要一点生活经验的 + 因为它不是真的让你去划账 + 你又没有这个金融牌照 + 拿什么去搞这笔钱 + 你有可能上网去买个衣服 + 还要跳转到另一个平台去付款 + 那比如说用户拿他的信用卡去买东西 + 那用户这边就是持卡人 + 商家里面叫做收款方 + 两边对应着各自有一个银行 + 像用户这边就是发卡行 + 然后对面就是收单行 + 中间其实还有一个叫做卡组织的东西 + 比如说Visa或者MasterCard + 他们都是card network + 然后中间会转发各种各样的支付请求 + 用户可能会有各种各样的银行卡 + 所以会对应不同的发卡行 + 然后商家作为收单方也会有不同的银行账号 + 那你要是挨个去对接呢 + 在工作量肯定是吓死人 + 合规的负担也会比较重 + 而且银行里面的技术更新通常会比较慢 + 有时候你可能看到一些上古的技术栈 + 所以通常我们会去找一个叫做PSP的平台 + 就是payment service platform + 通常这个PSP平台呢 + 会给我们一个比较简单的API + 然后我们直接去调用这个API + 然后由PSP来处理这些支付请求 + 当然它会向我们收取一定的手续费 + 通常是可能是0.5%到1%这个区间 + 那话说回来了 + 如果有人能去处理这个支付 + 那我们还设计什么支付系统呢 + 实际上这个支付系统想让你设计的是一个记账 + 再加上内部清算的这么一个流程 + 也就是说它是一个用来记录支付事实的系统 + 那这样这个问题就会简单很多了 + 我们可以拆分一下用户支付的这个流程 + 首先就是商家里面会发起一个支付请求 + 然后我们应该去调用这个外部的PSP的API + 去执行这个支付的请求 + 然后PSP执行完之后 + 我们应该去进行一下记录 + 就是说我们会把这笔账记下来 + 所以记下来之后不管它的成功和失败 + 你都应该去通知一下对应的用户 + 最后可能是每天对这个账目进行一下清算 + 其实真正的过程应该比这个要复杂一点 + 不过我觉得能够覆盖到这么几个步骤 + 应该已经差不多了 + 毕竟你的时间也有限 + 所以我先把这个放在这 + 我们来看一下它的功能性需求应该是什么 + 功能性需求其实还是比较明确的 + 首先商家这边 + 他要能发起一个支付请求 + 然后客户这边 + 要能去付费 + 然后商家这边应该能去查看这个订单的支付状态 + 非功能性需求呢 + 首先肯定是安全吧 + 这是肯定不用说的 + 所有和资金有关的东西 + security都是第一步的 + 然后应该是可靠性 + 他要能自己去handle一些failure之类的 + 接下来还有一个consistency + 因为肯定嘛 + 我们希望一笔钱只被扣款一次 + 然后这笔账被记下来 + 所以一致性很重要 + 另外一个比较重要的 + 常见的金融相关是正确性 + 肯定是希望你的账永远是对的 + 这里有一个不太确定的地方 + 就是说这个scalability + 我们会不会去考虑 + 因为实际上你是在调用一个外部的API + 所以那么很大程度上 + 你的bottleneck其实不在你自己这里 + 在外部 + 这种情况下 + 我建议我们就一开始不要先去考虑scalability了 + 后面如果有条件的话 + 我们再研究 + 搞清楚这些之后 + 我们就可以去画一个high level的diagram + OK + 这个图还是很简单的 + 只不过画的比较简略 + 我们要来挨个展开一下 + 那首先第一件事就是用户要去付款 + 那应该做什么 + 首先从商家这一方来说 + 我的服务器上肯定要给你提供付款这么一个逻辑 + 那也就是说我们应该做就是支付集成 + 支付集成 + 支付集成其实还是有不少方案的 + 比如说最简单就是说 + 我可以做一个server to server + 对吧 + 商家从用户这边去收集用户的这个卡号和他的这个CVV密码 + 然后后端把这个收集他的卡号和密码直接发给银行或者是卡网络 + 那这样其实你就相当于自己建立一个PSP了 + 对吧 + 因为你需要去取得支付机构的牌照 + 一般大家也不会这么去做 + 因为商家会接触到这些 PAN (primary account number) + 这些乱七八糟的东西 + 那也就是说你需要通过这个PCI的审计 + 审计这个事情通常比较复杂 + 合规门槛高 + 而且也比较贵 + 除了技术的复杂度之外呢 + 初期的投入也很高 + 基本上只有大型的企业会用这种方案了 + 但是好处也会比较明显 + 就是说一旦通过了这个PCI审计 + 自主权就相对比较大了 + 不再需要去依赖第三方的支付平台 + 可以省下一笔第三方的通道费 + 自己也可以去控制一些逻辑 + 比如说增加一些错误处理啊 + 分期 分账啊 这些玩意儿 + 那另外一个比较简单的一点方案 + 就是说我引入一个支付平台的SDK + 这个应该是我们比较常见一种方案 + 大家通常用着stripe paypal + 或者说国内有微信 支付宝 + 其实都提供这种方案 + 这样一来PSP提供一个js library + 商家把它呢 + 嵌入到自己的网站上 + 自己去开发一些定制的UI + 付费的时候 + 这个SDK把用户的卡号 + 直接传到这个PSP去 + 然后会返回一个token + 商家用这个token来调用PSP的API + 但是本身不接触用户的卡号 + 这样一来就可以避免 + 去接触用户的账号信息 + 但是这样说 + 你的瓶颈就在这个SDK上 + 如果这个SDK它崩溃了 + 那用户就不能付款 + 理论上这种 + JavaScript SDK是通过fetch() + 然后送到PSP的domain去的 + 不需要经过商家的服务器 + 但是脚本实际上 + 还是跑在商家页面里 + 所以这里如果你用XSS注入(Cross-Site Scripting跨站脚本) + 或者是MageCart植入 + 都可以在用户的浏览器里面 + 去窃取到这个输入的卡号 + 那也就是说 + 这里其实存在供应链攻击的风险 + 那商家这一端 + 就应该要保护自己的前端页面 + 做上一些安全策略 + 比如说去做一个 + CSP(内容安全策略 Content Security Policy) 或者是SRI (子资源完整性 Sub-resource Integrity)这些东西 + 只允许去加载可信的脚本 + 并且你要对这个脚本 + 验证一下哈希值 + 再往后退一步的话 + 你可以使用一个iframe + iframe相当于一个 + 更加安全的Sandbox管理 + 隔离敏感数据 + 也减小了攻击面 + 商家只得到支付的token + 那么现在的SDK + 实际上都会采用混合模式 + SDK直接在底层 + 注入一个iframe + 浏览器端会使用 + iframe hosted field + 对商家隐藏银行卡的信息 + 然后使用PSP的公钥 + 去做CSE(客户端加密 client-side encryption) + 把这个卡号加密起来 + 最后Post请求发到PSP去 + PSP拿到这个密文之后 + 会进行解密 + 然后返回一个不可逆的token + 商家还是拿这个token去做扣款 + 不再去接触这个卡号 + 但是iframe的问题在于 + 前端的CSS和响应式 + 会设到这个iframe的限制 + 这会对你的UI的开发 + 产生一定的限制 + 有能力企业还是会选择 + 基于JavaScript SDK + 自己去做二次开发 + 这样来说自由度高一点 + 但是同样成本也会更加高一点 + 最后一种 + 就会进行hosted redirect + 直接去走一个托管的支付页面 + 用户点击下单 + 然后一个302 + 跳转到PayPal或者Strape + 这样的平台上去 + 等你用户付款完了之后 + 再重定向回来 + 这其实是最简单的对吧 + PCI的负担最低 + 运维也最简单 + 一旦出了故障 + 就全靠PSP自己去兜底去了 + 同样它的自由度也是最低的 + 因为你的UI是人家的 + 品牌也是人家的 + 全看PSP + 而且这里会多一步跳转 + 这代表着 + 这里的用户的转化率 + 会有些损失 + 总的来说呢 + 你的可定制化越高 + 成本越高 + 不仅仅是技术实现的成本 + 还会有PCI的责任 + 和安全管控的成本 + 要求又会更多一点 + 等你集成了支付系统之后 + 这里就会出现第二个问题 + 我们花了这么大劲 对整个支付的流程做了下隔离 + 那既然隔离了 + 商家要怎么来记录这笔交易呢 + 其实我们只是隔离了 + 商家和用户的银行卡信息 + 用户这边提交完付款信息之后 + PSP还是会把这个token发回来 + 这个token也叫nonce + 后续的扣款 + 还是要商家拿着这个nonce + 去和PSP发请求的 + nonce一般来说是一次性的 + 有的时候你会看见 + 商家问你 + 需不需要保存这个支付信息 + 如果你勾取了这条 + 商家会再请求一次 + 换取一个可以复用的token + 这里我们简单一点 + 就只说这个单次的nonce + 有了这些信息之后 + 其实你已经可以开始 + 去写数据库了 + 那么这里就会有一个问题 + 我这数据库要怎么存 + 第一个想法通常是 + 我能不能用一张表 + 存所有的信息 + 对吧 + 我每次有一笔支付请求 + 我就记下来 + 用户买了什么东西 + 花了多少钱 + 然后这个支付的状态是什么 + 它的卡是什么 + 这样每一条交易 + 对应着这个表里的一条记录 + 但这里可能会有一个问题 + 因为支付这个东西 + 它是受到合规限制的 + 任何和金融相关的东西 + 都需要经过审计 + 支付数据在这里 + 应该是不可更改的 + 你每次有什么操作 + 不能说去覆盖 + 之前的这个状态 + 所以最好来说 + 还是要去拆分一下数据表 + 这个里面 + 应该会有三种不同的状态 + 首先是你的业务状态 + 这里有哪些订单 + 用户这个订单 + 对应着什么商品 + 第二个是你的交易状态 + 用户发起了这个 + 支付请求之后 + 有没有付钱? + 用什么卡付钱? + 付了多少? + 然后现在是被拒绝了 + 还是已经支付成功了 + 第三个状态 + 就是你的资金状态 + 这笔钱有没有到账 + 这笔账要不要进行分配 + 有多少是最后要给商家的 + 有多少是最后 + 要付给支付渠道的手续费 + 所以这里三个状态 + 如果要做责任分离的话 + 至少是三张数据表 + 这三张数据表的读写模式 + 应该是完全不一样的 + 比如说这个资金状态这张表 + 它就应该是不可更改的 + 对于支付系统 + 这种合规驱动业务来说 + 有条件 + 我们还是多存一张表 + 也好过在同一张表里 + 改来改去 + 理论上我们可能是需要 + order table + 加上一个payment table + 再加上一个ledger的账簿 + 这题的核心还是payment + 那我们还是关注一下 + payment表的数据结构 + 假设这里 + 就是这个payment表 + 我们可能需要哪些字段 + 首先你肯定会要有 + 这个charge ID + 第二 + 然后你会有order ID + 接下来可能是payment method + 然后是对应的PSP是什么 + 这笔订单的status是什么 + status可能是说 + 我一开始先去创建 + 这张支付的请求 + 然后这个支付开始处理 + 最后可能是success + 或者是failed + 这里通常是需要一个 + 状态机来表示 + 然后这里面会有多少钱 + 它对应的货币是什么 + 这笔支付是什么时候创建 + 又是什么时候更新的 + 最后这里可能需要一个 + unique ID来做这个幂等 + 我直接叫他 + idempotency key了 + 这张表要怎么存呢 + 通常来说和金融相关 + 我们都要注意一致性 + 但是一致性 + 也分内部一致性 + 外部一致性 + 内部一致性其实好处理 + 对吧 + 我直接拿关系性数据库 + 这关系性数据库 + 是有一致性保障的 + 那么外部一致性 + 这个时候我们就要盘一下 + 这个workflow + 用户在这里去进行下单 + 他打开这个客户端 + 点击一个checkout + 然后开始支付 + 用户的预期是 + 我就把这一单结了 + 这里其实最怕 + 发生的是一个 + double charging + 或者double spending + 比如说用户 + 网卡了一下 + 然后连着点了好几次 + 然后接下来服务器这边 + 就收到多个请求 + 那服务器这边 + 多个请求 + 肯定不能通通去扣款 + 对吧 + 他肯定要做一个 + exactly once + 这个exactly once + 就需要依赖了 + 这个idempotency key了 + 当用户点开 + 这个checkout页面的时候 + 服务器这个时候 + 应该会生成一个幂等键 + 然后后续流程 + 全都带着这个幂等键 + 用户点开这个页面 + 服务器把这幂等键 + 发给客户 + 客户下完单之后 + 带这个幂等键 + 再返回到payment service这边 + service这端 + 我看到这个idempotency key + 开始去做de-duplication + 拦截重复的请求 + 这样就算用户点了多次 + 也只有一个请求 + 会被进行后续的处理 + 处理的时候 + 需要去更新 + payment表的status状态 + 比如说一开始是created + 然后传给他一个idempotency key + 然后当你收到一个 + 合法的请求之后 + 就转入这个start状态 + 表示这个支付请求已经收到了 + 下一步就要去调用 + 外部平台的这个支付的API了 + 也就是说调用这个PSP + PSP收到这个请求开始处理 + 就中间可能会有一些风控啊 + 或者是授权之类的 + 交易授权这里可快可慢 + 通常你这里提交完 + 至少应该收到一个明确的信号 + 比如说“你的支付请求已经受理” + 或者告诉你“交易失败了” + 然后这个时候payment表里的status + 应该去转入到processing阶段 + 这里最好画一下这个状态机 + 好 有了这个状态机之后 + 我就可以把这个简化一下 + 由于PSP是异步处理 + 所以我们实际上不知道PSP + 什么时候能处理好 + 但通常来说PSP应该先给你一个 + “我已经在处理了” + 那如果PSP就没有给你一个信号 + 比如说有网络抖动 + 对吧 + 他给你信号 + 但是你没有收到 + 所以这里你应该引入一个retry的机制 + 那通常来说 + 一旦我们要加入retry的话 + 那你就要想到retry的四个要素了 + 第一个就是timeout + 这个retry的等待时间 + 应该是有个最大限制 + 超过这个timeout的时间之后 + 你才去触发这个retry + 那第二个就是backoff + 我是间隔一秒钟 + 还是间隔两秒钟 + 还是说我不断去增加这个时间间隔 + 通常来说 + 这里比较推荐的是 + 做一个exponential backoff + 我第一次等待一秒 + 然后第二次等待两秒 + 然后第三次等待四秒 + 这下来等待八秒 + 每次延长这个间隔的时间 + 这样不会给后端造成太大压力 + 那第三点是做一个jitter + jitter的意思是 + 我每次向后端发请求 + 我加入一些网络抖动 + 比如说我加一个正负15% + 比如说我按照某个频率 + 给后端去发送这个重试的请求 + 但是这里有另一个服务器 + 它也采用这个频率 + 跟你同步的发送这个请求 + 那这个时候对后端来说 + 它可能在同一个时间 + 会收到多个请求同时涌入 + 然后在下一个间隔 + 大家又同时暂停了 + 然后再到下一个间隔 + 又同时涌入 + 加入jitter之后 + 我可以对这个间隔时间 + 做一个正负15%的抖动 + 这样可以将这个分布 + 打得更加均匀一点 + 第四个要素就是 + dead letter queue了 + 所谓死信队列 + retry这里 + 我们最好设置一个 + 最大的重试次数 + 一旦超过这个次数之后 + 你可以当做 + 你的后端这个PSP + 已经宕机了 + 你就不要再进行retry + 你应该把这个消息 + 放入这个死信队列 + 然后去进行一些处理 + 比如说你本地 + 做一个整体的回滚 + 通常可能还要再 + 熔断一段时间 + 那么对于外部的PSP来说 + 因为你这里不断的在重视 + 所以外部的PSP + 也要去做 + 这个de-duplication的操作 + 那么这样一来 + 它代表着你这个幂等键 + 也应该同步的 + 穿透到外面的PSP去 + 这样至少也能 + 增加一道保险 + 等PSP返回消息 + 提交成功之后 + 就可以先给用户 + 返回一下信息了 + 比如说你告诉用户 + “已经下单” + “等待确认” + 然后PSP里面 + 会进行一步的处理 + 通常来说 + 我们会给PSP + 一个callback URL + PSP处理完这笔支付 + 会调用这个URL + 通知我们 + 但是如果这个PSP + 调用这个callback URL + 失败了 + 它没通知到我们 + 对吧 + 网络还是有抖动 + 所以这个时候 + 你还要再补上一个轮询 + 超过一段时间 + 没有收到这个callback + 那就是主动查询一下 + 当PSP处理完之后呢 + 这状态机 + 就应该进入这个success + 或者是fail + 得到一个固定结果 + 然后把这个结果通知用户 + 那如果这里进入success + 就代表着扣款成功了 + 那这个时候 + 你应该就要去写这个ledger + 这个里面又会涉及到 + 对账户 交易和分录的建模 + 所以也是一个比较复杂的系统 + 一般来说 + 行业的标准是 + 商务的实体要采取复式记账(double-entry bookkeeping) + 也就是说 + 每笔交易至少要 + 影响到两个账号 + 产生一组 + 金额相等 方向相反的记录 + 这里非要扯一下的话 + 你应该考虑到ledger的金融属性 + 账本这种东西 + 一旦你写入之后 + 就是不可变的 + 所以这个ledger + 通常应该写入到一个 + append-only的数据库去 + 这里除了你能想到的 + 关系型数据库之外呢 + 通常来说 + 对于这种specific domain + 你可以去猜一下 + 问一下面试官 + 是不是应该有一个 + 专用的技术栈 + 那ledger这里 + 确实是存在ledger使用的数据库 + 比如说amazon会有一个QLDB(Quantum Ledger Database) + 或者是Alibaba + 开源的ledgerDB这种东西 + 他们都提供了SQL接口 + 然后专门为金融事务 + 进行了优化 + 这种专业优化过的数据库 + 通常来说 + 开发难度会比较低 + 然后很多你需要的功能 + 都是已经内置好了 + 比如说审计啊 + 或者是加密之类的 + 最后一步就是进行对账了 + 因为任何在线链路 + 都可能遇到bug + 所以银行这里 + 应该定时发过来 + 这个settlement file(结算报告) + 我们可以每天执行一次 + 对账任务 + 如果有问题呢 + 那就可以触发一个自动补偿 + 或者是转到人工进行审核 + 这样一来 + 应该就是这个完整的支付流程 + 你看这个流程有问题吗 + 其实还是有的 + 你记得这里 + 我们补了一个轮询 + 有一笔交易一直在审核 + 然后商家这边 + 等不到这个callback + 于是开始进行轮询 + 轮询几次 + 到达这个最大上限 + 然后写入了一个fail + fail了之后 + PSP这边突然发现 + 交易过了 + 于是这个PSP + 调用了这个callback + 那这个时候 + 就应该写入一个成功 + 对吧 + 这个就叫做状态机的跳变 + 它也不是不能解决 + 这里我们可以使用一个 + 单调状态机 + 一旦你这个状态进入fail之后 + 就不能再覆盖成这个success + 或者说我这里 + 打一个版本号的补定 + 每次进行更新的时候 + 我要去比较这个版本号 + 那最差的情况下 + 你还可以等到 + 这个日终的对账环节 + 总之你的账不能出错 + 但是这个问题的根源在于 + 你每次状态机的变化 + 实际上是对于这个 + paymentDB的一条记录 + 发起了一次UPSERT + 用UPSERT这个语句 + 覆盖之前的状态 + 但是这个问题也不大 + 因为大部分PSP + 其实都是这条方案 + 一张表去维护 + 这个实时的操作状态 + 另一张账务表 + 会负责这个资金的守恒 + 这样一来 + 它开发成本也比较低 + 维护成本也低 + 方案本身呢 + 也比较成熟 + 也是合规的 + 但如果你要进一步呢 + 其实这里确实是存在 + 另一种方案 + 之前我们也讲过一些 + 数据库和流系统互换的案例 + 有过这么一篇 + 《Turning the database inside-out》 + 我们在这里去掉这个数据库 + 然后采用一个event store + event store呢 + 可以使用Kafka + 或者是Apache Pulsar + 之类的流系统 + 也有可能是一个 + append-only的db + 当然你也可以两个都使用 + 那这样一来 + 所有的变更 + 都作为一个事件 + 写入到这个event store里面 + 从这个event store + 作为source of truth + 在我们再通过 + projection或者view的方式 + 来生成视图表 + 加速查询 + 事件一旦写入呢 + 就不再更改 + 任何的纠错 + 以新事件的方式写入 + 进行冲正 + 不少互联网公司 + 其实都会采用 + 这种ledger系统 + 比如说stripe用过 + Coinbase用过 + Square也用过 + 都是一个不可变的账本 + 再加上事件流的形式 + 这样一来 + 你既可以获得一个 + 金融系统的正确性 + 也可以获得一个 + 互联网系统的可拓展性 + 但它的代价也比较明显 + 这里有一个比较高的 + 系统复杂度 + 那开发和运维的成本 + 也比较高 + 团队本身呢 + 需要对流系统和 + 金融系统都比较熟悉 + 具体怎么选型 + 还是要看你的场景 + 一旦你接受了 + 这个流系统的这种设定 + 那其实这个系统 + 可以产生很多的变化 + 比如说 + 我将这个消息队列 + 放到前面去 + 进行一下削峰填谷 + 同时进行数据落盘 + 那这样一来的话 + Payment Service + 作为一个Broadcast + 把你每次的事件 + 投放到不同的数据库去 + 别的结构可以不变 + 每次对账的时候 + 你可以在消息队列的前端 + 进行事件的重放 + 重新计算一下 + 如果你最后还有时间的话 + 我建议还是做一下 + show off + 给你的面试官录一手 + 比如说这里 + 我们可以在Payment Service + 前面插入一个Risk Engine + 去做一下Fraud Detection + 欺诈检测 + 其实有很多的方案 + 不一定一上来 + 就要做什么机器学习 + 那最简单 + 你可以说 + 我使用一个规则引擎 + 比如说我加入一个规则树 + 或者是黑名单 + 白名单这样 + 那这种方法比较简单 + 门槛也比较 + 也便于合规和审计 + 但是呢 + 它的问题就不太适合 + 比较复杂的欺诈手段 + 那另一种比较常见的是 + 我使用一些统计学的方式 + 或者是直接上一些 + 简单的机器学习模型 + 那这类的方案 + 通常会受限于数据本身 + 对于新的欺诈手段 + 反应会比较慢一点 + 而且你也需要 + 手动去寻找特征 + 标注一下数据 + 大的公司 + 可能会使用无监督学习 + 来解决冷启动 + 或者零样本的问题 + 但是这种方案呢 + 也有一定的误报率 + 需要人工介入进行审核 + infrastructure的支持呢 + 你可以上一些图模型 + 或者GNN这样的手段 + 来捕捉比较复杂的形式 + 这类系统的维护和开发 + 都会比较复杂 + 模型的推断和开销 + 也会比较大 + 但是金融风控力 + 还是比较常见的 \ No newline at end of file