银行卡归属地查询排错:外层 -1、body ret_code -1、remark 提示分别代表什么
银行卡归属地查询排错ret_code失败处理remark # 银行卡归属地查询排错:外层 -1、body ret_code -1、remark 提示分别代表什么
> 接口:银行卡归属地查询(apiCode=30)· 接入点:`30-7` · 计费:按次计费,5 厘/次,查询失败不计费 · 返回格式:JSON · 适用人群:已接入、正在处理异常分支的开发者 · 阅读时间:约 7 分钟
> 最后实测核对:2026-09-15
一句话结论:银行卡归属地查询接口的「调用不成功」有三种不同的响应形态——参数缺失时外层 `showapi_res_code` 直接是 `-1` 且 `showapi_res_body` 为空对象,卡号未收录时外层为 `0`、内层 `ret_code` 为 `-1` 并带 `remark`,归属地未收录但 BIN 命中时内层 `ret_code` 为 `-1`、`area` 是占位串而银行其余字段照常返回。
## 三种形态对照表
| 形态 | 触发条件 | `showapi_res_code` | `showapi_res_error` | `showapi_res_body` | `showapi_fee_num` |
|------|---------|-------------------|--------------------|--------------------|-------------------|
| A 参数缺失 | 不传 `cardNum` | `-1` | `must input cardNum field` | 空对象 `{}`,**没有 `ret_code`** | `0` |
| B 卡号未收录 | 卡号无法识别到 BIN | `0` | `""` | `{"remark":"找不到此卡号信息","ret_code":-1}` | `0` |
| C 归属地未收录、BIN 命中 | BIN 可识别但归属地库无记录 | `0` | `""` | `ret_code:-1`,`area` 为 `该卡归属地信息暂未收录 - `,银行其余字段照常返回 | `0` |
三种形态的 `showapi_fee_num` 都是 `0`。也就是说这三次调用都不计费,与接口文档「若归属地查询失败,则不收取费用」的表述一致。
## 判定顺序:先外层,再内层
形态 A 下 `showapi_res_body` 是空对象,里面没有 `ret_code`。如果代码里直接写 `body["ret_code"]`,这一路会抛 `KeyError`。判定必须从外层开始。
一段可以直接放进项目的判定逻辑:
```python
import requests
APPKEY = "YOUR_APPKEY"
API_URL = "https://route.showapi.com/30-7"
def query_bank_card(card_num: str) -> dict:
"""返回统一结构:{"status": ..., "data": {...}}"""
resp = requests.get(API_URL, params={"appKey": APPKEY, "cardNum": card_num}, timeout=30)
resp.raise_for_status()
data = resp.json()
# 第一层:请求是否被受理。形态 A 在这里拦下。
if data.get("showapi_res_code") != 0:
return {
"status": "request_rejected",
"detail": data.get("showapi_res_error", ""),
"fee_num": data.get("showapi_fee_num", 0),
}
body = data.get("showapi_res_body") or {}
# 第二层:本次查询是否拿到结果。形态 B、C 在这里拦下。
if str(body.get("ret_code")) != "0":
# 有 remark 的是形态 B;没有 remark 但 area 是占位串的是形态 C。
if body.get("remark"):
status = "card_not_found"
elif body.get("area", "").startswith("该卡归属地信息暂未收录"):
status = "area_not_found"
else:
status = "query_failed"
return {"status": status, "detail": body.get("remark", ""), "fee_num": data.get("showapi_fee_num", 0), "raw": body}
return {"status": "ok", "data": body, "fee_num": data.get("showapi_fee_num", 0)}
```
## 形态 A:参数缺失
不传 `cardNum` 时的完整响应(2026-09-15 实测):
```json
{
"showapi_res_error": "must input cardNum field",
"showapi_res_id": "6aa8e455fb638c2f695203f2",
"showapi_res_code": -1,
"showapi_fee_num": 0,
"showapi_res_body": {}
}
```
识别特征有三个:外层 `showapi_res_code` 为 `-1`、`showapi_res_error` 是英文提示 `must input cardNum field`、`showapi_res_body` 是空对象。这条路径不必读 `ret_code`,因为根本没有这个键。
`cardNum` 是本接口唯一的必填业务参数,传之前判一次空就能避免这一类返回。
## 形态 B:卡号未收录
传一个 BIN 完全无法识别的卡号(实测用 `cardNum=1234567890`):
```json
{
"showapi_res_error": "",
"showapi_res_id": "6aa8e454fb638c2f6951c5fa",
"showapi_res_code": 0,
"showapi_fee_num": 0,
"showapi_res_body": {
"remark": "找不到此卡号信息",
"ret_code": -1
}
}
```
外层是 `0`,说明请求已经被正常受理;内层 `ret_code` 为 `-1`,说明这次没查到。`remark` 给出了文字说明。
这一形态下 `showapi_res_body` 里只有 `remark` 和 `ret_code` 两个键,`area`、`bankName` 等字段都不出现。取值时统一带默认值,不要假设它们一定存在。
## 形态 C:归属地未收录、BIN 命中
传一个 BIN 能命中、但归属地库没有记录的卡号(实测用 `cardNum=9999999999999999999&needBin=1`):
```json
{
"showapi_res_error": "",
"showapi_res_id": "6aa8e445fb638c2f694c6fc8",
"showapi_res_code": 0,
"showapi_fee_num": 0,
"showapi_res_body": {
"logo": "http://static1.showapi.com/app2/banklogo/hxb.png",
"cardNum": "9999999999999999999",
"area": "该卡归属地信息暂未收录 - ",
"cardType": "借记卡",
"bankName": "华夏银行",
"formatBankName": "华夏银行",
"isLuhn": "0",
"card_digits": "19",
"brand": "华夏卡(银联卡)",
"simpleCode": "HXB",
"bin_digits": "",
"url": "www.hxb.com.cn",
"card_bin": "",
"tel": "95528",
"ret_code": -1
}
}
```
这一形态值得单独讲,因为它同时具备「失败」和「有数据」两个特征:
- `ret_code` 是 `-1`,按 `ret_code` 判定属于失败;
- 但 `bankName`、`formatBankName`、`brand`、`tel`、`url`、`logo`、`cardType`、`card_digits` 都有正常取值;
- `area` 不是空字符串,而是占位串 `该卡归属地信息暂未收录 - `(帮助手册的 646 个取值里没有这一项);
- 开了 `needBin=1`,但 `card_bin` 和 `bin_digits` 是空字符串,`isLuhn` 为 `0`。
如果你的业务只需要银行名和客服电话,这一形态的数据是可以用的;如果业务必须要归属地,那就按失败处理。判定时可以按 `area` 是否等于占位串来分流。
占位串的边界建议用前缀匹配,不要写全等:`body["area"].startswith("该卡归属地信息暂未收录")`。理由是占位串末尾带了一个分隔符,直接全等容易因为不可见字符对不上。
## 计费与重试
三种失败形态的 `showapi_fee_num` 都是 `0`。按次计费的口径是「调用成功才计费」,所以失败返回不会消耗额度。
重试策略上有一点要区分:形态 A 属于请求构造问题,重试同样会失败,改完参数再发;形态 B、C 属于数据层面没收录,同参数重试结果不变,可以把卡号记进「待人工复核」队列,等数据更新后再说。
数据更新频率是每年不定期更新,没有固定的刷新时间点。
## FAQ
**Q1:为什么 `ret_code` 有时候读不到?**
因为参数缺失时返回的 `showapi_res_body` 是空对象 `{}`,里面没有 `ret_code`。先判外层 `showapi_res_code` 就能避开这种情况。
**Q2:`showapi_res_code` 和 `ret_code` 有什么区别?**
`showapi_res_code` 在返回体最外层,判的是这次请求有没有被接口受理(比如参数是否齐全);`ret_code` 在 `showapi_res_body` 里,判的是这次业务查询有没有结果。两者要分开看。
**Q3:失败会不会扣费?**
不会。2026-09-15 实测三种失败形态的 `showapi_fee_num` 都是 `0`。接口文档的相应表述是「若归属地查询失败,则不收取费用」。
**Q4:`area` 返回 `该卡归属地信息暂未收录 - ` 算成功还是失败?**
`ret_code` 为 `-1`,按接口定义属于失败;但同一响应里的银行名、客服电话、官网等字段是正常值,可以按业务需要取用。
**Q5:接口有具体的错误码列表吗?**
接口文档对 `ret_code` 的说明是「`0` 为成功,其他失败」,没有给出以外的编号枚举。实际排错靠的是三样东西:外层 `showapi_res_code`、内层 `ret_code`、以及 `remark` 与 `area` 的文字内容。帮助手册里的错误码/枚举文档覆盖的是 `formatBankName`、`area`、`brand` 三组业务取值,不是错误码。
## 下一步阅读
- [银行卡归属地查询返回字段逐个说清](https://www.showapi.com/guides/bank-card-attribution-response-fields-30)——字段出现条件与空值处理
- [银行卡归属地查询的 cardNum 与 needBin 怎么传](https://www.showapi.com/guides/bank-card-attribution-params-guide-30)——避免形态 A 的参数写法
- [银行卡归属地查询按次计费下怎么省调用](https://www.showapi.com/guides/bank-card-attribution-cache-cost-30)——失败结果怎么缓存才不浪费额度
- **本系列共 10 篇**:查看[银行卡归属地查询指南总目录](https://www.showapi.com/guides/bank-card-attribution-guides-30)