本文目录导读:

在PHP项目中处理巴基斯坦卢比(PKR) 与ePKR(数字巴基斯坦卢比,巴基斯坦央行发行的央行数字货币)时,主要涉及货币格式化、金额处理、汇率换算(如果涉及兑换)以及存储设计。
以下是针对PHP项目的完整实践指南:
理解 PKR 与 ePKR 的关系
- PKR:传统法定货币,物理硬币和纸币。
- ePKR:数字形式的法定货币,1 ePKR = 1 PKR(固定汇率,无波动)。
- 核心逻辑:在系统内部,它们本质上是同一种价值单位,但在显示和交易类型上可以区分为“传统现金”和“数字现金”。
PHP中的金额处理(核心)
PHP 浮点数存在精度问题,所有货币金额必须使用整数(分/派萨)或字符串/BCMath库处理。
推荐方案:以最小的货币单位(派萨,Paisa)存储和计算
- 1 PKR = 100 Paisa
- 在数据库中存储为
BIGINT或DECIMAL(12,2)(但内部计算用整数)。
示例:使用 BCMath 处理 ePKR 与 PKR 转换
<?php
/**
* PKR与ePKR的精确处理类
*/
class CurrencyHandler
{
// 将金额转换为最小单位 (Paisa)
public static function toPaisa(string $amount): string
{
// 假设输入是 "100.50" 字符串
$parts = explode('.', $amount);
$rupees = $parts[0] ?? '0';
$paisa = $parts[1] ?? '00';
$paisa = str_pad(substr($paisa, 0, 2), 2, '0');
return bcmul($rupees, '100', 0) + $paisa;
}
// 从Paisa转换为显示金额 (PKR)
public static function fromPaisa(string $paisa): string
{
$rupees = bcdiv($paisa, '100', 2);
return $rupees; // 返回 "100.50"
}
// 格式化显示 (带卢比符号)
public static function formatPKR(string $amount, string $type = 'PKR'): string
{
$formatted = number_format((float)$amount, 2, '.', ',');
if ($type === 'ePKR') {
return "ePKR {$formatted}";
}
return "₨ {$formatted}";
}
}
// 使用示例
$paisa = CurrencyHandler::toPaisa('1500.75'); // 150075
$display = CurrencyHandler::fromPaisa($paisa); // "1500.75"
echo CurrencyHandler::formatPKR($display, 'ePKR'); // "ePKR 1,500.75"
数据库设计(多币种支持)
如果只处理PKR/ePKR(1:1),可以简化:
CREATE TABLE transactions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
amount_paisa BIGINT NOT NULL COMMENT '金额,单位:派萨',
currency_type ENUM('PKR', 'ePKR') NOT NULL,
balance_type ENUM('cash', 'digital') DEFAULT 'cash',
balance_before BIGINT DEFAULT 0,
balance_after BIGINT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
注意:amount_paisa 存储整数,避免浮点误差,若需要存储多种法定货币(USD/PKR),建议使用 DECIMAL(20, 8) 并加上 currency_code 字段。
汇率处理(如果涉及“数字钱包”间转换)
虽然 PKR 与 ePKR 汇率固定为 1:1,但实际业务中可能需要:
- 提现:ePKR -> 传统PKR(需要保证金或清算逻辑,费率可以为零)。
- 充值:传统PKR -> ePKR。
建议使用服务层:
class CurrencyConversionService
{
/**
* 将物理PKR转换为ePKR (1:1 固定)
*/
public function convertPKRtoePKR(int $userID, string $amount): bool
{
// 1. 验证用户物理钱包余额
// 2. 扣除物理钱包金额 (amount_paisa)
// 3. 增加数字钱包金额 (amount_paisa)
// 4. 记录双向交易记录
}
}
前端显示与本地化
- 使用
Intl.NumberFormat(JavaScript)或number_format(PHP)进行本地化格式。 - 巴基斯坦格式:分组为 (逗号)分隔,每3位一组(与印度不同,注意:巴基斯坦通常使用英式千分位)。
- 示例:
1,234,567.89而不是12,34,567。
- 示例:
- 货币符号: 或
Rs.,编码用UTF-8(如\u20A8)。
PHP端格式化示例(已兼容):
echo 'RS. ' . number_format(1234567.89, 2, '.', ',') . ' PKR'; // 输出: RS. 1,234,567.89 PKR
安全性注意事项
- 固定汇率陷阱:即使1 ePKR = 1 PKR,建议在代码中不要硬编码,而是从配置文件或数据库读取,方便未来升级(虽然央行通常不会变动)。
- 防重放攻击:ePKR相关API需要添加
nonce(随机数)和timestamp(时间戳)。 - 日志审计:所有PKR/ePKR转换必须记录操作前余额和操作后余额。
完整代码示例(交易记录与余额更新)
<?php
class TransactionService
{
private $db; // PDO实例
public function transferPKR2ePKR(int $userId, string $amountPaisa): bool
{
$this->db->beginTransaction();
try {
// 1. 获取用户物理钱包余额
$stmt = $this->db->prepare("SELECT balance_paisa FROM wallets WHERE user_id = ? AND type = 'physical' FOR UPDATE");
$stmt->execute([$userId]);
$physicalBalance = $stmt->fetchColumn();
if (bccomp($physicalBalance, $amountPaisa, 0) < 0) {
throw new Exception("Insufficient physical PKR balance.");
}
// 2. 扣减物理钱包
$newPhysical = bcsub($physicalBalance, $amountPaisa, 0);
$this->db->prepare("UPDATE wallets SET balance_paisa = ? WHERE user_id = ? AND type = 'physical'")
->execute([$newPhysical, $userId]);
// 3. 增加数字钱包
$this->db->prepare("UPDATE wallets SET balance_paisa = balance_paisa + ? WHERE user_id = ? AND type = 'digital'")
->execute([$amountPaisa, $userId]);
// 4. 记录交易
$stmt = $this->db->prepare("INSERT INTO transactions(user_id, type, amount_paisa, from_type, to_type, balance_before, balance_after)
VALUES (?, 'conversion', ?, 'PKR', 'ePKR', ?, ?)");
$stmt->execute([$userId, $amountPaisa, $physicalBalance, $newPhysical]);
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
}
- 核心法则:PHP处理任何货币金额,永远使用整数(派萨) + BCMath。
- PKR/ePKR关系:价值固定1:1,但作为不同资产类型管理(
type字段区分)。 - 存储:金额字段
BIGINT,类型字段ENUM。 - 显示:
number_format+ 符号或ePKR前缀。
如果你需要处理实时汇率(例如ePKR兑换其他数字货币,如USDT),则需要接入第三方外汇API(如OpenExchangeRates),但基于巴基斯坦央行规定,ePKR与PKR之间是严格1:1。