HAPPYTRAVEL 开放接口 v1
给合作方系统用的接口:把游客的实名资料直接推送到 HAPPYTRAVEL 平台激活印尼电话卡,并实时拿到激活结果。合作方不用再让游客打开我们的网页填写。
- 接口地址
https://api.happytravel.lwytech.top/v1- 数据格式
- JSON(UTF-8),照片用 Base64 编码的 JPEG
- 时间
- 北京时间(UTC+8),格式
2026-11-04 14:30;日期2026-11-04 - 频率限制
- 每个密钥每分钟 60 次,超出返回
429 - 文档语言
- 中文 / English(正式版会附英文版)
POST /orders,订单直接进入供应商「待处理」PUT /orders/{no}- 每个代理在创建时自动开通 API,管理员会把 API 密钥 和 回调密钥 发给你。密钥只显示一次,丢了可以请管理员重置。
- 通过接口提交的订单算在你的代理名下,和网页提交的订单一样处理、一样结算。
- 回调地址(Webhook)请告诉管理员登记,状态变化时平台会推送过去。
- 防重复规则和网页一致:同一个印尼号码或条码号只能提交一次。
认证与安全
每个代理创建时自动生成一把 API 密钥,管理员可以随时停用或重新生成。每个请求都在请求头里带上:
Authorization: Bearer ht_live_7Qm2xK9pL4vR8sT1wY6zA3bC5dE0fG Content-Type: application/json
安全措施
| 措施 | 说明 |
|---|---|
| 只能看到自己的订单 | 密钥绑定到一个代理,查询、修改都限制在这个代理名下 |
| 全程 HTTPS | 非加密请求一律拒绝 |
| IP 白名单(可选) | 管理员可以填写合作方服务器 IP,其他 IP 用这把密钥也调不通 |
| 幂等 | 请求里带合作方自己的单号 external_id,网络重试不会重复下单 |
| 回调签名 | 每次回调都带 X-HT-Signature,合作方用回调密钥验证是不是我们发的 |
订单状态
| status | 中文 | 含义 | 会触发的回调 |
|---|---|---|---|
| pending | 待处理 | 已提交,等供应商激活 | — |
| activated | 已激活 | 供应商已激活成功 | order.activated |
| returned | 已退回 | 资料有问题,return_reason 写明原因,需要修改重提交 | order.returned |
| resubmitted | 重提交 | 已修改重提交,等供应商再处理 | — |
| deleted | 已删除 | 被管理员删除,不再处理 | order.deleted |
提交订单
/v1/orders提交一位游客的资料。成功后订单直接进入供应商「待处理」,在代理端、供应商端、管理端都能看到。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
external_id | string | 是 | 合作方自己的单号,最长 64 位;同一个单号重复提交会返回第一次的结果 |
name | string | 是 | 英文姓名,与护照一致,如 ZHANG SAN |
passport_no | string | 是 | 护照号码 |
nationality | string | 否 | 国籍,三位国家代码,如 CHN |
birth_date | date | 否 | 出生日期 1990-01-31 |
msisdn | string | 是 | 印尼号码,包装上 08 开头那串,如 085283673294 |
package_serial | string | 否 | 包装条码号(ICCID),如 0150000066492291 |
imei | string | 是 | 手机 IMEI,15 位,会校验最后一位校验码 |
arrival_date | date | 是 | 抵达印尼日期 |
contact | string | 是 | 游客联系方式:手机号或邮箱 |
remark | string | 否 | 备注,最长 200 字 |
passport_photo | base64 | 是 | 护照照片页,JPEG,建议长边 1600 像素,最大 8 MB |
package_photo | base64 | 是 | 电话卡包装正面照片,JPEG |
请求示例
{
"external_id": "TRIP-20261104-0081",
"name": "ZHANG SAN",
"passport_no": "E12345678",
"nationality": "CHN",
"birth_date": "1990-01-01",
"msisdn": "085283673294",
"package_serial": "0150000066492291",
"imei": "866641057096095",
"arrival_date": "2026-11-06",
"contact": "+86 138 0000 0000",
"remark": "晚上航班",
"passport_photo": "/9j/4AAQSkZJRg...",
"package_photo": "/9j/4AAQSkZJRg..."
}成功返回 201
{
"ok": true,
"order": {
"order_no": "#1104-012",
"external_id": "TRIP-20261104-0081",
"status": "pending",
"created_at": "2026-11-04 14:30"
}
}
重复提交 409
{
"ok": false,
"error": "duplicate_msisdn",
"message": "这张电话卡已经提交过了",
"existing_order_no": "#1103-004"
}查询订单
/v1/orders/{order_no}/v1/orders?external_id=TRIP-20261104-0081用平台单号或合作方单号查询。订单号里的 # 可以省略,1104-012 和 %231104-012 都可以。
{
"ok": true,
"order": {
"order_no": "#1104-012",
"external_id": "TRIP-20261104-0081",
"status": "returned",
"return_reason": "护照照片反光,看不清号码",
"msisdn": "085283673294",
"imei": "866641057096095",
"arrival_date": "2026-11-06",
"created_at": "2026-11-04 14:30",
"updated_at": "2026-11-04 15:02",
"history": [
{"at": "2026-11-04 14:30", "event": "submitted"},
{"at": "2026-11-04 15:02", "event": "returned", "reason": "护照照片反光,看不清号码"}
]
}
}
订单列表
/v1/orders?status=activated&date_field=updated&from=2026-11-01&to=2026-11-30&page=1| 参数 | 说明 |
|---|---|
status | pending / activated / returned / resubmitted,不填为全部 |
date_field | created 提交日期、arrival 抵达日期、updated 处理日期 |
from / to | 日期范围,含首尾 |
page / page_size | 分页,每页默认 50,最多 200 |
返回 {"ok":true,"total":128,"page":1,"orders":[…]},每条订单字段和「查询订单」一样,不含 history。
修改重提交
/v1/orders/{order_no}只有 returned 状态的订单可以重提交。只传要改的字段即可,照片不传就沿用原来的。成功后状态变为 resubmitted,进入供应商「重提交」。
{
"passport_photo": "/9j/4AAQSkZJRg...",
"remark": "已重拍护照照片"
}
套餐充值
/v1/plans/v1/orders/{order_no}/topup套餐由平台管理员创建,每个套餐有唯一的产品 ID(product_id)。先用 GET /v1/plans 取得可用套餐,再给订单指定套餐;也可以在提交订单时直接带上 "product_id"。平台充值后按套餐结算金额从代理账户余额扣款。
// GET /v1/plans 返回
{"ok": true, "currency": "¥", "local_currency": "Rp",
"plans": [{"product_id": "TSEL-10G-30", "name": "10GB 30天", "price": 45.45, "local_price": 100000}]}
// POST /v1/orders/1104-012/topup
{"product_id": "TSEL-10G-30"} // 传 null 表示取消;已充值的订单不能再改(409 topup_locked)
// 每个订单都带 topup 字段
"topup": {"status": "pending", "product_id": "TSEL-10G-30", "plan": "10GB 30天", "price": 45.45}
// status: none 未选 · pending 待充值 · done 已充值 · failed 充值退回(附 fail_reason)· self 代理自充 · other 其他原因
充值结果会通过回调通知:topup.done(已充值)、topup.failed(充值退回,附 topup_fail_reason)。
识别辅助(可选)
/v1/recognize/passport/v1/recognize/package/v1/recognize/imei合作方系统如果没有自己的识别功能,可以把照片发过来,用我们平台的识别结果预填表单。传 {"photo":"base64…"},返回识别出的字段,和网页上的识别是同一套。
// POST /v1/recognize/imei 返回
{"ok": true, "imeis": ["866641057096095", "866641057096103"]}
回调通知
合作方在管理端登记一个回调地址,例如 https://partner.example.com/ht/callback。订单状态一变,平台马上向这个地址发送 POST。合作方返回 HTTP 200 即视为收到;没收到会在 1 分钟、5 分钟、30 分钟、2 小时、12 小时后重试,最多 5 次。
回调内容
POST /ht/callback
X-HT-Event: order.activated
X-HT-Timestamp: 1793750400
X-HT-Signature: sha256=5f2c…e91a
{
"event": "order.activated",
"order_no": "#1104-012",
"external_id": "TRIP-20261104-0081",
"status": "activated",
"msisdn": "085283673294",
"topup": {"status": "done", "product_id": "T3", "plan": "30天60GB流量", "price": 37},
"occurred_at": "2026-11-04 15:20"
}验证签名(Python)
import hmac, hashlib
def verify(body: bytes, ts: str, sig: str, secret: str) -> bool:
msg = ts.encode() + b"." + body
expect = "sha256=" + hmac.new(
secret.encode(), msg, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expect, sig)
时间戳超过 5 分钟的回调建议丢弃,防止被重放。
事件类型
| event | 什么时候发 | 额外字段 |
|---|---|---|
order.activated | 供应商点「成功」 | — |
order.returned | 供应商点「退回」 | return_reason |
order.reopened | 供应商点「返回待办事项」,订单回到待处理 | — |
order.deleted | 管理员删除订单 | — |
错误码
| HTTP | error | 原因 | 怎么处理 |
|---|---|---|---|
| 400 | invalid_field | 字段缺失或格式不对,fields 里列出具体字段 | 按提示改正后重发 |
| 400 | invalid_imei | IMEI 不是 15 位,或校验码不对 | 让游客核对 IMEI |
| 401 | unauthorized | 密钥错误、已停用,或 IP 不在白名单 | 联系管理员 |
| 404 | not_found | 订单不存在,或不属于这把密钥 | 检查单号 |
| 409 | duplicate_msisdn | 这个印尼号码已经提交过 | 不要重复提交;需要时联系管理员 |
| 409 | duplicate_serial | 这个条码号已经提交过 | 同上 |
| 400 | invalid_product | 产品 ID 不存在或套餐已停用 | 先用 GET /v1/plans 取最新产品 ID |
| 409 | topup_locked | 订单已充值,不能再改套餐 | 需要改的话联系管理员撤销充值 |
| 409 | invalid_state | 订单当前状态不能做这个操作,比如未退回的订单不能重提交 | 先查询订单状态 |
| 413 | photo_too_large | 照片超过 8 MB | 压缩后重发 |
| 429 | rate_limited | 请求太频繁 | 按 Retry-After 秒数后重试 |
在线试用
下面是模拟的接口,数据只在这个页面里,不会连到正式平台,用来熟悉请求和返回的格式。正式调用请用你自己的密钥请求上面的接口地址。可以先提交订单,再用右边按钮模拟供应商「成功」或「退回」,看回调会推送什么内容。
—