本文目录导读:

这是一个非常具体的需求,PHP 项目中处理 数字毛里求斯卢比(MUR) 与 eMUR(e-MUR,数字版本或电子版本的毛里求斯卢比)。
由于毛里求斯银行(Bank of Mauritius)目前仍在推进其 CBDC(央行数字货币,即数字卢比)的试点与落地,市面上并没有一个“eMUR”的公开汇率或专用API,它通常被视为 MUR 的一种数字形式,在金融结算中通常与 MUR 等值。
在 PHP 项目中处理这两者,核心挑战在于数据建模、汇率处理(通常是1:1固定或浮动) 以及前端展示。
以下是针对该需求的完整解决方案:
核心假设与业务规则
- 关系:1 eMUR = 1 MUR,数字卢比不是另一个币种,而是 MUR 的电子形式,通常结算价值相等。
- 存管:同一个货币代码“MUR”下,需要区分是“传统账户余额”还是“eMUR 数字钱包余额”。
- 前端显示:需要显示符号
Rs或 ,同时标识出是电子货币(例如加上“e-”前缀或不同的颜色/图标)。
数据模型设计(PHP + MySQL/SQL)
在数据库层面,建议将 eMUR 视为 MUR 的一个子类型或来源字段。
方案 A:单表加类型字段(推荐最简化)
CREATE TABLE `wallets` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`currency_type` ENUM('MUR', 'eMUR') NOT NULL DEFAULT 'MUR',
`balance` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `user_currency` (`user_id`, `currency_type`)
);
- 说明:同一个用户既有 MUR 余额,也有 eMUR 余额。
- PHP 查询:
// 获取用户所有余额 $query = "SELECT currency_type, balance FROM wallets WHERE user_id = ?";
方案 B:双币种合一表 + 卡片字段(基于 JSON 或两个字段)
CREATE TABLE `accounts` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`balance_mur` DECIMAL(15, 2) DEFAULT 0.00,
`balance_emur` DECIMAL(15, 2) DEFAULT 0.00,
`emur_to_mur_rate` DECIMAL(8, 4) DEFAULT 1.0000, -- 预留灵活性
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
- 优势:便于在一个查询里获取所有余额,无需 JOIN。
PHP 金额处理类(面向对象)
<?php
class Money
{
private string $amount; // 使用 string 避免浮点数精度问题
private string $currency; // 'MUR' 或 'eMUR'
public function __construct(string $amount, string $currency = 'MUR')
{
if (!in_array($currency, ['MUR', 'eMUR'])) {
throw new InvalidArgumentException("Unsupported currency type: {$currency}");
}
$this->amount = $amount;
$this->currency = $currency;
}
// 核心转换:eMUR 转 MUR
public function toMur(): self
{
if ($this->currency === 'MUR') {
return $this;
}
// 假设 1 eMUR = 1 MUR
return new self($this->amount, 'MUR');
}
// 转换回 eMUR
public function toEmur(): self
{
if ($this->currency === 'eMUR') {
return $this;
}
return new self($this->amount, 'eMUR');
}
// 金额相加(自动处理类型)
public function add(Money $other): self
{
// 先统一转为 MUR 相加
$total = bcadd($this->toMur()->getAmount(), $other->toMur()->getAmount(), 2);
// 结果货币类型按原对象
return new self($total, $this->currency);
}
// 格式化显示(带符号)
public function format(): string
{
$symbol = 'Rs ';
if ($this->currency === 'eMUR') {
$symbol = 'e' . $symbol; // 显示 eRs 100.00
}
return $symbol . number_format($this->amount, 2);
}
public function getAmount(): string
{
return $this->amount;
}
public function getCurrency(): string
{
return $this->currency;
}
}
// 使用示例
$murMoney = new Money('500.00', 'MUR');
$emurMoney = new Money('150.75', 'eMUR');
echo $murMoney->format(); // Rs 500.00
echo $emurMoney->format(); // eRs 150.75
$total = $murMoney->add($emurMoney);
echo $total->format(); // Rs 650.75 (默认使用第一个对象的货币类型)
汇率与转换服务
如果未来 eMUR 与 MUR 不再 1:1,或者你正在开发一个交易系统。
<?php
class CurrencyConverter
{
private array $rates;
public function __construct()
{
// 从数据库或外部 API 加载汇率
// 例: 1 eMUR = 1.0023 MUR (仅示例)
$this->rates = [
'MUR_to_eMUR' => 1.0000,
'eMUR_to_MUR' => 1.0000, // 通常相等
];
}
public function convert(Money $money, string $targetCurrency): Money
{
if ($money->getCurrency() === $targetCurrency) {
return $money;
}
$rateKey = $money->getCurrency() . '_to_' . $targetCurrency;
if (!isset($this->rates[$rateKey])) {
throw new Exception("Conversion rate not found");
}
$convertedAmount = bcmul($money->getAmount(), (string) $this->rates[$rateKey], 2);
return new Money($convertedAmount, $targetCurrency);
}
}
// 使用示例
$converter = new CurrencyConverter();
$emur = new Money('100', 'eMUR');
$mur = $converter->convert($emur, 'MUR');
echo $mur->format(); // Rs 100.00
支付与转账逻辑(避免混用)
在支付交易中,必须严格指定使用哪种余额(MUR 或 eMUR)。
<?php
function processPayment(int $userId, Money $paymentAmount, string $sourceType)
{
// 1. 检查余额
// 2. 如果是 eMUR 支付,只从 eMUR 余额扣减
// 3. 如果是 MUR 支付,只从 MUR 余额扣减
$db = getDbConnection();
// 原子操作:从特定类型钱包扣减
$sql = "UPDATE wallets
SET balance = balance - ?
WHERE user_id = ?
AND currency_type = ?
AND balance >= ?";
$stmt = $db->prepare($sql);
$stmt->execute([
$paymentAmount->getAmount(),
$userId,
$paymentAmount->getCurrency(),
$paymentAmount->getAmount()
]);
if ($stmt->rowCount() === 0) {
throw new Exception("Insufficient funds or wallet not found");
}
}
前端显示(HTML + Blade / Twig)
如果你使用模板引擎:
// 在控制器中
$userWallet = [
'mur_balance' => 1250.50,
'emur_balance' => 300.00,
];
Blade 模板示例:
<div class="wallet">
<div class="balance-item">
<span class="currency-label">MUR 余额</span>
<span class="amount">Rs {{ number_format($userWallet['mur_balance'], 2) }}</span>
</div>
<div class="balance-item">
<span class="currency-label e">eMUR 余额</span>
<span class="amount e">eRs {{ number_format($userWallet['emur_balance'], 2) }}</span>
</div>
</div>
<style>
.currency-label.e { color: #1a73e8; }
.amount.e { background: #e8f0fe; padding: 2px 8px; border-radius: 4px; }
</style>
总结建议
| 场景 | 推荐做法 |
|---|---|
| 简单钱包 | 使用 currency_type 字段区分 MUR / eMUR |
| Excel/报告导出 | 先转换为统一 MUR,再显示符号 |
| 与外部系统对接 | 一律传输 MUR 数值,在备注中注明 eMUR |
| API 设计 | currency 字段传 MUR 或 eMUR,amount 只需数字 |
| 精度 | 使用 BCMath 或 Decimal 类,避免 float |
如果你需要对接毛里求斯银行实际发行的数字卢比 API(BOM数字卢比试点),目前的公开资料较少,通常需要企业身份申请接口,市面上的主流支付网关(如 MyT Pos、Jubilee)暂未公开支持直接扣减 eMUR。
建议:在 PHP 项目中将 eMUR 作为 MUR 的一个“标签”或“钱包类型”处理,保持金额逻辑一致,前端做差异化展示,这是目前成本最低、最稳定的实现方式。