diff --git a/api/pay.php b/api/pay.php
index fb2b5bb..0fd3d8c 100644
--- a/api/pay.php
+++ b/api/pay.php
@@ -466,6 +466,14 @@ function handleNotify() {
$txIns->execute();
debugLog('[pay notify] callback同步成功', ['out_trade_no' => $outTradeNo, 'player' => $platformPlayer]);
+
+ // 发送付款凭据邮件(异步静默)
+ try {
+ sendPaymentReceiptEmail($platformPlayer, $outTradeNo, $platformResult['money'] ?? '0.01', $bonds, '在线充值', $now);
+ } catch (\Throwable $e) {
+ debugLog('[pay notify] callback同步邮件发送失败', ['player' => $platformPlayer, 'error' => $e->getMessage()]);
+ }
+
echo 'success';
exit;
}
@@ -508,6 +516,14 @@ function handleNotify() {
$stmt->execute();
debugLog('[pay notify] 充值交易已写入', ['player' => $row['player_name'], 'bonds' => $row['bond_amount'], 'out_trade_no' => $outTradeNo]);
+
+ // 7) 发送付款凭据邮件(异步静默,不影响主流程)
+ try {
+ sendPaymentReceiptEmail($row['player_name'], $outTradeNo, $row['money'], (int)$row['bond_amount'], $row['name'] ?? '在线充值', time());
+ } catch (\Throwable $e) {
+ debugLog('[pay notify] 邮件发送失败(不影响主流程)', ['player' => $row['player_name'], 'error' => $e->getMessage()]);
+ }
+
echo 'success';
exit;
}
@@ -663,6 +679,14 @@ function queryOrder($token) {
}
debugLog('[queryOrder] 平台同步成功', ['out_trade_no' => $outTradeNo, 'player' => $platformPlayer]);
+
+ // 发送付款凭据邮件(异步静默)
+ try {
+ sendPaymentReceiptEmail($platformPlayer, $outTradeNo, $money, $bonds, '在线充值', $now);
+ } catch (\Throwable $e) {
+ debugLog('[queryOrder] 平台同步邮件发送失败', ['player' => $platformPlayer, 'error' => $e->getMessage()]);
+ }
+
success([
'out_trade_no' => $outTradeNo,
'status' => 'paid',
@@ -720,6 +744,13 @@ function queryOrder($token) {
$status = 'paid';
debugLog('[queryOrder] 直查平台补单成功', ['out_trade_no' => $outTradeNo, 'player' => $info['player']]);
+
+ // 发送付款凭据邮件(异步静默)
+ try {
+ sendPaymentReceiptEmail($info['player'], $outTradeNo, $row['money'] ?? '0.01', (int)$row['bond_amount'], $row['name'] ?? '在线充值', $now);
+ } catch (\Throwable $e) {
+ debugLog('[queryOrder] 邮件发送失败(不影响主流程)', ['player' => $info['player'], 'error' => $e->getMessage()]);
+ }
} else {
// 检查是否已过期(5分钟)
$nowSec = time();
@@ -946,6 +977,88 @@ function parseExpireDate($expireStr) {
return ['valid' => false, 'timestamp' => 0, 'display' => '日期格式错误: ' . $expireStr];
}
+// ====================================================================
+// 邮件:支付成功后发送付款凭据邮件
+// ====================================================================
+
+/**
+ * 发送付款凭据邮件给玩家(仅在玩家绑定了邮箱时发送)
+ * @param string $playerName 玩家名
+ * @param string $outTradeNo 订单号
+ * @param string $money 付款金额
+ * @param int $bondAmount 到账债券
+ * @param string $productName 商品名称
+ * @param int $paidAt 支付时间(Unix时间戳)
+ */
+function sendPaymentReceiptEmail($playerName, $outTradeNo, $money, $bondAmount, $productName, $paidAt) {
+ // 1) 查询玩家邮箱
+ $db = getDB();
+ try {
+ $stmt = $db->prepare("SELECT email FROM users WHERE player_name = :player");
+ $stmt->bindValue(':player', $playerName, SQLITE3_TEXT);
+ $row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
+ } catch (\Throwable $e) {
+ debugLog('[sendPaymentReceiptEmail] 查询邮箱失败', ['player' => $playerName, 'error' => $e->getMessage()]);
+ return;
+ }
+
+ if (!$row || empty($row['email'])) {
+ debugLog('[sendPaymentReceiptEmail] 玩家未绑定邮箱,跳过', ['player' => $playerName]);
+ return;
+ }
+
+ $email = trim($row['email']);
+ // 简单邮箱格式校验
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ debugLog('[sendPaymentReceiptEmail] 邮箱格式无效', ['player' => $playerName, 'email' => $email]);
+ return;
+ }
+
+ // 2) 构建邮件内容
+ $paidTimeStr = date('Y-m-d H:i:s', $paidAt);
+ $baseUrl = getBaseUrl();
+ $orderLink = $baseUrl . '/player.php?page=orders';
+
+ $subject = '[Sdf1] 付款凭据 - 订单 ' . $outTradeNo;
+
+ $html = '
'
+ . ''
+ . '付款凭据'
+ . ''
+ . '
'
+ // 头部
+ . '
'
+ . '
✅ 付款成功
'
+ . '
Sdf1 Minecraft 充值中心
'
+ // 正文
+ . '
'
+ . '
亲爱的 ' . htmlspecialchars($playerName) . ':
'
+ . '
您的充值订单已支付成功,以下是您的付款凭据:
'
+ // 订单信息卡片
+ . '
'
+ . '
'
+ . '| 订单号 | ' . htmlspecialchars($outTradeNo) . ' |
'
+ . '| 商品 | ' . htmlspecialchars($productName) . ' |
'
+ . '| 付款金额 | ¥' . htmlspecialchars($money) . ' |
'
+ . '| 到账债券 | ' . intval($bondAmount) . ' 债券 |
'
+ . '| 支付时间 | ' . $paidTimeStr . ' |
'
+ . '
'
+ // 提示
+ . '
债券将在游戏内自动发放,如未到账请稍候或联系管理员。
'
+ . '
'
+ . '
本邮件由系统自动发送,请勿直接回复。如有疑问请联系管理员。
'
+ . '
';
+
+ // 3) 发送邮件
+ $headers = "From: " . SMTP_SENDER_NAME . " <" . SMTP_USER . ">\r\n"
+ . "Reply-To: " . SMTP_USER . "\r\n"
+ . "MIME-Version: 1.0\r\n"
+ . "Content-Type: text/html; charset=UTF-8\r\n";
+
+ smtpSendEmail(SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, $email, $subject, $html, $headers, SMTP_USE_SSL);
+ debugLog('[sendPaymentReceiptEmail] 邮件发送成功', ['player' => $playerName, 'email' => $email, 'out_trade_no' => $outTradeNo]);
+}
+
function getShopProducts() {
$db = getOrdersDB();
@@ -1019,6 +1132,42 @@ function getShopProducts() {
success(['products' => $products]);
}
+// ====================================================================
+// 玩家订单查询
+// ====================================================================
+
+function getPlayerOrders($token) {
+ $info = validateTokenSilent($token);
+ if (!$info || empty($info['player'])) {
+ error('登录状态无效', 401);
+ }
+ $player = $info['player'];
+
+ $db = getOrdersDB();
+ ensurePayOrdersTable($db);
+
+ // 查询该玩家的所有订单(最近50条)
+ $stmt = $db->prepare("SELECT out_trade_no, trade_no, player_name, money, bond_amount, status, name, created_at, paid_at FROM pay_orders WHERE player_name = :p ORDER BY created_at DESC LIMIT 50");
+ $stmt->bindValue(':p', $player, SQLITE3_TEXT);
+ $result = $stmt->execute();
+
+ $orders = [];
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
+ $orders[] = [
+ 'out_trade_no' => $row['out_trade_no'],
+ 'trade_no' => $row['trade_no'] ?? '',
+ 'money' => $row['money'],
+ 'bond_amount' => (int)$row['bond_amount'],
+ 'status' => $row['status'],
+ 'name' => $row['name'] ?? '在线充值',
+ 'created_at' => (int)$row['created_at'],
+ 'paid_at' => $row['paid_at'] ? (int)$row['paid_at'] : null,
+ ];
+ }
+
+ success(['orders' => $orders, 'player' => $player]);
+}
+
// ====================================================================
// 入口分发
// ====================================================================
@@ -1046,6 +1195,9 @@ try {
case 'get_shop_products':
getShopProducts();
break;
+ case 'my_orders':
+ getPlayerOrders(getParam('token'));
+ break;
default:
error('未知操作: ' . $action);
}
diff --git a/player.php b/player.php
index f915963..d4adb1c 100644
--- a/player.php
+++ b/player.php
@@ -277,6 +277,7 @@ if ($currentVersion !== $BUILD_VERSION) {
+
@@ -1052,6 +1053,7 @@ if ($currentVersion !== $BUILD_VERSION) {
else if (page === 'account') renderAccount(c);
else if (page === 'ticket') renderTicket(c);
else if (page === 'recharge') renderRecharge(c);
+ else if (page === 'orders') renderMyOrders(c);
}
// ==================== 债券在线充值(动态加载商品) ====================
@@ -1472,6 +1474,93 @@ if ($currentVersion !== $BUILD_VERSION) {
}
}
+ // ==================== 我的订单 ====================
+ async function renderMyOrders(el) {
+ el.innerHTML = '';
+
+ try {
+ const res = await api('pay.php', {action: 'my_orders'});
+ if (!res.success || !res.data || !res.data.orders) {
+ el.innerHTML = '加载失败: ' + (res.message || '未知错误') + '
';
+ return;
+ }
+
+ const orders = res.data.orders;
+ if (orders.length === 0) {
+ el.innerHTML = `
+
+
📋 我的订单
+
查看您的在线充值订单记录。
+
+
📭
+
暂无订单记录
+
前往「在线充值」发起第一笔订单
+
+
+ `;
+ return;
+ }
+
+ // 状态标签样式
+ const statusMap = {
+ 'paid': { text: '✅ 已支付', color: '#1a7f37', bg: '#dafbe1' },
+ 'created': { text: '⏳ 待支付', color: '#9a6700', bg: '#fff8c5' },
+ 'expired': { text: '❌ 已过期', color: '#656d76', bg: '#f6f8fa' },
+ 'failed': { text: '⚠️ 失败', color: '#cf222e', bg: '#ffebe9' },
+ };
+
+ let html = `
+
+
+
+
📋 我的订单
+
共 ${orders.length} 条记录
+
+
+
+ `;
+
+ orders.forEach(order => {
+ const st = statusMap[order.status] || { text: order.status, color: '#656d76', bg: '#f6f8fa' };
+ const createdTime = order.created_at ? new Date(order.created_at * 1000).toLocaleString('zh-CN', {month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}) : '-';
+ const paidTime = order.paid_at ? new Date(order.paid_at * 1000).toLocaleString('zh-CN', {month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}) : '-';
+
+ html += `
+
+
+
+
${escHtml(order.name)}
+
${escHtml(order.out_trade_no)}
+
+
${st.text}
+
+
+
+ 创建: ${createdTime}
+ ${order.paid_at ? '支付: ' + paidTime + '' : ''}
+
+
+
¥${escHtml(order.money)}
+
→ ${order.bond_amount} 债券
+
+
+
+ `;
+ });
+
+ html += '
';
+ el.innerHTML = html;
+
+ } catch (e) {
+ el.innerHTML = '';
+ }
+ }
+
+ function escHtml(s) {
+ if (!s) return '';
+ return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');
+ }
+
// ===== 商城(分类标签页) =====
let shopItems = [];
let shopCategories = [];