码支付订单接口 & 免签短信接口说明
api_token 验证,token 通过会员登录获取,不可伪造。
https://wndog.qianxi.xin/api/order/
所有订单接口必须携带 api_token 参数,支持以下方式传递:
| 方式 | 示例 |
|---|---|
| URL参数 | ?api_token=xxxx |
| POST表单 | api_token=xxxx |
| POST JSON | {"api_token": "xxxx"} |
POST /api/order/create?api_token=xxxx
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
| api_token | 是 | string | 会员API Token(鉴权必填) |
| title | 是 | string | 商品名称 |
| amount | 是 | float | 金额(元),系统会自动调整确保唯一性 |
| description | 否 | string | 商品描述 |
| pay_type | 否 | string | 支付方式: wechat/alipay/any(默认) |
| expire_minutes | 否 | int | 过期分钟数(默认5,最长1440) |
| notify_url | 否 | string | 支付成功回调地址 |
| extra | 否 | string | 扩展数据(JSON) |
| 场景 | 处理方式 |
|---|---|
| 5分钟内无相同金额的待支付订单 | 使用原始金额创建订单 |
| 5分钟内有相同金额的待支付订单 | 自动加0.01元,直到找到可用金额 |
| 原订单支付成功/取消/过期 | 该金额可再次使用 |
| 最多调整次数 | 100次(最多加1元) |
示例场景:
用户A请求创建 ¥1.00 订单 → 创建成功,金额 ¥1.00 用户A再次请求创建 ¥1.00 订单 → 自动调整为 ¥1.01 用户A第三次请求 ¥1.00 → 自动调整为 ¥1.02 第一个订单 ¥1.00 支付成功 → 下次可再次创建 ¥1.00
请求示例:
POST /api/order/create?api_token=你的API_TOKEN
Content-Type: application/json
{
"title": "VIP会员月卡",
"description": "一个月VIP会员服务",
"amount": 29.90,
"pay_type": "any",
"expire_minutes": 5,
"notify_url": "https://your-site.com/callback"
}
返回字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| order_id | int | 订单ID |
| order_no | string | 系统订单号(ORD开头) |
| title | string | 商品名称 |
| description | string | 商品描述 |
| amount | float | 实际支付金额(可能已调整) |
| original_amount | float | 原始请求金额 |
| amount_adjusted | bool | 金额是否被自动调整 |
| pay_type | string | 支付方式: wechat/alipay/any |
| status | string | 订单状态: pending |
| expire_at | string | 过期时间 |
| expire_minutes | int | 过期分钟数 |
| created_at | string | 创建时间 |
| qrcodes | array | 收款二维码列表 |
qrcodes 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 收款码ID |
| type | string | 类型: wechat/alipay |
| name | string | 收款码名称 |
| image_path | string | 二维码图片路径 |
| qr_content | string | 二维码原始内容 |
| alipay_uid | string | 支付宝UID(微信码为空) |
| free_pay_enabled | int | 是否开启免输入金额模式(支付宝专用): 1开启 0关闭。开启后配合 alipay_uid 生成免输入金额的支付链接 |
返回示例:
{
"code": 0,
"msg": "订单创建成功",
"data": {
"order_id": 200,
"order_no": "ORD20260703022150516692",
"title": "测试支付订单 - 万能狗",
"description": "",
"amount": 0.01,
"original_amount": 0.01,
"amount_adjusted": false,
"pay_type": "any",
"status": "pending",
"expire_at": "2026-07-03 02:26:50",
"expire_minutes": 5,
"created_at": "2026-07-03 02:21:50",
"fee_amount": 0.01,
"is_free_fee": false,
"is_platform_subsidy": false,
"platform_member_id": null,
"qr_member_id": 1,
"qrcodes": [
{
"id": 2,
"type": "wechat",
"name": "微信",
"image_path": "uploads/member/qr_1_1779603622_2890.png",
"qr_content": "wxp://f2f0h-xrGByLyteXzvdnIk8AmwLeF5r6PMIkRBC9bTwDsaM",
"alipay_uid": "",
"free_pay_enabled": 1
},
{
"id": 13,
"type": "alipay",
"name": "支付宝",
"image_path": "uploads/member/qr_1_1782270911_8382.png",
"qr_content": "https://qr.alipay.com/fkx11764k1cdynkuev8mc17",
"alipay_uid": "2088642730792055",
"free_pay_enabled": 0
}
]
}
}
is_free_fee=true: 会员有有效套餐或等级免手续费,fee_amount=0is_platform_subsidy=true: 余额不足时由平台会员代收,订单金额进入平台会员收款码qr_member_id: 实际收款码所属会员ID。代收时为平台会员ID,否则为当前会员IDGET /api/order/list?api_token=xxxx&page=1&size=20
| 参数 | 必填 | 说明 |
|---|---|---|
| api_token | 是 | 会员API Token |
| page | 否 | 页码(默认1) |
| size | 否 | 每页条数(默认20,最大100) |
| status | 否 | 筛选状态: pending/paid/expired/cancelled/refunded |
| keyword | 否 | 搜索关键词(商品名/订单号/备注) |
| start_date | 否 | 开始日期(2026-01-01) |
| end_date | 否 | 结束日期(2026-12-31) |
GET /api/order/detail?api_token=xxxx&order_no=ORD20260530143022123456
返回订单完整信息及操作日志。
POST /api/order/cancel?api_token=xxxx
{
"order_no": "ORD20260530143022123456"
}
POST /api/order/refund?api_token=xxxx
{
"order_no": "ORD20260530143022123456",
"reason": "用户申请退款"
}
POST /api/order/notify?api_token=xxxx
{
"order_no": "ORD20260530143022123456"
}
GET /api/order/stats?api_token=xxxx&period=today
| period值 | 说明 |
|---|---|
| today | 今天 |
| week | 最近7天 |
| month | 最近30天 |
| all | 全部 |
| 状态 | 说明 |
|---|---|
| pending | 待支付 |
| paid | 已支付 |
| expired | 已过期 |
| cancelled | 已取消 |
| refunded | 已退款 |
订单支付成功后,系统会向 notify_url 发送 POST 请求:
{
"order_no": "ORD20260530143022123456",
"title": "VIP会员月卡",
"amount": 29.90,
"pay_type": "alipay",
"status": "paid",
"payer": "付款人昵称",
"remark": "付款备注",
"paid_at": "2026-05-30 14:31:05",
"created_at": "2026-05-30 14:30:22",
"timestamp": 1748615465
}
A: 系统会自动调整金额避免冲突。返回的 amount 是实际创建的金额,original_amount 是你请求的原始金额,amount_adjusted 为 true 表示已调整。
A: 订单默认5分钟过期。请确认:1) 付款金额与订单金额完全一致(精确到分);2) 付款在过期时间内完成;3) App已正常上报收款到服务器。
A: 回调数据包含 status: "paid" 字段。如果第三方系统需要兼容,建议同时检查 status 和 paid_at 字段。
A: 通过会员登录接口 POST /api/member/login 获取,或在会员中心查看。
A: 会。系统有定时任务每分钟检查,将过期订单标记为 expired。也可以通过 Cron 脚本 cron_order_expire.php 手动执行。
A: 无限制。但系统会自动调整金额确保每个订单金额唯一,最多调整100次(加1元)。
api_token 验证,短信发送前需在会员中心开启短信开关,并通过事务确保扣费与发送的一致性。
https://wndog.qianxi.xin/api/member/
所有短信接口必须携带 api_token 参数,支持以下方式传递:
| 方式 | 示例 |
|---|---|
| URL参数 | ?api_token=xxxx |
| POST表单 | api_token=xxxx |
| POST JSON | {"api_token": "xxxx"} |
POST /api/member/sms-send?api_token=xxxx
verify_code 字段)。verify_code 字段,不要自己生成验证码,否则与用户收到的会不一致。
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
| api_token | 是 | string | 会员API Token(鉴权必填) |
| phone | 是 | string | 接收短信的手机号 |
| scene | 否 | string | 发送场景: api(默认)/login/verify/reset_pwd |
| code_length | 否 | int | 验证码长度(默认4位) |
| valid_time | 否 | int | 验证码有效时间(秒,默认300) |
| signature_id | 是(必传) | int | 签名ID(通过查询可用签名接口获取) |
| template_id | 是(必传) | int | 模板ID(通过查询可用模板接口获取) |
| from_domain | 否 | string | 注:会员中心设置了白名单域名 这个值就必须传域名否则发送不了 |
请求示例(推荐,带 from_domain 以适配白名单):
POST /api/member/sms-send?api_token=你的API_TOKEN
Content-Type: application/json
{
"phone": "13800138000",
"signature_id": 1,
"template_id": 1,
"scene": "api",
"code_length": 4,
"from_domain": "你的域名"
}
返回字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| verify_code | string | 实际下发的验证码(由接口生成,与用户手机收到的一致) |
| cost_type | string | 扣费类型: sms_count=短信条数 / balance=余额 |
| cost_amount | float/int | 扣费金额(余额扣费)或条数(短信包扣费) |
| sms_count_remaining | int | 短信包剩余条数 |
| balance_remaining | float | 余额剩余金额 |
返回示例(成功):
{
"code": 0,
"msg": "短信发送成功",
"data": {
"verify_code": "3829",
"cost_type": "sms_count",
"cost_amount": 1,
"sms_count_remaining": 99,
"balance_remaining": 100.00
}
}
返回示例(余额扣费):
{
"code": 0,
"msg": "短信发送成功",
"data": {
"verify_code": "3829",
"cost_type": "balance",
"cost_amount": 0.10,
"sms_count_remaining": 0,
"balance_remaining": 99.90
}
}
GET /api/member/sms-logs?api_token=xxxx&page=1&page_size=20
| 参数 | 必填 | 说明 |
|---|---|---|
| api_token | 是 | 会员API Token |
| page | 否 | 页码(默认1) |
| page_size | 否 | 每页条数(默认20,最大50) |
返回字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 日志ID |
| phone | string | 接收手机号 |
| scene | string | 发送场景 |
| template_code | string | 短信模板CODE |
| sign_name | string | 短信签名 |
| cost_type | string | 扣费类型: sms_count/balance |
| cost_amount | float/int | 扣费金额或条数 |
| sms_count_before | int | 扣费前短信条数 |
| sms_count_after | int | 扣费后短信条数 |
| balance_before | float | 扣费前余额 |
| balance_after | float | 扣费后余额 |
| status | int | 状态: 0=发送中 1=成功 2=失败 |
| created_at | string | 发送时间 |
返回示例:
{
"code": 0,
"msg": "ok",
"data": {
"list": [
{
"id": 1,
"phone": "13800138000",
"scene": "api",
"template_code": "SMS_123456",
"sign_name": "万能狗",
"cost_type": "sms_count",
"cost_amount": 1,
"sms_count_before": 100,
"sms_count_after": 99,
"balance_before": 100.00,
"balance_after": 100.00,
"status": 1,
"created_at": "2026-07-12 10:30:00"
}
],
"total": 1,
"page": 1,
"page_size": 20
}
}
| 条件 | 扣费方式 | 说明 |
|---|---|---|
| 短信包条数 > 0 | 扣除1条短信包 | cost_type=sms_count |
| 短信包为0,余额 ≥ 单条价格 | 从余额扣除 | cost_type=balance |
| 短信包为0,余额不足 | 拒绝发送 | 返回 403 错误 |
| 发送失败 | 自动退还扣费 | 事务回滚,条数/余额恢复 |
FOR UPDATE)防止并发问题。若短信发送失败,已扣除的费用会自动退还。
在发送短信前,可先调用此接口获取当前可用的签名ID和模板ID列表。
GET /api/member/sms-resources?api_token=xxxx
请求参数:无
返回示例:
{
"code": 0,
"msg": "获取成功",
"data": {
"signatures": [
{
"id": 1,
"name": "万能狗",
"remark": "系统备案签名",
"status": 1
}
],
"templates": [
{
"id": 1,
"name": "登录验证码",
"template_code": "SMS_123456",
"scene": "login",
"scene_label": "登录",
"template_content": "【万能狗】您的验证码为${code},${min}分钟内有效。",
"template_params": {
"code": "验证码",
"min": "有效分钟"
},
"status": 1
}
]
}
}
使用说明:
api_token 鉴权id 字段就是发信接口需要的 signature_id 和 template_id以下是直接可用的 PHP 调用代码示例:
from_domain=$_SERVER['HTTP_HOST']。
案例0.5:带 from_domain 发送短信(推荐服务器端对接)
<?php
/**
* 带 from_domain 的短信发送示例(适配白名单)
* 适合:服务器端 curl / SDK / 跨域后端对接
* from_domain 直接用 $_SERVER['HTTP_HOST'] 即可
*/
function sendSmsWithDomain($apiBase, $apiToken, $phone, $fromDomain, $signatureId, $templateId, $options = []) {
$url = $apiBase . '/api/member/sms-send?api_token=' . urlencode($apiToken);
$data = array_merge([
'phone' => $phone,
'scene' => 'api',
'code_length' => 4,
'valid_time' => 300,
'signature_id' => $signatureId, // ★ 必传:签名ID
'template_id' => $templateId, // ★ 必传:模板ID
'from_domain' => $fromDomain, // ★ 关键:加上这一行,白名单设置了这个值也必须传
], $options);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 所有业务错误都是 HTTP 200,通过 body.code 字段判断(避免被 SDK 误判成"网络异常")
if ($httpCode !== 200) {
return ['success' => false, 'msg' => 'HTTP网络错误: ' . $httpCode];
}
$result = json_decode($response, true);
if (!is_array($result)) {
return ['success' => false, 'msg' => '响应非JSON: ' . $response];
}
$result['success'] = ($result['code'] === 0);
return $result;
}
// 调用示例:白名单配了 就直接用 $_SERVER['HTTP_HOST'] 传值
$apiBase = $_SERVER['HTTP_HOST']; // API域名
$apiToken = '你的会员API_TOKEN';
$fromDomain = '$_SERVER['HTTP_HOST']'; // ★ 填当前调用方的域名推荐直接使用变量 $_SERVER['HTTP_HOST']
$signatureId = 1; // ★ 必传:签名ID(通过接口查询可用签名获取)
$templateId = 1; // ★ 必传:模板ID(通过接口查询可用模板获取)
$phone = '13800138000';
$result = sendSmsWithDomain($apiBase, $apiToken, $phone, $fromDomain, $signatureId, $templateId);
if ($result['success']) {
echo "发送成功,验证码:" . $result['data']['verify_code'] . "\n";
echo "扣费方式:" . $result['data']['cost_type'] . "\n";
} else {
// msg 字段就是明确的错误信息(域名非白名单 / 条数不足 / SDK报错 等)
echo "发送失败:[" . $result['code'] . "] " . $result['msg'] . "\n";
}
?>
案例0:先查询可用的签名和模板
<?php
/**
* 先调用此接口获取可用的签名ID和模板ID
* 建议在系统初始化时调用一次,结果可缓存到本地
*/
function getSmsResources($apiBase, $apiToken) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiBase . '/api/member/sms-resources?api_token=' . urlencode($apiToken));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
if ($result['code'] !== 0) {
throw new Exception('获取资源失败: ' . $result['msg']);
}
return $result['data']; // ['signatures' => [...], 'templates' => [...]]
}
// 调用并打印
$resources = getSmsResources('https://www.wndog.com', '你的API_TOKEN');
echo "可用的签名:\n";
foreach ($resources['signatures'] as $sig) {
echo " ID={$sig['id']} 名称={$sig['name']}\n";
}
echo "可用的模板:\n";
foreach ($resources['templates'] as $tpl) {
echo " ID={$tpl['id']} 场景={$tpl['scene_label']} CODE={$tpl['template_code']}\n";
}
?>
案例1:使用cURL直接调用(推荐)
<?php
/**
* 万能狗短信API调用示例
*/
// ====== 配置 ======
$apiBase = 'https://www.wndog.com'; // 万能狗域名
$apiToken = '你的会员API_TOKEN'; // 会员中心 → API Token
$phone = '13800138000'; // 接收短信的手机号
$signatureId = 1; // ★ 必传:签名ID(通过查询可用签名接口获取)
$templateId = 1; // ★ 必传:模板ID(通过查询可用模板接口获取)
// ====== 方式一:JSON传参(推荐) ======
function sendSms($apiBase, $apiToken, $phone, $signatureId, $templateId, $options = []) {
$url = $apiBase . '/api/member/sms-send?api_token=' . urlencode($apiToken);
$data = array_merge([
'phone' => $phone,
'scene' => 'api',
'code_length' => 4,
'valid_time' => 300,
'signature_id' => $signatureId, // ★ 必传:签名ID
'template_id' => $templateId, // ★ 必传:模板ID
], $options);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
return ['success' => false, 'msg' => 'HTTP错误: ' . $httpCode];
}
return json_decode($response, true);
}
// ====== 方式二:表单传参 ======
function sendSmsByForm($apiBase, $apiToken, $phone, $signatureId, $templateId) {
$url = $apiBase . '/api/member/sms-send?api_token=' . urlencode($apiToken);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(['phone' => $phone, 'scene' => 'api', 'signature_id' => $signatureId, 'template_id' => $templateId]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// ====== 方式三:使用GuzzleHttp(需composer安装guzzlehttp/guzzle) ======
function sendSmsByGuzzle($apiBase, $apiToken, $phone, $signatureId, $templateId) {
$client = new \GuzzleHttp\Client();
$response = $client->post($apiBase . '/api/member/sms-send', [
'query' => ['api_token' => $apiToken],
'json' => ['phone' => $phone, 'scene' => 'api', 'signature_id' => $signatureId, 'template_id' => $templateId],
'timeout' => 30,
]);
return json_decode($response->getBody(), true);
}
// ====== 调用示例 ======
$signatureId = 1; // ★ 必传:签名ID(通过查询可用签名接口获取)
$templateId = 1; // ★ 必传:模板ID(通过查询可用模板接口获取)
$result = sendSms($apiBase, $apiToken, $phone, $signatureId, $templateId);
if ($result && $result['code'] === 0) {
echo "发送成功!\n";
echo "验证码: " . $result['data']['verify_code'] . "\n";
echo "扣费方式: " . $result['data']['cost_type'] . "\n";
echo "短信条数剩余: " . $result['data']['sms_count_remaining'] . "\n";
echo "余额剩余: " . $result['data']['balance_remaining'] . "\n";
} else {
echo "发送失败: " . ($result['msg'] ?? '未知错误') . "\n";
echo "错误码: " . ($result['code'] ?? 'N/A') . "\n";
}
?>
案例2:登录验证码场景(使用login模板)
<?php
// 发送登录验证码
$phone = $_POST['phone'] ?? '';
$signatureId = 1; // ★ 必传:签名ID
$templateId = 1; // ★ 必传:模板ID(对应login场景的模板)
$result = sendSms('https://www.wndog.com', '你的API_TOKEN', $phone, $signatureId, $templateId, [
'scene' => 'login',
'code_length' => 6,
'valid_time' => 600, // 10分钟有效
]);
if ($result['code'] === 0) {
// ★★★ 关键点 ★★★
// 必须使用接口返回的 verify_code(这是接口实际下发的验证码,与用户手机收到的一致)
// 不要自己用 mt_rand() 等函数生成,否则会与用户收到的对不上
$_SESSION['sms_code'] = $result['data']['verify_code']; // 用接口返回的!
$_SESSION['sms_expire'] = time() + 600;
$_SESSION['sms_phone'] = $phone;
echo json_encode(['success' => true, 'msg' => '验证码已发送']);
} else {
echo json_encode(['success' => false, 'msg' => $result['msg']]);
}
?>
案例3:批量发送(循环调用)
<?php
// 批量发送短信(注意:每个手机号都单独调用一次API)
$phones = ['13800138000', '13900139000', '13700137000'];
$success = 0;
$failed = [];
foreach ($phones as $phone) {
$result = sendSms('https://www.wndog.com', '你的API_TOKEN', $phone);
if ($result['code'] === 0) {
$success++;
} else {
$failed[$phone] = $result['msg'];
}
sleep(1); // 间隔1秒,避免频率限制
}
echo "成功: $success, 失败: " . count($failed) . "\n";
print_r($failed);
?>
案例4:查询发送记录
<?php
// 查询最近的发送记录
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.wndog.com/api/member/sms-logs?api_token=你的API_TOKEN&page=1&page_size=20&status=1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
if ($result['code'] === 0) {
echo "总共发送: " . $result['data']['total'] . " 条\n";
foreach ($result['data']['list'] as $log) {
echo "[" . $log['created_at'] . "] " . $log['phone'] . " - " .
($log['cost_type'] === 'sms_count' ? "扣短信条数" : "扣余额¥" . $log['cost_amount']) . "\n";
}
}
?>
| 错误码 | 说明 |
|---|---|
| 0 | 成功 |
| 400 | 参数错误(手机号为空、格式错误、签名算法错、签名过期等) |
| 401 | 未认证 / api_token无效或缺失 |
| 403 | 短信功能未开启 / 签名模板配置无效 / 域名非白名单 / 短信条数不足且余额不足 |
| 404 | 接口不存在 |
| 405 | 请求方法不允许(如sms-send必须用POST) |
| 429 | 发送频率限制(60秒内重复发送同一手机号) |
| 500 | 短信发送失败(接口返回错误,会附带SDK返回的真实错误原因) |
| 措施 | 说明 |
|---|---|
| 身份验证 | 通过 api_token 验证会员身份,token 不可伪造 |
| 事务处理 | 扣费使用数据库事务 + FOR UPDATE 行锁,防止并发问题 |
| 失败回退 | 短信发送失败时自动退还已扣除的条数或余额 |
| 完整日志 | 每次发送记录到 pm_sms_log,含IP、扣费前后状态、API响应 |
| 频率限制 | 同一手机号60秒内只能发送一次,防止滥用 |
| HTTPS传输 | 建议使用HTTPS加密传输API请求 |
A: 登录会员中心 → 设置 → 短信服务,打开短信开关。
A: 因为 fetch / axios / 大部分自研 SDK 会把 HTTP 非 200 的响应直接抛进 catch,统一显示"网络异常",把真实的 msg(域名非白名单、条数不足、阿里云签名报错等)完全吞掉,导致用户根本不知道错在哪。
所以本系统所有业务错误都用 HTTP 200 返回,真实错误码放在 JSON code 字段里,错误原因在 msg 字段里,客户端只要判断 body.code === 0 就是成功,否则直接显示 body.msg,不会再出现"网络异常"三个字。
A: 可以在会员中心 → 增值服务中购买短信包,也可以不购买直接从余额扣费(按单条短信价格扣除)。
A: 不会。系统使用事务处理,发送失败时已扣除的条数或余额会自动退还。如果是阿里云SDK错误,msg 字段会直接附带 SDK 真实错误信息(如 RAM权限不足、签名未过审、模板变量不对),方便排查。