API 对接

一、更新日志

日期 变更情况
2026-04-13 增加 webhook 机制,异步通知订单状态、库存座位变更,见 十、章节 章节
2026-04-10 增加 取消订单 接口,见 7.9 章节
2026-03-08 文档初始化

二、文档说明

该文档为API对接文档,实现主动对接我方接口,获取演出节目所有物理座位编码、某场演出剩余座位情况,并完成下单、付款、出票等一系列业务场景。

三、对接步骤

四、接口地址与协议规范

启动对接后,先在测试环境进行,测试环境中,

   client_id:pa.apitest
client_secret:8fceac99b3c1ce966caf9c5724b95df7

测试环境的 client_id 和 client_secret 固定如上。(但可能不定期发生轮换,请注意刷新该文档)

测试环境对接调试完毕后,正式上线前:

所有交互数据均为 UTF-8,如无明确约定,请在务必在发出的请求头中增加:

Content-Type: application/json
X-Tenant-Id: 1

上面的 X-Tenant-Id 为我方自定义请求头,固定值为:1,不同项目该值可能发生变化,具体请咨询我方技术人员。

大部分接口使用 restful 规范,并且返回 json 编码,但个别接口考虑性能,将使用 protobuf 编码,具体将在接口中明确。

对于请求与响应均正常的对接,API 返回 http status code : 2XX,并且在响应体中返回 json (或者protobuf)格式的数据。

对于失败的请求,API 服务器将返回 4XX,并且在响应体中给出具体的错误提示,比如:

{
    "code": "错误编码",
    "message": "错误信息"
}

你方可以根据返回是否是 2XX,来判断是否发生错误,进而通过返回的响应得到错误原因。

返回的code值在未来会发生变化,不建议本地保存映射。 ⚠️

您应该尤其注意 ⚠️ 401 响应,该响应的 body 返回内容为:

{
  "code": "401",
  "message": "Invalid Token or Expired"
}

该响应意味着您提供的 access_token 是无效的,或者已经过期。您需要重新获取有效的 access_token。

五、项目变量

5.1、《精灵宝盒的秘密》

节目编号变量:project_id = 1,后续用 {project_id} 表示该变量
场馆编号变量:venue_id = 1,后续用 {venue_id} 表示该变量

六、字典

6.1 - 订单状态码(order_status)

状态码 中文名称 状态说明
created 订单创建成功 订单创建后的初始状态,等待支付,如果超过 pay_expired_at 则订单会自动取消
cancelled 订单已取消 订单超时没有支付,或者由用户/后台取消,如果已付款将原路返回款项
issued 已出票 订单支付成功后,已经出票
checked 已核销 订单已核销进场(如多次核销,以第一次核销为准)
success 成功 订单成功结束,订单成功结束后不能再退款

6.2 - 支付状态码(pay_status)

状态码 中文名称 状态说明
pending 等待支付 订单创建后的初始状态,等待支付,如果超过 pay_expired_at 则订单会自动取消
closed 已关闭 订单取消后,不再需要付款,支付状态为关闭
paid 已支付 订单付款后状态
refunded 已退款 订单已退款

6.3 - 票种类型(ticket_type)

状态码 中文名称 说明
t1 全票 不分票种,按张售卖,一人一票
a1 成人票 成人票
c1 儿童票 儿童票

⚠️ 该字段的值可能会经常增加,请注意刷新本文档

七、API 接口

7.1 - 获取 access_token

POST /aaa/oauth/token 

{
    "grant_type": "implicit",
    "client_id": "pa.apitest",
    "client_secret": "8fceac99b3c1ce966caf9c5724b95df7"
}

上面传递的参数中,grant_type 固定为:implicit,如无异常,接口将返回:

{
    "access_token": "***********",
    "expires_in": 7200
}

access_token 过期时间 7200秒,请定期重新使用该接口获取新的 access_token,后续请求请在 HTTP Request Header 中附带 access_token 的值:

⚠️ 请务必在本地缓存本 access_token 值,而不要每次请求的时候都获取新的 access_token,否则会被封IP,导致业务受损。⚠️

Authorization: Bearer ******* 上面获取的 access_token 值 ******

7.2 - (可选)获取演出剧目的基本信息

每个演出剧目均有自己的 project_id,可根据以下接口获取演出剧目的基本信息(一般不会频繁变化)

GET /pa/project/{project_id}

将返回以下结构:

{
    "id": 1,
    "name": "精灵宝盒的秘密",
    "venue": 1
}

其中,返回的 venue 就是该剧目所在场馆的编号,可根据下面的接口获取场馆的全量物理座位信息。

7.3 - (可选)获取演出节目所在场馆信息和全量座位信息

演出会发生在不同的场馆,每个场馆物理层面的座位并不相同,可以通过

GET /pa/venue/{venue_id}

获取当前剧目指定的场馆及全量座位信息,返回基本结构如下:

{
    "name": "上海马戏城中剧场",
    "address": "上海市共和新路2266号上海马戏城中剧场",
    "latlng": "31.277249,121.450011",
    "traffic": "马戏城停车位紧张,建议绿色出行(地铁1号线,上海马戏城站,3号出口)。",
    "section": [
        {
            "code": "A",
            "name": "A区"
        },
        {
            "code": "B",
            "name": "B区"
        },
        ......
    ],
    "seat": [
        {
            "section": "C",
            "code": "C-2-1",
            "name": "C区2排1座",
        },
        {
            "section": "C",
            "code": "C-2-3",
            "name": "C区2排3座",
        },
        ......
    ]
}

上面返回的结构中,seat 结构中的 section 表示所在区(也就是座位编码的首字母)

🔔 如果需要实现在线选座等功能,请使用该座位编号实现全部物理座位。

7.4 - 获取演出节目的排期日历

GET /pa/project/{project_id}/event?event_date1=2026-01-01&event_date2=2026-04-01

其中 event_date1 和 event_date2 均是格式为:yyyy-mm-dd 格式的日期,表示要查询该起止日期(包含起止日期)内的所有排期情况。

请注意:event_date2 最大为 未来90天,event_date1 必须 >= 今天(不能查询过去的演出日历)

接口返回以下内容:

[
    {
        "event_id": 1,
        "event_date": "2026-03-05",
        "slot_time": "14:00:00",
        "slot_id": 1,
        "slot_name": "午场",
        "price": {
            "A": [
                {
                    "ticket_type": "a1",
                    "ticket_price": 9.88,
                    "ticket_name": "成人"
                },
                {
                    "ticket_type": "c1",
                    "ticket_price": 5.68,
                    "ticket_name": "儿童"
                }
            ],
            "B": [
                {
                    "ticket_type": "a1",
                    "ticket_price": 1.99,
                    "ticket_name": "成人"
                },
                {
                    "ticket_type": "c1",
                    "ticket_price": 2.37,
                    "ticket_name": "儿童"
                }
            ],
            "C": [
                {
                    "ticket_type": "a1",
                    "ticket_price": 3.69,
                    "ticket_name": "成人"
                },
                {
                    "ticket_type": "c1",
                    "ticket_price": 5.42,
                    "ticket_name": "儿童"
                }
            ],
            .....
        }
    },
    {
        "event_id": 2,
        "event_date": "2026-03-05",
        "slot_time": "14:00:00",
        "slot_id": 1,
        "slot_name": "午场",
        "price": {
            "A": [
                {
                    "ticket_type": "a1",
                    "ticket_price": 9.88,
                    "ticket_name": "成人"
                },
                {
                    "ticket_type": "c1",
                    "ticket_price": 5.68,
                    "ticket_name": "儿童"
                }
            ],
            "B": [
                {
                    "ticket_type": "a1",
                    "ticket_price": 1.99,
                    "ticket_name": "成人"
                },
                {
                    "ticket_type": "c1",
                    "ticket_price": 2.37,
                    "ticket_name": "儿童"
                }
            ],
            ......
        }
    },
    {
        "event_id": 3,
        "event_date": "2026-03-20",
        "slot_time": "14:00:00",
        "slot_id": 1,
        "slot_name": "午场",
        "price": {
            ......
        }
    }
    .....
]

以上返回结构中:

event_date:演出的具体日期
slot_time:演出时间,
slot_name:场次的名字(可辅助使用)
slot_id:场次编号(可辅助使用)
price 结构中,会返回剧场各分区不同票种的结算价。(就是给你方的结算价)

💯 可以根据该接口生成对应的演出日历供用户选择具体的演出日期和场次。

⚠️ ticket_type 的值参考上文的字典。

7.5 - 获取具体演出的剩余可预订座位

用户一旦确定选择具体的演出 {event_id} 后,可以进一步查询该具体演出的剩余可预订座位。

GET /pa/project/{project_id}/event/{event_id}/inventory

将返回以下结构:

{
    "seats": [
        {
            "code": "C-2-1",
            "name": "C区2排1座"
        },
        {
            "code": "C-2-3",
            "name": "C区2排3座"
        },
        ....
    ]
}

⚠️ 这里只返回可以预订的座位,如果您需要在剧场座位SVG图中表示,可以结合 GET /pa/venue/{venue_id} 的返回结果计算。

考虑该接口查询较为频繁,且传输数据量较大,实际返回的将是 protobuf 编码格式 ⚠️ ⚠️ ⚠️ ,返回头 Content-Type: application/x-protobuf

请下载对应的 proto 文件:https://help.shzjt.com/proto/inventory.proto

7.6 - 下单订票

POST /pa/order

{
    "cart": [
        { "event_id": 1, "seat_code": "C-5-6", "ticket_type": "a1" }
        { "event_id": 1, "seat_code": "C-5-8", "ticket_type": "c1" }
    ],
    "guest": { "mobile": "13918112357", "name": "张三" },
    "out_oid": "你方系统中对应的订单号,最长64字符"
}

上例中,guest 为可选项,如果提供 guest,那么其完整结构为:

guest: { 
    "mobile": "11位手机号", 
    "name": "客人的姓名/昵称等", 
    "email": "用户邮件地址", 
    "idcard": "18位身份证", 
    "country": "ISO 3166-1国家编码的三位数字,比如中国为:156",
    "msg": "客人留言信息" 
}

可根据需要不提供或只提供子结构数据,比如只提供 mobile。

下单成功后,返回如下结构:

{
    "oid": "3",
    "net_amount": 3.69,
    "pay_expired_at": "2026-03-04 19:48:40"
}

其中,
oid:我方产生的唯一订单号;
net_amount:该订单与你方的结算总价(人民币);
pay_expired_at:该订单最后付款截止时间(北京时间),超过该时间未付款,则订单被取消,座位被释放。

7.7 - 为订单付款

订单生成后,并不马上出票,你方需要在订单付款超时时间内完成付款,然后才会出票,订单超时未付款,订单内的座位将被释放,同时订单关闭。

🌼 目前,通过 API 对接的合作方仅支持通过 “账户余额“ 或者 “ 信用 ” 进行付款。(具体请咨询我方相关业务人员)

POST /pa/order/{oid}/pay

{
    "gw": "debit或者credit"
}

上面的 gw 只可以取 debitcredit 这两个可选值,请提供其中之一,分别表示使用 账户余额信用 付款。

付款成功后,接口返回:

{
    "oid": 2,
    "order_status": "issued",
    "pay_status": "paid",
    "net_amount": "5.68",
    "headcount": 1,
    "eticket_qrcode": "1-XX-XXXX",
    
    ........

    "cart": [
        {
            "project_id": 1,
            "event_id": 1,
            "event_date": "2026-01-06",
            "slot_time": "14:00:00",
            "ticket_type": "a1",
            "ticket_name": "成人",
            "seat_code": "A-5-24",
            "seat_name": "A区5排24座",
            "price": "5.68"
        }
    ]  
}

上面返回的结构中,eticket_qrcode:电子票二维码内容,请自行根据该内容生成二维码,用户将凭此二维码核销进场。

⚠️ 一个订单无论多少人(座位),均只有一个二维码,具体核销进场由我方门票核销人员现场控制:

7.8 - 查询订单

可以根据我方的{oid}查询订单

GET /pa/order/{oid}

上面返回的结构中,同付款成功后返回的结构一样。
如果未付款,将不会返回 eticket_qrcode

7.9 - 取消订单

原则上已出票订单不支付退改。

目前本接口只支持取消未付款订单,也可以不主动取消,等待系统超时候自动取消,如已付款订单需要取消,请联系相应的客户经理。

POST /pa/order/{oid}/cancel

如果取消成功,返回结构如下:

{
    "order_status": "cancelled", 
    "pay_status": "closed"
}

如果取消失败,则返回标准错误结构,类似如下:

{
    "code": 801403,
    "message": "当前订单状态:cancelled,无法取消",
}

八、⚠️ 安全和性能提示 ⚠️

我方接口对请求有限流和限速,请务必平滑所有请求,过多并发会导致接口返回 429 Too Many Requests

九、QA

9.1 - 在线选座

如您需要实现在线选座,上文已有提及,一般分为两步:

  1. 请先使用 GET /pa/project/{project_id} 获取项目所在场馆 venue 值,然后根据 GET /pa/venue/{venue_id} 获取该场馆所有物理座位信息,自行生成合适的 SVG 物理座位图并默认座位不可选状态。
  2. 根据 GET /pa/project/{project_id}/event/{event_id}/inventory,获取具体某一场可预订的座位,从而点亮第 1 步生成的座位,并编写 javascript click 选座功能。

9.2 - 订单核销

只要该订单核销过一次(无论是进场一人,还是多人),均认为该订单核销。目前不提供每个座位(每人)的核销状态。

9.3 - guest 对象

上文已有提及,guest 为客人信息(实际进场的客人),整体为可选,其子字段也为可选。一般建议提供 mobile 值,以便于现场如果发生订单核销争议,可以根据 mobile 快速查询到订单,进行后续协调处理。

十、WebHook(可选)

系统支持对接方在我方系统注册一个 webhook url,我方会将订单等状态变更信息实时异步回调通知到 webhook url。

10.1 - 注册 webhook url

⚠️ 测试环境下注册 webhook,不能使用上文 四、接口地址与协议规范 中的 公共 client_id 和 client_secret,需要我方提供独立的 client_idclient_secret 给你方。

建议先使用公共 client_id 和 client_secret 完成主线功能开发,推进到 webhook 对接的时候,再向我方申请特定 client_id,并配置 webhook。

10.2 - 订单状态回调通知信息

我方系统会向 webhook url POST 推送兼容 cloudevents 规范的消息,具体结构如下:

{
    "specversion":"1.0",
    "id":"d05d9792-6cda-42be-ad1d-ed31a47677a4",
    "source":"event.order.service",
    "type":"event.order.created",
    "time":"2026-04-06T13:26:58.069914Z",
    "data": {
        "oid": "123456",
        "order_status": "created"
    }
}

上述结构中:
data > oid 为我方系统订单号。
data > order_status 为订单状态,具体可以参考上文中的订单状态字典。
type 为事件类型,可选值如下:

订单状态事件编码(type值) 事件说明
event.order.created 订单创建成功
event.order.cancelled 订单已取消
event.order.issued 订单已出票
event.order.checked 电子票已核销(进场)

消息推动到 webhook 后,对于成功处理的事件,需要 webhook 返回 http status code : 202 表示已经成功接受(处理)该通知,我方将不再重发该消息。(我方忽略响应体中的内容,仅建议debug使用)

其他任何非 202 http status code,我方会认为失败,并触发消息重发,消息在失败后的 1秒3秒5秒10秒30秒120秒 内重试 6次
如果6次均失败,将不再重发该消息。

如因某些原因,你方无法有效处理 webhook 响应,建议使用上文中 GET /pa/order/{oid} 主动获取订单详情。

10.3 - 库存(座位)状态回调通知信息

每一场演出(event_id),座位状态发生变化后,我方系统会向 webhook url POST 推送兼容 cloudevents 规范的消息,具体结构如下:

{
    "specversion":"1.0",
    "id":"d05d9792-6cda-42be-ad1d-ed31a47677a4",
    "source":"event.inventory.service",
    "type":"event.seat.invalid",
    "time":"2026-04-06T13:26:58.069914Z",
    "data": {
        "event_id": 1234,
        "seat_code": [ "A-1-1", "A-1-3", "A-1-5", "A-1-7", ... ],
        "seat_status": "invaid"
    }
}

上述结构中:
data > event_id 为具体演出场次的 id 号(上述下订单的时候有使用到)。
data > seat_code 是一个数组,当前变化的座位编码。(参考上文)
data > seat_status 为座位状态,仅有两个可选值:

事件 type 取值如下:

座位状态事件编码 事件说明
event.seat.invalid 座位无效,不能被预定(比如已被预定了)
event.seat.valid 座位有效,可以被预定

消息推动到 webhook 后,对于成功处理的事件,需要 webhook 返回 http status code : 202 表示已经成功接受(处理)该通知,我方将不再重发该消息。(我方忽略响应体中的内容,仅建议debug使用)

其他任何非 202 http status code,我方会认为失败,并触发消息重发,消息在失败后的 1秒3秒5秒10秒30秒120秒 内重试 6次
如果6次均失败,将不再重发该消息。

如因某些原因,你方无法有效处理 webhook 响应,建议使用上文中 GET /pa/project/{project_id}/event/{event_id}/inventory 主动获取场次可预定座位信息。

10.4 - webhook url 监控

我方提供了 webhook url 监控,系统会每 10分钟 向 webhook url POST 发送以下内容:

{
    "specversion":"1.0",
    "id":"7b2f3b19-d1eb-4340-a69a-09dfc7d1a08e",
    "source":"webhook.monitor",
    "type":"ping",
    "time":"2026-04-06T13:54:48.12336Z"
}

webhook 需要返回 http status code : 202,表示状态正常,所有非 202 响应,均认为异常。

我们在连续检测到 3次 异常后(总共30分钟),会向您提供的 email 发送警告邮件。(该邮件24小时内只发送一次)

10.5 - webhook 安全

10.5.1 - 请求内容签名

为尽量保证你方 webhook 收到的请求是合法安全的,在对 webhook 的请求中,我们会对发送内容进行签名,并附加以下两个 HTTP Header 一起发送:

Webhook-Nonce: ********
Webhook-Signature: *******

其中,Webhook-Signature 为签名后的值,签名算法如下:

以为是以 go 语言实现的函数:

func signature(body, client_secret, nonce string) string {
    key := client_secret + nonce
	mac := hmac.New(sha256.New, []byte(key))
	mac.Write([]byte(body))
	return hex.EncodeToString(mac.Sum(nil))
}

签名过程是可选的,您可以自行选择是否对 http raw body 进行签名校验,以增强 webhook 安全性。

10.5.2 - IP 白名单

为进一步限制请求安全性,你方可以将我方请求 IP,加入白名单:

测试环境 IP:61.129.70.165
生产环境 IP:47.100.204.142