PHP项目数字格林兰克朗与eDKK

wen PHP项目 1

本文目录导读:

PHP项目数字格林兰克朗与eDKK

  1. 核心架构原则
  2. 数据模型设计(MySQL/PostgreSQL 示例)
  3. PHP 金额处理核心类
  4. 核心业务逻辑:DKK 与 eDKK 之间的兑换与支付
  5. 与外部 eDKK 区块链/中心化网络的集成
  6. 需要注意的安全与合规问题
  7. 简化的文件结构建议

这是一个非常有意思且专业的问题,在PHP项目中处理 数字格林兰克朗(DKK)eDKK(电子克朗/数字货币克朗)时,核心挑战不在于PHP本身,而在于金融精度货币表示以及法定货币与数字货币的交互模型

由于当前(2025年)格林兰尚未推出独立的法定数字货币(eDKK仍是丹麦央行的试点或在格林兰的本地化方案),以下方案假设你正在构建一个支持 DKK(传统克朗)eDKK(本地电子克朗) 并行支付、结算与记账的系统。

核心架构原则

  1. 绝对避免浮点数:使用 stringint(最小单位:欧尔)表示金额。
  2. 统一存储层:在数据库中,将DKK与eDKK视为同一货币的两种“发行形式”,但账户余额分开记录。
  3. 汇率模型:理论上1 eDKK = 1 DKK(固定汇率1:1),但需处理兑换手续费、每日限额或机构间结算汇率差。

数据模型设计(MySQL/PostgreSQL 示例)

-- 账户表:支持多币种(DKK/eDKK)余额
CREATE TABLE accounts (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    currency_type ENUM('DKK', 'eDKK') NOT NULL,  -- 明确区分
    balance DECIMAL(19, 4) NOT NULL DEFAULT 0.0000, -- 精度到0.0001克朗(1/10欧尔)
    version INT UNSIGNED DEFAULT 1,               -- 乐观锁防并发
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_user_currency (user_id, currency_type)
);
-- 交易流水表:记录所有资金变动
CREATE TABLE transactions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    account_id BIGINT UNSIGNED NOT NULL,
    transaction_type ENUM('DEPOSIT', 'WITHDRAWAL', 'TRANSFER_IN', 'TRANSFER_OUT', 'EXCHANGE'),
    currency_type ENUM('DKK', 'eDKK') NOT NULL,
    amount DECIMAL(19, 4) NOT NULL,   -- 正数表示增加,负数表示减少
    fee DECIMAL(19, 4) NOT NULL DEFAULT 0.0000,
    balance_after DECIMAL(19, 4) NOT NULL, -- 交易后余额
    reference_id VARCHAR(64) NULL,   -- eDKK交易哈希或DKK银行流水号
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

PHP 金额处理核心类

<?php
declare(strict_types=1);
class DanishKrone
{
    private string $amount; // 始终以“欧尔(ører)”为单位存储整数(1 DKK = 100 øre)
    private string $currency; // 'DKK' 或 'eDKK'
    public function __construct(string $amount, string $currency = 'DKK')
    {
        // 输入格式:支持 "123.45" 或 "12345"(若仅整数)
        // 内部转为最小整数单位(øre)
        $this->amount = $this->normalizeToOre($amount);
        $this->currency = strtoupper($currency);
        if (!in_array($this->currency, ['DKK', 'EDKK'])) {
            throw new InvalidArgumentException("不支持的货币类型: {$currency}");
        }
    }
    // 将用户输入的金额转为欧尔(整数)
    private function normalizeToOre(string $input): string
    {
        // 处理小数点,最多保留2位小数(欧尔无更小单位)
        if (str_contains($input, '.')) {
            $parts = explode('.', $input);
            $integer = $parts[0];
            $decimal = str_pad(substr($parts[1], 0, 2), 2, '0');
            return bcmul("{$integer}.{$decimal}", '100', 0); // 乘以100
        }
        return bcmul($input, '100', 0);
    }
    // 显示为克朗(输出如 "123.45 DKK")
    public function display(): string
    {
        $krone = bcdiv($this->amount, '100', 2); // 除以100保留2位
        return $krone . ' ' . $this->currency;
    }
    // 加法(务必使用bcmath扩展)
    public function add(self $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new RuntimeException("不能直接相加不同货币类型!请先兑换。");
        }
        $newOre = bcadd($this->amount, $other->amount, 0);
        $new = clone $this;
        $new->amount = $newOre;
        return $new;
    }
    // 获取当前余额(用于数据库存储,将欧尔换算为DECIMAL(19,4)格式)
    public function getDecimalForDB(): string
    {
        return bcdiv($this->amount, '100', 4); // 精确到4位小数存入DB
    }
    // 静态工厂:从DB读取的余额(DECIMAL)创建对象
    public static function fromDB(string $decimalAmount, string $currency): self
    {
        $ore = bcmul($decimalAmount, '100', 0);
        $instance = new self('0', $currency);
        $instance->amount = $ore;
        return $instance;
    }
}

核心业务逻辑:DKK 与 eDKK 之间的兑换与支付

A. 用户内部兑换(1:1固定汇率 + 手续费)

<?php
class CurrencyExchangeService
{
    private float $exchangeFeeRate = 0.005; // 0.5% 手续费
    public function exchange(
        int $userId,
        string $fromCurrency, // 'DKK' 或 'eDKK'
        string $toCurrency,
        string $amountKrone   // 输入的是克朗数,如 "100.00"
    ): array {
        // 1. 起止金额验证
        $amount = new DanishKrone($amountKrone, $fromCurrency);
        $fee = new DanishKrone(bcmul($amountKrone, (string)$this->exchangeFeeRate, 4), $fromCurrency);
        $netAmount = $amount->subtract($fee); // 实际转出金额
        // 2. 数据库事务中执行(伪代码)
        DB::beginTransaction();
        try {
            // 扣除源账户
            $fromAccount = Account::lockForUpdate($userId, $fromCurrency);
            if ($fromAccount->balance < $amount->getDecimalForDB()) {
                throw new InsufficientBalanceException();
            }
            $fromAccount->decrement('balance', $amount->getDecimalForDB());
            // 增加目标账户(按净额)
            $toAccount = Account::lockForUpdate($userId, $toCurrency);
            $toAccount->increment('balance', $netAmount->getDecimalForDB());
            // 记录交易
            Transaction::create([
                'user_id' => $userId,
                'type' => 'EXCHANGE',
                'from_currency' => $fromCurrency,
                'to_currency' => $toCurrency,
                'gross_amount' => $amount->getDecimalForDB(),
                'fee' => $fee->getDecimalForDB(),
                'net_amount' => $netAmount->getDecimalForDB(),
            ]);
            DB::commit();
            return ['status' => 'success', 'net_received' => $netAmount->display()];
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }
}

B. 支付场景:用户选择用 eDKK 付款

<?php
class PaymentProcessor
{
    public function payWithEDKK(int $userId, string $totalAmountDKK): bool
    {
        $amount = new DanishKrone($totalAmountDKK, 'EDKK');
        // 1. 检查 eDKK 余额
        $eDKKAccount = Account::where(['user_id' => $userId, 'currency_type' => 'eDKK'])->first();
        if (!$eDKKAccount || $eDKKAccount->balance < $amount->getDecimalForDB()) {
            // 可选:自动从DKK兑换差额
            $this->autoTopUpFromDKK($userId, $amount);
            return false;
        }
        // 2. 扣除 eDKK 余额
        DB::transaction(function() use ($eDKKAccount, $amount, $userId) {
            $eDKKAccount->decrement('balance', $amount->getDecimalForDB());
            // 3. 向商户结算(假设商户只收DKK)
            // 此处需要将 eDKK 兑换为 DKK 再结算,或系统内部统一记账
            $merchantAccount = Account::where(['user_id' => $merchantId, 'currency_type' => 'DKK'])->first();
            $merchantAccount->increment('balance', $amount->getDecimalForDB()); // 即时兑换
            // 记录内部清算流水
        });
        return true;
    }
}

与外部 eDKK 区块链/中心化网络的集成

若 eDKK 运行在区块链或独立结算网络上,需在 PHP 中调用 SDK:

<?php
// 假设有一个 eDKK 钱包节点 API
class EDKKGateway
{
    private string $apiEndpoint = 'https://edkk.greenland.gl';
    private string $apiKey;
    // 发起链上转账
    public function sendTransaction(string $fromAddress, string $toAddress, string $amountOre): string
    {
        $response = Http::withHeaders([
            'X-API-Key' => $this->apiKey
        ])->post("{$this->apiEndpoint}/api/v1/transactions", [
            'from' => $fromAddress,
            'to'   => $toAddress,
            'value' => $amountOre, // 最小单位(欧尔)
        ]);
        return $response->json('tx_hash'); // 返回交易哈希
    }
    // 查询余额
    public function getBalance(string $address): string
    {
        $response = Http::get("{$this->apiEndpoint}/api/v1/addresses/{$address}/balance");
        return $response->json('balance'); // 返回欧尔数
    }
}

需要注意的安全与合规问题

问题领域 PHP 实现建议
精度 强制使用 bcmath 扩展,所有计算保留4位小数以上。
并发 账户余额更新使用 UPDATE ... WHERE version = ? 乐观锁 或 FOR UPDATE 悲观锁。
审计 所有余额变动必须产生不可篡改的流水记录。
合规 eDKK 可能受《数字货币法》监管,需记录用户KYC、交易限额、可疑交易上报接口。
测试 使用 PHPUnit + 模拟 bcmath 进行单元测试,特别注意边界值(0、负数、超大金额)。

简化的文件结构建议

project/
├── src/
│   ├── Money/
│   │   ├── DanishKrone.php        # 金额值对象
│   │   ├── CurrencyConverter.php  # DKK/eDKK兑换器
│   │   └── FeeCalculator.php      # 手续费计算
│   ├── Models/
│   │   ├── Account.php            # Eloquent/Doctrine模型
│   │   └── Transaction.php        # 流水模型
│   ├── Services/
│   │   ├── PaymentService.php     # 支付流程编排
│   │   └── EDKKBlockchain.php     # 外部eDKK集成
├── migrations/
│   └── create_accounts_table.php  # 迁移文件
└── tests/
    └── Money/
        └── DanishKroneTest.php    # 严格的单元测试
  • 数字格林兰克朗(DKK) 与 eDKK 在PHP中主要是会计分类问题,而非技术问题。
  • 使用 DECIMAL(19,4) 存储,bcmath 计算,事务保证一致性。
  • eDKK 是真正的区块链货币,增加一个中间层隔离内部账户与链上状态。
  • 当前(2025年)格林兰大概率仍在使用丹麦克朗(DKK),eDKK 若为当地数字货币,需关注是否与DKK完全等价(1:1锚定),否则需引入汇率浮动模型。

如果你能提供更具体的信息(eDKK 是完全去中心化的、还是央行中心化的、是否支持离链转账),我可以进一步优化代码结构。

抱歉,评论功能暂时关闭!