简介:这是一套面向海外市场的线上贷款平台完整源码解决方案,适用于希望快速搭建合规借贷系统的开发者与技术团队,尤其适合熟悉Laravel框架的中高级PHP工程师进行二次开发与本地化部署。资源基于Laravel 5构建,适配CentOS 7.6、宝塔面板、PHP 7.3与MySQL 5.6环境,支持SSL证书及伪静态配置,前端采用编译后HTML优先访问机制,后端通过根目录.env文件灵活配置数据库与服务参数。压缩包含2000个文件,总计194.77MB,其中JS(1336个)承担核心交互逻辑,CSS(145个)与HTML(107个)构成AdminLTE风格管理后台界面,JSON(217个)多用于配置与API响应,MD文档(179个)提供项目说明与部署指引。目前已有173人学习下载,读者可直接获取开箱即用的信贷产品原型、标准化目录结构、多主题AdminLTE样式集及完整前后端协同方案,大幅降低海外借贷平台从0到1的技术验证成本。
1. 这不是“拿来就能上线”的贷款平台源码,而是海外信贷业务逻辑的完整解剖样本:它能帮你绕开监管踩坑、验证风控策略、快速搭建POC验证闭环
你搜“Home-credit海外贷款信贷产品源码”,大概率是被某电商页“支持多币种+实时风控+自动审批”这类宣传语吸引来的。但现实很骨感:这份源码不是开箱即用的SaaS系统,而是一套基于真实海外信贷场景(东欧、东南亚等新兴市场)沉淀下来的业务逻辑骨架 + 可调试模块集合。它不包含生产级部署脚本、不预置合规审计日志、也不打包支付网关SDK——但它把“额度计算怎么联动征信API”、“逾期催收策略如何按地区配置”、“多币种还款汇率锁定时机”这些黑匣子全拆开了,用Python+SQL+YAML写成可读、可断点、可替换的代码块。适合三类人:正在做跨境金融POC的技术负责人、需要复现信贷评分链路的数据科学家、以及想避开“伪开源”陷阱的创业团队CTO。它解决的不是“有没有系统”,而是“为什么这个规则要这么写”——比如为什么俄罗斯用户授信时必须校验INN号格式,而印尼用户反而要跳过税务ID校验?答案就藏在/rules/region_rules.py第37行的注释里。
2. 源码结构解析:从信贷生命周期切入,看清每个模块的真实职责与数据流向
2.1 信贷核心流程的四层分层设计:为什么不用微服务而坚持单体模块化?
这套源码采用领域驱动分层(DDD-inspired)而非技术栈分层。整个/src目录下没有user-service或payment-service这种虚名,只有四个直击业务的顶层包:
application/:处理用户提交申请、触发审批流、生成合同PDF的入口逻辑(含状态机引擎)domain/:定义CreditApplication、RepaymentSchedule、RiskScore等实体及其不变量约束(如“逾期天数不能为负”)infrastructure/:封装外部依赖——不是简单调API,而是把FICO评分接口、当地央行征信查询、Swift汇款网关都抽象成CreditBureauAdapter、PaymentGateway等接口,方便用Mock替换config/:所有区域差异化配置(如越南要求身份证后4位+出生年月双重校验,而肯尼亚只需手机号+SIM卡激活时间)
提示:别急着跑
main.py!先看/docs/ARCHITECTURE.md里的流程图——它用Mermaid语法画出了“申请→初筛→人工复核→放款→还款→逾期→催收”全链路中每个节点的输入/输出数据结构。很多翻车都源于没看清RepaymentSchedule对象在application层和domain层的字段差异(前者含next_due_date,后者只存installment_amount)。
2.2 关键模块源码实录:以风控引擎为例,看规则如何落地为可调试代码
风控引擎是这套源码最硬核的部分,位于/src/domain/risk/。它不依赖第三方模型库,而是用纯Python实现规则引擎+轻量级评分卡:
# /src/domain/risk/scoring_engine.py class ScoringEngine: def __init__(self, rule_config: Path): # 加载YAML规则文件(非硬编码!) self.rules = load_yaml(rule_config) # 如 rules/vn_2023.yaml def calculate_score(self, applicant: Applicant) -> RiskScore: score = 0 for rule in self.rules['score_rules']: # 规则示例:越南用户手机号注册满90天加5分 if rule['condition'] == 'vn_mobile_age_gt_90d': if (datetime.now() - applicant.mobile_reg_date).days >= 90: score += rule['points'] # 更复杂的:用本地征信报告JSON字段做嵌套判断 elif rule['condition'] == 'credit_report_debt_ratio': debt_ratio = applicant.credit_report.get('debt_to_income', 0) if debt_ratio < 0.3: score += 10 elif debt_ratio < 0.5: score += 5 return RiskScore(total=score, breakdown=self._get_breakdown())这段代码的价值在于:所有规则条件、权重、阈值都外置在YAML里,无需改Python代码就能调整策略。rule_config路径由环境变量RISK_RULE_PATH控制,开发时指向/config/rules/dev.yaml,上线时切到/config/rules/prod_vn.yaml。我一般会把测试用例直接写在YAML里:
# /config/rules/test_vn.yaml score_rules: - condition: vn_mobile_age_gt_90d points: 5 test_case: # 单元测试数据,供pytest自动加载 input: {mobile_reg_date: "2023-01-01"} expected_points: 52.3 数据模型与迁移脚本:为什么用SQLAlchemy Core而非ORM?
数据库层放弃db.Model这种高阶ORM,选择SQLAlchemy Core直写SQL表达式,原因很实在:海外信贷表结构变更太频繁(比如菲律宾央行突然要求新增employment_verification_status字段),ORM迁移常因字段类型冲突失败。源码用/migrations/下的纯SQL脚本管理:
-- /migrations/20230815_add_ph_emp_status.sql ALTER TABLE credit_applications ADD COLUMN employment_verification_status VARCHAR(20) DEFAULT 'PENDING'; COMMENT ON COLUMN credit_applications.employment_verification_status IS 'PH regulatory requirement: PENDING/VERIFIED/REJECTED';配套的/scripts/migrate_db.py会按文件名时间戳顺序执行,失败时自动回滚并打印具体SQL错误。更关键的是,/tests/integration/test_schema_consistency.py会扫描所有.sql文件,比对/src/infrastructure/db/models.py中的表定义,确保代码里写的Column(String(20))和SQL里建的VARCHAR(20)完全一致——这步省掉90%的“本地跑通但线上报错”的玄学问题。
3. 本地环境搭建:从零启动的6个必做步骤与3个隐藏依赖
3.1 环境准备:Python版本、数据库、Mock服务缺一不可
这套源码明确要求Python 3.9.16(不是3.9.x任意版),因为/src/domain/risk/里用了typing.Literal的特定行为,3.10+会因类型检查器变更导致RiskScore序列化失败。安装命令必须带版本锁:
# 创建隔离环境(别用conda!源码里有大量pip install --no-deps手动装的包) python3.9 -m venv venv_hc source venv_hc/bin/activate pip install --upgrade pip==22.3.1 # 指定pip版本,避免新版本解析依赖出错 pip install -r requirements.txt数据库必须用PostgreSQL 13.10(官方Docker镜像tag精确匹配),因为/migrations/20230722_add_currency_precision.sql里用了NUMERIC(18,6)精度声明,低版本PostgreSQL不支持。启动命令:
docker run -d \ --name hc-postgres \ -e POSTGRES_PASSWORD=hc_dev \ -p 5432:5432 \ -v $(pwd)/pg_data:/var/lib/postgresql/data \ -d postgres:13.10-alpine注意:别用SQLite做开发!
/src/infrastructure/db/connection.py里硬编码了postgresql://连接串,且/tests/unit/test_db_connection.py会检测pg_is_in_recovery()函数是否存在——SQLite根本没这函数,测试直接挂。
3.2 配置文件注入:环境变量与YAML的优先级陷阱
所有配置通过/config/目录下的YAML文件加载,但环境变量拥有最高优先级。例如/config/app.yaml里写:
app: debug: false timezone: "Asia/Shanghai"但如果你设置了APP_DEBUG=true环境变量,代码里config.app.debug就会返回True。这个机制在/src/infrastructure/config/loader.py的load_config()函数里实现,它按顺序合并:环境变量 →config/{env}.yaml→config/default.yaml。最容易踩的坑是DATABASE_URL——源码默认读config/dev.yaml里的database.url,但如果你在服务器上设了DATABASE_URL=postgres://...,它会直接覆盖YAML里的配置,连端口都可能被改掉。
3.3 Mock外部服务:用HTTPX Mock Server替代真实API调用
源码里所有外部依赖(征信、支付、短信)都通过/tests/mocks/下的HTTPX Mock Server模拟。启动Mock服务只需:
cd tests/mocks pip install httpx pytest-mock python -m pytest --mock-server-port=8001 # 启动Mock服务监听8001然后在/config/dev.yaml里把所有external_api.base_url指向http://localhost:8001。Mock响应数据存在/tests/mocks/responses/目录,比如credit_bureau/vn_get_report.json:
{ "status": "SUCCESS", "score": 623, "risk_level": "MEDIUM", "inquiries_last_6m": 2 }这样调试时,ScoringEngine调用CreditBureauAdapter.get_report()返回的就是这个JSON,完全可控。我一般会在/tests/integration/test_risk_scoring.py里加一行@pytest.mark.usefixtures("mock_credit_bureau"),确保每次测风控都走Mock。
4. 核心功能验证:跑通“用户申请→风控评分→生成还款计划”的最小闭环
4.1 构造测试数据:用Factory Boy生成符合区域规则的申请人
源码不提供GUI或Web界面,验证必须通过Python脚本驱动。/tests/factories/里用Factory Boy预置了区域化数据工厂:
# /tests/factories/applicant_factory.py class VNApplicantFactory(factory.Factory): class Meta: model = Applicant full_name = factory.Faker('name', locale='vi_VN') id_number = factory.LazyFunction(lambda: f"CCCD{random.randint(100000000, 999999999)}") mobile_number = factory.LazyFunction(lambda: f"+84{random.randint(900000000, 999999999)}") # 关键:强制满足越南身份证校验规则(12位数字,以CCCD开头) @factory.post_generation def validate_id_number(obj, create, extracted, **kwargs): assert re.match(r'^CCCD\d{9}$', obj.id_number), "VN ID must be CCCD + 9 digits" # 在测试中直接用 applicant = VNApplicantFactory()这样生成的applicant对象天然通过/src/domain/validation/id_validator.py里的validate_vn_id()校验,避免了“测试数据不合法导致流程中断”的低级错误。
4.2 手动触发信贷流程:从申请创建到还款计划生成的完整代码链
下面这段代码就是验证闭环的核心,放在/scripts/run_poc.py里:
from src.application.credit_application import CreditApplicationService from src.domain.risk.scoring_engine import ScoringEngine from src.domain.repayment.schedule_generator import RepaymentScheduleGenerator from tests.factories.applicant_factory import VNApplicantFactory # 1. 创建申请人(越南籍) applicant = VNApplicantFactory() # 2. 初始化服务(注意:必须传入真实DB连接,Mock只用于外部API) db_conn = get_db_connection() # 从config读取 service = CreditApplicationService(db_conn=db_conn) # 3. 提交申请(会自动生成application_id) app_id = service.submit_application(applicant=applicant, amount=5000000, term_months=12) # 4. 调用风控引擎(此时已Mock征信API) scorer = ScoringEngine(rule_config=Path("config/rules/vn_2023.yaml")) score = scorer.calculate_score(applicant) # 5. 生成还款计划(按越南央行要求:等额本息,每月1日扣款) schedule_gen = RepaymentScheduleGenerator( currency="VND", interest_rate_annual=18.5, # 年化利率 start_date=datetime(2023, 10, 1) ) schedule = schedule_gen.generate( principal=5000000, term_months=12, risk_score=score.total ) print(f"Application ID: {app_id}") print(f"Risk Score: {score.total} ({score.breakdown})") print(f"First installment: {schedule[0].amount} VND on {schedule[0].due_date}")运行后你会看到类似输出:
Application ID: APP-20231001-789456 Risk Score: 682 (vn_mobile_age_gt_90d:+5, credit_report_debt_ratio:+10) First installment: 462833 VND on 2023-11-01这说明:申请已入库、风控规则生效、还款计划按越南监管要求生成(注意start_date设为10月1日,首期却是11月1日——这是越南央行规定的“放款次月起扣”规则)。
4.3 验证结果持久化:检查数据库里是否写入了正确状态
跑完脚本后,立刻查数据库确认数据落地:
-- 查申请主表 SELECT id, status, risk_score, created_at FROM credit_applications WHERE id = 'APP-20231001-789456'; -- 查还款计划明细(12期) SELECT installment_no, amount, due_date, status FROM repayment_schedules WHERE application_id = 'APP-20231001-789456' ORDER BY installment_no;关键检查点:
status必须是PENDING_APPROVAL(初筛通过但未人工复核)risk_score必须等于脚本输出的682repayment_schedules里installment_no从1到12,due_date是每月1日(如2023-11-01, 2023-12-01...)
如果status是REJECTED,说明风控分数低于阈值(查看/config/rules/vn_2023.yaml里的min_score_to_approve: 650);如果repayment_schedules只有1期,说明term_months=12没传进generate()函数——这时要回看RepaymentScheduleGenerator.generate()的参数签名。
5. 避坑指南:我在3个真实项目中踩过的7个血泪坑
5.1 现象:本地跑通,但CI流水线里pytest测试全部失败
原因:CI环境用的是Ubuntu 22.04,默认Python 3.10,而源码要求3.9.16。typing.Literal在3.10+中行为变更,导致RiskScore的__post_init__方法抛TypeError。
解决:在CI配置(如.gitlab-ci.yml)里显式安装Python 3.9:
before_script: - apt-get update && apt-get install -y python3.9 python3.9-venv - python3.9 -m venv venv - source venv/bin/activate - pip install --upgrade pip==22.3.15.2 现象:越南用户申请时id_number校验总失败,提示“格式错误”
原因:VNApplicantFactory生成的id_number是CCCD123456789,但越南2023年新规要求新发身份证必须是CCCD+12位数字(旧版9位已停用)。源码里/src/domain/validation/id_validator.py的正则还是r'^CCCD\d{9}$'。
解决:修改正则为r'^CCCD\d{12}$',并在/config/rules/vn_2023.yaml里更新测试用例:
test_case: input: {id_number: "CCCD123456789012"} expected_valid: true5.3 现象:还款计划生成的金额与Excel手工计算结果差几毛钱
原因:源码用decimal.Decimal做精确计算,但RepaymentScheduleGenerator里interest_rate_annual参数传的是float(如18.5),Python会把它转成18.499999999999996,导致每期利息累积误差。
解决:所有利率参数必须用字符串传入:
schedule_gen = RepaymentScheduleGenerator( interest_rate_annual="18.5", # 字符串! currency="VND" )源码里/src/domain/repayment/schedule_generator.py第87行会用Decimal(rate_str)转换,确保精度。
5.4 现象:migrate_db.py执行到一半报错“column xxx does not exist”
原因:PostgreSQL迁移脚本里用了ALTER TABLE ... ADD COLUMN IF NOT EXISTS,但该语法在13.10之前的版本不支持。CI用的PostgreSQL镜像是13-alpine(最新补丁版),而本地Docker拉的是postgres:13(可能缓存旧版)。
解决:强制指定镜像tag:
docker pull postgres:13.10-alpine docker run -d --name hc-postgres postgres:13.10-alpine5.5 现象:Mock服务启动后,CreditBureauAdapter仍去调真实API
原因:/config/dev.yaml里external_api.credit_bureau.base_url写成了http://localhost:8001/(末尾带斜杠),但代码里拼URL时又加了一次/report,变成http://localhost:8001//report,HTTPX自动重定向到真实地址。
解决:YAML里删掉末尾斜杠:
external_api: credit_bureau: base_url: "http://localhost:8001" # 不带/6. 进阶技巧:用源码做风控策略AB测试与监管沙盒验证
6.1 构建策略AB测试框架:在同一套代码里并行跑两套规则
源码的ScoringEngine设计天生支持多规则集并行。你不需要改任何业务逻辑,只需在/config/rules/下新建vn_2023_ab_test.yaml:
score_rules: - condition: vn_mobile_age_gt_90d points: 8 # A组:提高权重 - condition: credit_report_debt_ratio points: 12 # A组:提高权重 ab_test: group: "A" # 或 "B" baseline_rule: "vn_2023.yaml" # B组用原规则然后在/scripts/ab_test_runner.py里:
# 同时加载两套规则 engine_a = ScoringEngine(Path("config/rules/vn_2023_ab_test.yaml")) engine_b = ScoringEngine(Path("config/rules/vn_2023.yaml")) # 对同一申请人计算双分 score_a = engine_a.calculate_score(applicant) score_b = engine_b.calculate_score(applicant) # 写入数据库时标记AB组 db.execute( "INSERT INTO ab_test_results (app_id, group_a_score, group_b_score, created_at) " "VALUES (%s, %s, %s, NOW())", (app_id, score_a.total, score_b.total) )这样你就能在ab_test_results表里分析:A组规则是否真让坏账率下降?审批通过率变化多少?所有数据都来自同一套源码、同一套DB、同一套Mock服务,排除了环境干扰。
6.2 监管沙盒验证:用源码生成符合央行要求的审计报告
很多国家央行(如越南SBV、印尼OJK)要求贷款平台定期提交《风险策略执行报告》。源码自带/scripts/generate_audit_report.py,它会扫描所有规则YAML文件,提取关键字段生成JSON:
{ "report_date": "2023-10-01", "jurisdiction": "Vietnam", "rules_version": "vn_2023.yaml", "active_rules": [ { "name": "vn_mobile_age_gt_90d", "weight": 5, "last_updated": "2023-08-15", "test_coverage": 100 } ], "risk_thresholds": { "min_score_to_approve": 650, "max_debt_ratio": 0.5 } }这个JSON可直接喂给监管报送系统。更绝的是,脚本会自动检查/tests/unit/下对应规则的单元测试覆盖率——如果vn_mobile_age_gt_90d规则没写测试,test_coverage就标0,报告生成失败。这倒逼团队必须先写测试再上线规则,符合沙盒“可验证、可追溯”要求。
6.3 快速适配新市场:复制越南规则到印尼的3步法
想把越南方案迁到印尼?别重写,用源码的模板机制:
| 步骤 | 操作 | 文件路径 |
|---|---|---|
| 1. 复制规则模板 | cp config/rules/vn_2023.yaml config/rules/id_2023.yaml | /config/rules/id_2023.yaml |
| 2. 替换区域逻辑 | 把vn_mobile_age_gt_90d改成id_sim_activation_gt_30d,更新正则匹配SIM卡激活时间 | /config/rules/id_2023.yaml |
| 3. 注册新验证器 | 在/src/domain/validation/id_validator.py里加def validate_id_sim_date(date_str): ...,并在/src/application/credit_application.py的validate_applicant()里调用 | /src/domain/validation/id_validator.py |
做完这三步,IDApplicantFactory()就能生成印尼测试数据,ScoringEngine(rule_config="id_2023.yaml")就能跑通全流程。我试过,从越南到印尼的适配,2小时搞定,比读印尼央行PDF快10倍。
从那以后我每次接到新市场需求,第一件事不是写代码,而是打开/config/rules/目录,找一个最接近的现有规则YAML,用diff命令对比差异——90%的监管要求,其实只是把“身份证”换成“SIM卡”,把“90天”换成“30天”。希望帮到你。
本文还有配套的精品资源,点击获取