gcx/blockchain/genex-contracts/src/Redemption.sol

107 lines
3.5 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "./interfaces/ICoupon.sol";
import "./interfaces/ICompliance.sol";
/// @title Redemption — 兑付合约
/// @notice 消费者直接与发行方结算,平台不介入;兑付时销毁券
/// @dev 使用 Transparent Proxy 部署
contract Redemption is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
ICoupon public couponContract;
ICompliance public compliance;
// 兑付记录
struct RedemptionRecord {
uint256 tokenId;
address consumer;
address issuer;
bytes32 storeId;
uint256 redeemedAt;
}
uint256 public totalRedemptions;
mapping(uint256 => RedemptionRecord) public redemptions; // redemptionId → record
// --- Events ---
event CouponRedeemed(
uint256 indexed tokenId,
address indexed consumer,
address indexed issuer,
bytes32 storeId,
uint256 redemptionId
);
function initialize(
address _couponContract,
address _compliance,
address admin
) external initializer {
__AccessControl_init();
__ReentrancyGuard_init();
couponContract = ICoupon(_couponContract);
compliance = ICompliance(_compliance);
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(ADMIN_ROLE, admin);
}
/// @notice 消费者兑付券 — 销毁 NFT触发事件通知发行方
/// @param tokenId 券 token ID
/// @param storeId 门店 IDbytes32 哈希)
function redeem(uint256 tokenId, bytes32 storeId) external nonReentrant {
// 所有权验证
require(couponContract.ownerOf(tokenId) == msg.sender, "Redemption: not owner");
// 合规检查
require(!compliance.isBlacklisted(msg.sender), "Redemption: blacklisted");
ICoupon.CouponConfig memory config = couponContract.getConfig(tokenId);
// 到期验证
require(block.timestamp <= config.expiryDate, "Redemption: coupon expired");
// 门店限定验证
if (config.allowedStores.length > 0) {
require(_isAllowedStore(storeId, config.allowedStores), "Redemption: store not allowed");
}
// 销毁券burn
couponContract.burn(tokenId);
// 记录兑付
uint256 redemptionId = totalRedemptions++;
redemptions[redemptionId] = RedemptionRecord({
tokenId: tokenId,
consumer: msg.sender,
issuer: config.issuer,
storeId: storeId,
redeemedAt: block.timestamp
});
// 通知发行方event平台不介入消费数据
emit CouponRedeemed(tokenId, msg.sender, config.issuer, storeId, redemptionId);
}
/// @notice 查询兑付记录
function getRedemption(uint256 redemptionId) external view returns (RedemptionRecord memory) {
return redemptions[redemptionId];
}
// --- Internal ---
/// @dev 检查门店是否在允许列表中
function _isAllowedStore(bytes32 storeId, bytes32[] memory allowedStores) internal pure returns (bool) {
for (uint256 i = 0; i < allowedStores.length; i++) {
if (allowedStores[i] == storeId) return true;
}
return false;
}
}