ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

发卡源码实战:FastAdmin二开、USDT支付与宝塔部署避坑指南

发卡源码实战:FastAdmin二开、USDT支付与宝塔部署避坑指南 简介这是一份面向发卡系统搭建者的最新界面源码包支持多语言切换并集成多个主流钱包支付接口内含泰达币转账支付源码适合有一定后端基础的开发者学习参考。整体压缩包约三十九点三九兆共两千个文件其中以JavaScript、HTML、CSS等前端资源为主辅以PHP后端逻辑、JSON配置、Markdown说明文档及SQL数据库脚本文件类型与用途对应明确目录结构清晰便于按功能模块检索。目前已有四百余人学习浏览可作为构建钱包支付发卡平台的界面参考与功能借鉴。包内附带搭建教程覆盖Linux宝塔环境下Nginx、MySQL、PHP的部署思路帮助理解站点配置与钱包授权流程源码需自行检查后门并建议通过智能合约地址完成授权仅用于学习与美工研究切勿用于商业或非法用途。1. 发卡源码到底值不值得装先说结论再看套路这套号称“最新 UI”的发卡源码真正值钱的不是那几张界面截图而是支付和授权这两层逻辑。我的结论很直接把它当成一个基于 FastAdmin 的二开 Demo 来用前台卖货、后台管货接 TRC20/ERC20/BEP20 的 USDT 收款还带多语言切换。对只想快速搭一个自动发货站点、又不想从零写前后端的同学它能省掉不少基础工作但如果想直接商用、直接挂主网地址收钱那得先把后门自查、授权校验和支付回调这三件事摸清楚否则跑着跑着静默掉单甚至后台密码被改都是常见事故。下面按“拆结构 → 搭环境 → 接通支付 → 排查 → 验证”的顺序来拆。2. 源码结构与多语言机制从 CSS 清单认出 FastAdmin 骨架2.1 文件清单先读懂哪些是壳哪些是核心拿到压缩包第一件事不是急着上传跑安装而是把文件列表整体过一遍。这份资源附带的样式文件清单基本把底牌亮出来了pimple.css有些渠道打包时把文件名截成了pimple.c本质是站点整体样式命名风格偏向自写主题index.css前台首页样式通常和上一份有大量重复定义weui.css微信系前端组件库常用于移动端下单页、订单详情页bootstrap.css和bootstrap.min.css全局基础框架同时出现两份是这类打包源码的通病说明模板引用了重复依赖backend.min.css、frontend.min.css后台、前台的压缩产物bui.css看到它基本可以判定后台用的是 FastAdmin 的界面组件库fastadmin.css框架级样式跟着 FastAdmin 的路由结构加载看到这组文件我的判断是后台界面直接复用 FastAdmin 默认框架真正花过心思的部分是前台首页和下单页。同时塞两份 bootstrap 会让首屏多出几十 KB样式覆盖顺序也乱正式上线前建议只保留一份再按需压缩。样式文件里也能藏东西常见做法是把上报地址写成背景图url()或者把混淆后的字符串塞进 CSS 注释浏览器加载页面时照样会请求那个外部地址等于在不知情的情况下给外部发了一台“基站”信号。我一般会在部署前先跑一遍全局检索cd /www/wwwroot/你的站点目录 grep -rn url(http --include*.css . grep -rn url(// --include*.css . grep -rn base64 --include*.css .注意这里查的不是简单的“有没有外部链接”而是“有没有不明身份的外部链接”。正常模板引字体、引图标库很常见但如果某个 CSS 文件里有url(//某未知域名/collect.php)这种地址基本可以判定是数据回传点。这一步花十分钟能省掉后面很多麻烦。2.2 多语言切换语言包结构、缓存与清除时机FastAdmin 的多语言基于 ThinkPHP 的language机制语言包放在application/index/lang/或application/common/lang/目录下每个语言一个 PHP 文件返回键值对数组。前台控制器里用__(键名)输出翻译文本切换语言时框架根据 Cookie 或 URL 参数选择对应语言包。这套源码里的多语言实现基本沿用这个结构。以中英双语为例语言包大致长这样// application/index/lang/zh-cn.php return [ order_create_success 订单创建成功请扫码支付, pay_expired 支付链接已过期请重新下单, wallet_format_error 钱包地址格式不正确请检查链类型, card_send_failed 发卡失败请联系客服, ];// application/index/lang/en.php return [ order_create_success Order created, please scan to pay, pay_expired Payment link expired, please re-order, wallet_format_error Invalid wallet address, check chain type, card_send_failed Failed to deliver card, contact support, ];控制器里的用法是__(order_create_success)框架会按当前语言环境自动取出对应文案。这里有两个容易翻车的地方。第一语言包文件必须存成 UTF-8 无 BOM。用 Windows 记事本改过文件再传上去大概率带 BOMPHP 解析时会在输出里混入不可见字符表现为页面顶部多了几个空格、接口返回 JSON 时 Status 异常。我自己的排查习惯上传完语言包后用head -c 3看一眼文件开头的字节正常是?p如果出现EF BB BF前缀说明带 BOM用转换命令处理find /www/wwwroot/你的站点/application -name *.php -exec sed -i 1s/^\xEF\xBB\xBF// {} \;第二语言包改了不生效。FastAdmin 的语言包有缓存机制后台“常规管理 → 清除缓存”可以手动清命令行下直接删runtime目录也行。改语言包后没清缓存前端永远拿到旧文案这时候不要怀疑代码写错先清缓存再刷新。2.3 多钱包支付抽象为什么说“多钱包”不等于“多地址”标题写“多个主流钱包”很多人的第一反应是后台配一串钱包地址。实际上这套源码的钱包层是按链抽象的也就是每种链有自己的收单地址、合约地址、精度参数和监听适配器。常见支持三种 USDT 收单链TRC20、ERC20、BEP20它们的地址格式完全不一样精度也不同。设计上通常用一个配置文件统一管理// config/wallet.php return [ default TRC20, chains [ TRC20 [ coin USDT, decimals 6, currency USDT, api_driver trongrid, ], ERC20 [ coin USDT, decimals 6, currency USDT, api_driver etherscan, ], BEP20 [ coin USDT, decimals 18, currency USDT, api_driver bscscan, ], ], ];注意decimals这个参数它直接决定金额比较时要不要做精度换算。TRC20 的 USDT 是 6 位精度BEP20 的 USDT 是 18 位精度链上返回的数值是带精度的最小单位字符串如果所有链都用同一套比较逻辑BEP20 的订单永远对不上账。这个配置的意义就是把差异收口到一层上游只认订单里的chain、amount、to_address三个字段。订单表的关键字段一般是这样设计的字段说明典型值order_sn订单号前端展示用20250101xxxxchain收单链类型TRC20/ERC20/BEP20pay_amount应付 USDT 数量100.500000to_address本次收单的钱包地址以T开头的 34 位地址tx_hash链上交易哈希2b3a...status0 待支付 / 1 已支付 / 2 已发卡0把这层抽象做在前面后面接监听回调时就不用为每条链单独写一套流程了。3. 宝塔部署全流程Nginx 1.22.1 MySQL 8.0 PHP 7.4 的配平实战3.1 环境选择这个版本组合为什么合理摘要里给出的运行环境是 Linux 宝塔面板 Nginx 1.22.1 MySQL 8.0 PHP 7.4 phpMyAdmin 5.2。这个组合不是随便选的原因有三条。PHP 7.4 对 FastAdmin 以及它底层依赖的 ThinkPHP 5.x 兼容性最好PHP 8.0 及以上版本对老代码里的一些隐式类型转换更严格很多函数在 8.1 下直接报 Deprecated 或 TypeError二手源码通常没精力适配新版所以 7.4 是这套系统里最不折腾的选择。MySQL 8.0 是宝塔当前的默认版本支持 utf8mb4 排序规则发卡系统里存的卡密、订单备注往往带特殊符号utf8mb4 才能完整存下。Nginx 1.22.1 属于长期维护分支站点并发能力在线配合 PHP-FPM 足够跑这类业务。需要注意的是 PHP 7.4 连 MySQL 8.0 存在一个经典认证问题后面第 5 章会专门展开。现在先按正常流程装。3.2 站点创建与伪静态Nginx 配置和 PATH_INFO在宝塔面板里新建站点PHP 版本选 7.4数据库选 MySQL 8.0不要勾选“自动创建数据库”后面手动建库更可控。站点创建完成后源码上传到站点根目录运行目录改成public或web目录具体看压缩包里的入口文件在哪。FastAdmin 的入口结构决定了 Nginx 必须支持 PATH_INFO否则访问/index.php/admin这类路由会直接 404。在站点设置里打开伪静态配置填入以下内容server { listen 80; server_name 你的域名; root /www/wwwroot/你的站点/web/; index index.php index.html; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } } location ~ \.php(.*)$ { fastcgi_pass unix:/tmp/php-cgi-74.sock; fastcgi_index index.php; include fastcgi.conf; fastcgi_param PATH_INFO $1; } location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|css|js)$ { expires 30d; access_log off; } }这段配置里最关键的是fastcgi_param PATH_INFO $1;这一行。ThinkPHP 的路由依赖 PATH_INFO 来解析/index.php/模块/控制器/操作这种 URL少了它后台登录页能进但点任何二级菜单都会白屏。静态资源加expires 30d是为了让 CSS、JS 走浏览器缓存减少服务器压力开发阶段如果改了样式不生效先清浏览器缓存再排查。上传完源码后先把目录权限理顺常见做法是这样cd /www/wwwroot/你的站点 find . -type d -exec chmod 755 {} \; find . -type f -exec chmod 644 {} \; chmod -R 777 web/runtime web/upload runtime 2/dev/nullchmod -R 777只给 runtime 和 upload 这种需要写入的目录其他目录保持 755/644避免木马利用目录权限写入恶意文件。这一步很关键二手源码最容易出问题的地方就是给了根目录写权限。3.3 数据库创建、配置与安装在宝塔的 phpMyAdmin 里手动建库建用户用 MySQL 8.0 时注意认证插件要指定成兼容 PHP 7.4 的格式CREATE DATABASE IF NOT EXISTS fakaim DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER fakauser127.0.0.1 IDENTIFIED WITH mysql_native_password BY 替换成强密码; GRANT ALL PRIVILEGES ON fakaim.* TO fakauser127.0.0.1; FLUSH PRIVILEGES;IDENTIFIED WITH mysql_native_password这个子句是重点。MySQL 8.0 默认用的caching_sha2_password认证插件PHP 7.4 自带的 mysqlnd 驱动如果版本偏旧可能连不上报错SQLSTATE[HY000] [2054]。显式建一个mysql_native_password用户是搭 PHP 7.4 连 MySQL 8.0 最省事的解法。库建好后找到压缩包里的 SQL 文件在 phpMyAdmin 里导入。然后打开站点根目录下的.env文件重点改这几项APP_DEBUG false [DATABASE] TYPE mysql HOSTNAME 127.0.0.1 DATABASE fakaim USERNAME fakauser PASSWORD 你的强密码 HOSTPORT 3306 CHARSET utf8mb4 DEBUG falseCHARSET必须写成utf8mb4小写也可以但不能写成utf8否则订单备注里的表情符号入库会变成问号。数据库配置改完后访问http://你的域名/index.php/install进入安装流程按提示填管理员账号和后台入口路径。安装完成后务必把install目录删掉或改名不然别人可以通过安装脚本重置管理员密码。4. USDT 支付与授权校验TRC20 回调、签名验签和合约授权4.1 USDT 收单流程下单、锁价、展示收款地址这套源码的收单逻辑走的是标准发卡流程用户选商品 → 创建订单 → 返回一段成链的收款地址和应付金额 → 用户转账 → 后台监听确认到账 → 自动发卡。整个链路里最容易被忽视的是“锁价”这一步。USDT 这类稳定币价格也会有波动用户在页面停留时间长了订单金额按实时汇率算会导致支付金额对不上。我一般会在创建订单那一刻把当时的汇率写进订单表后续只按订单里记录的汇率计算应付金额避免页面刷新后金额变来变去。参考做法// 下单时锁定汇率并写入订单 $rate Cache::get(usdt_cny_rate); if (!$rate) { $rate fetchRateFromApi(); // 封装好的行情接口只取 USDT/CNY Cache::set(usdt_cny_rate, $rate, 300); } // 商品价格单位是元换算成 USDT 数量保留 6 位小数 $payAmount ceil($goodsPrice / $rate * 1e6) / 1e6; Db::name(order)-insert([ order_sn date(YmdHis) . rand(1000, 9999), goods_id $goodsId, chain TRC20, pay_amount $payAmount, status 0, ]);ceil($goodsPrice / $rate * 1e6) / 1e6这个写法是为了防止出现0.30000000000004这种浮点尾差。金额比较在链上回调环节是严格等于或大于判断尾差不处理的话用户转对了钱也可能判成金额不匹配属于那种容易耗上半小时的玄学问题。下单完成后前端拿到chain、pay_amount、to_address生成收款二维码展示给用户。生成二维码不需要后端参与前端用现成的 qrcode 库按to_address和pay_amount拼字符串就能出图现金额会让用户的钱包自动带上金额省掉手动输入的麻烦。4.2 链上监听与回调验签确认数、精度和金额对比订单创建后剩下的问题是如何确认“用户真的转进来了”。这套源码里的监听方式常见是轮询也就是用计划任务每隔一段时间去扫区块链浏览器 API查收款地址有没有新交易。TRC20 的监听脚本大致是这个结构// 轮询脚本建议每 30 秒执行一次 $pendingOrders Db::name(order) -where(status, 0) -where(chain, TRC20) -where(create_time, , time() - 1800) // 只处理 30 分钟内的订单 -select(); foreach ($pendingOrders as $order) { $transactions queryTronGrid(api/v1/accounts/ . $order[to_address] . /transactions/trc20); foreach ($transactions as $tx) { // token 符号与金额必须匹配 if ($tx[token_symbol] ! USDT) continue; if ($tx[to_address] ! $order[to_address]) continue; // 链上返回的是按精度放大的字符串需要转成小数 $realAmount $tx[value] / 1e6; if (bccomp((string)$realAmount, (string)$order[pay_amount], 6) 0) continue; // 确认数不足时跳过防止网络重组导致交易回滚 if ($tx[confirmations] 19) continue; markOrderPaid($order[id], $tx[hash]); $order[tx_hash] $tx[hash]; Db::name(order)-update($order); break 2; } }这里有几个容易踩的细节value字段是字符串直接除1e6得到的是浮点数浮点数比较不可靠所以用bccomp比较精度传 6避免尾差误判confirmations阈值建议不低于 19也就是大约一分钟的链上确认时间防的是区块重组每笔订单成功后就break 2跳出双重循环防止一笔交易被多笔订单重复消费。回调验签是另一个关键点。如果这套源码的支付网关支持异步通知那接收通知的接口必须做签名校验否则页面 URL 被人拼一下就能伪装成“已到账”。常见验签流程是把参数排序拼接后加上密钥做 md5// 支付回调通知接收接口 $params $_POST; $sign $params[sign] ?? ; unset($params[sign]); ksort($params); $str urldecode(http_build_query($params)) . $config[pay_secret]; if (md5($str) $sign) { // 验签通过按订单号查单并标记支付 $order Db::name(order)-where(order_sn, $params[order_sn])-find(); if ($order $order[status] 0) { Db::name(order)-where(id, $order[id])-update([status 1]); } } else { // 验签失败写日志不落单 Log::write(回调验签失败: . json_encode($params)); }ksort的目的是让两边的签名拼接顺序一致任何一方改变参数顺序都会导致签名不同。urldecode是为了处理参数值里带特殊字符的情况。真正上线时还要对回调来源 IP 做白名单只接受网关服务器 IP 的请求这一步可以过滤掉绝大多数伪造回调。4.3 授权机制再往前一步把授权校验搬到智能合约地址摘要里特别提到“自行检查后门最好是部署智能合约后用合约地址来授权”这句话在二手源码场景里非常实在。很多渠道流出的源码自带远程校验什么“授权服务器”“激活接口”本质是作者留了一道远程开关他随时可以关掉你的系统或者把数据同步走。解决思路确实是把授权校验从“远程 HTTP 请求”改成本地或链上只读调用。有一种做法是自己部署一个简单的授权合约合约里记录授权截止时间或授权账户列表PHP 端通过节点 RPC 调用合约的只读方法判断当前站点是否在授权期。这样即使别人拿到了 PHP 源码只要合约地址和调用参数对不上授权状态就不可控。PHP 端大致是这个思路// 授权校验调用合约只读方法返回授权状态 function checkLicense() { $contractAddress 0x你的合约地址; $rpcUrl 你的节点RPC地址; // 合约方法: function isLicensed(address user) public view returns (bool) $result json_decode(callContractMethod($rpcUrl, $contractAddress, isLicensed, [config(site_admin_address)]), true); return $result true; }但这里有个前置条件先确认 PHP 代码本身是干净的。如果后门藏在某个文件里它完全可以在授权函数外面包一层“永远返回 true”的壳合约再可靠也拦不住已经被改写的入口。所以我的习惯是先按第 5 章的后门检查手段把源码扫一遍再决定要不要接合约授权。否则合约地址只是一个心理安慰。5. 避坑与排查后门自查、静默掉单和 MySQL 8.0 连接失败5.1 后台密码被改或出现陌生管理员遇到的情况是某天登录后台发现原密码不对phpMyAdmin 里查看管理员表多了一个不认识的账户或者管理员的password字段被改过。原因这类发卡源码大多数从渠道流出原作者的远程后门可以事后写入新的管理员账号。另一种是后台入口文件没有改名弱密码被扫描器猜中。跑一次全库检索是最直接的自查方式cd /www/wwwroot/你的站点 grep -rn eval( --include*.php . grep -rn base64_decode --include*.php . grep -rn assert( --include*.php . grep -rn system( --include*.php . find . -name *.php -mtime -7 -type f -exec ls -la {} \;解决第一条eval(和base64_decode连在一起出现的文件优先审木马最常见的形态是eval(base64_decode(...))。mtime -7列出最近七天被修改的文件如果哪个文件在安装后突然变了基本可以断定有问题。确认干净后把后台入口从admin改成自定义目录名管理员密码用 16 位以上强密码清掉所有不认识的用户记录。从那以后我每次部署完都强制把 install 目录删掉避免安装流程被二次触发。5.2 USDT 已经转账订单却一直不发货现象用户钱包里显示转账成功链上也能查到交易但订单状态一直是“待支付”卡密迟迟不发。原因逐层排查。第一层计划任务没跑。监听依赖 cron 定时执行 PHP 脚本宝塔默认不会自动创建任务需要手动到“计划任务”里加一条执行周期建议*/1 * * * *命令是php /www/wwwroot/你的站点/think 自定义命令或直接访问监听脚本 URL。第二层确认数阈值设太高。主网环境如果设置确认数要等 30 个用户转完钱可能要等好几分钟才到账体验很糟。第三层回调接口被关了或验签密钥不匹配回调日志里能查到相关记录。解决先看监听脚本日志是正常执行还是报错。如果日志里根本没有循环记录说明 cron 没跑如果有记录但订单没更新说明判断逻辑有误常见就是精度问题。检查bccomp比较时两边的值是否都是真实小数链上返回的value除以精度后跟订单金额比较如果直接用整数比永远不会匹配。5.3 PHP 报 SQLSTATE[HY000] [2054]无法连接数据库现象安装时数据库连接失败或后台偶发报SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client。原因MySQL 8.0 默认使用caching_sha2_password认证而 PHP 7.4 的 mysqlnd 驱动较老两者在握手阶段不兼容。这是在 MySQL 8.0 环境下跑老 PHP 项目最典型的翻车点。解决不用纠结改驱动还是升级 PHP。在 MySQL 里重新创建用户时显式指定IDENTIFIED WITH mysql_native_password BY 密码就能绕过这个握手问题。已经建好的用户可以用ALTER USER改回来ALTER USER fakauser127.0.0.1 IDENTIFIED WITH mysql_native_password BY 你的强密码; FLUSH PRIVILEGES;改完清一下数据库连接池重启 PHP-FPM问题消失。如果 my.cnf 里有default_authentication_pluginmysql_native_password的选项也是一种全局解法但单独设置用户更不容易影响其他同库应用。5.4 多语言切换后页面样式错乱或语言包不生效现象切换英文后页面文字变了但 CSS 文件加载 404或者干脆整页布局乱掉。还有一种情况是语言包文件明明改过前端却一直是旧文案。原因样式 404 大多是伪静态配置里静态资源规则的问题或站点运行目录指错了。语言包不生效则是两个套路一是语言包文件带 BOMPHP 解析异常二是 FastAdmin 的运行时缓存没清语言包一直在用旧版本。解决先确认站点运行的root目录包含静态文件关闭伪静态里的静态资源拦截规则测试直接访问/assets/css/index.css看能否返回 200。语言包这边按第 2 章的命令清理 BOM再在后台清缓存。如果清完还是旧文案直接删掉/www/wwwroot/你的站点/runtime目录后重新生成。不要试图在浏览器里强刷服务端缓存不清理刷新十次也没用。6. 上线前验证清单与两个进阶调整正式跑业务之前我会先按一张清单过一遍剩下来的问题少一半检查项通过标准后台入口匿名访问访问后台 URL 必须跳登录页不能直开首页测试订单闭环用 1 USDT 下单扫码转账确认订单自动置为已支付并发出卡密监听任务存活连续三次 cron 日志有执行记录且无 fatal error回调验签失败日志里有失败记录但订单不会标成已支付服务器时间date命令和真实时间误差不超过 60 秒防止签名失效备份还原把 SQL 备份文件导入一台测试库确认表结构和数据完整验证清单走完还有两个调整可以做。第一自动识别浏览器语言让多语言真正落地。放在公共控制器的初始化方法里$browserLang strtolower(substr($_SERVER[HTTP_ACCEPT_LANGUAGE] ?? zh, 0, 2)); $allowLang [zh, en, ja, ko, es]; if (in_array($browserLang, $allowLang) !Session::get(lang_forced)) { Cookie::set(think_lang, $browserLang); }这个判断不会影响后台管理员的语言选择后台用户在登录后lang_forced标记已存在浏览器语言不会再覆盖。第二汇率缓存加锁避免并发下单时同一秒内汇率不一致。下单接口里对缓存值加文件锁或 Redis 锁多个请求同时创建订单时时只有第一个请求会去刷新汇率其余请求直接读取缓存保证同一时间段汇率一致。这套源码我拆过不止一次最大的教训是不要因为界面好看就跳过审计直接上生产。有一回我图省事把某渠道下载的版本直接部署第二天后台密码就被改了凌晨爬起来查日志发现插件目录里多了一个带base64_decode的 PHP 文件。从那以后我拿到任何二手发卡源码都会强制走一遍第 5 章那套检查并在线上跑一笔真实订单验证整个链路。希望这套流程对你的部署也有帮助。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表