接入概览

星潮提供两个收款入口,均为服务端对服务端接入,需要先由星潮为你开通接入方并发放密钥:

入口适用场景金额单位(下单)能否指定用户
H5 收银台你的系统给星潮 App 用户代充:传入用户 ID,买家在手机上打开我们的收银页付款,到账后金币直接加到该用户元(字符串,如 "10.00")可传 uid;不传则按付款人自动生成账号
星潮第三方你的商城/业务系统收款:我们返回收银页链接,买家付款后异步通知你分(整数,如 1000)不指定用户
一个接入方可以同时开通两个入口,也可以只开通其中一个。入口未开通时,即使签名正确也会返回 403。

你需要向星潮申请

接口地址:https://appapi.cstfparty.com(下文接口路径均相对此域名)。返回的 pay_url 是收银页链接,域名由星潮分配(H5 收银台为 cashier.xcparty.com,星潮第三方为 pay.xcparty.com),请直接使用返回值,不要自行拼接或写死域名。无需提供 IP 白名单;如需限定来源 IP,可让星潮在后台为你配置。

鉴权与签名

所有接入方接口(下单、查单)都需要在请求头携带:

请求头说明
X-App-Id接入方标识
X-Timestamp当前 Unix 时间戳(秒)。与服务器时间相差超过 300 秒会被拒绝,请保持服务器时钟准确(NTP)
X-Sign签名,见下
Content-Typeapplication/json

签名算法

X-Sign = UPPER( MD5( 请求体原文 + X-Timestamp + app_secret ) )

测试向量(自检用)

以下数据可用来验证你的签名实现是否正确,app_secret 为演示值 demo_secret_123456,X-Timestamp 为 1760860800:

请求体原文期望的 X-Sign
{"out_order_no":"ORDER20261019001","amount":"10.00","uid":"10001","subject":"充值10元"}AF5801E7C32EC2EE326AC42DF0BA1237
{"out_order_no":"ORDER20261019002","amount":1000,"subject":"充值10元"}87F82C843AFEBFC1CED42E20325C6AEB
{"out_order_no":"ORDER20261019001"}A91E24D27BC1BC997DDE7C8D8C7C8297
时间戳校验是防重放的唯一手段之一。请使用每次请求当下的时间戳,不要缓存复用。

通用约定

幂等

以 (接入方, out_order_no) 为幂等键。同一个 out_order_no 重复下单:参数一致则返回原订单(含同一个收银页链接),不会重复生成订单;金额或用户与首次不一致,返回 12002。因此网络超时后可以放心用同一单号重试。

订单状态(查单、回调通用)

status含义
1支付中(未支付)
2支付成功
6已关闭(超时未支付)

请以 status=2 作为发货/到账的唯一依据,不要以买家是否跳转回你的页面为准。

H5 收银台 · 流程

  1. 你的服务端调用下单,传入金额、uid、你方单号。
  2. 我们返回 pay_url(收银页链接)。
  3. 买家在手机浏览器打开 pay_url,完成支付宝付款(收银页免登录、免签名)。
  4. 付款成功后,金币自动加到 uid 对应用户;同时我们向你的 notify_url 发送到账回调。
  5. 你也可以随时调用查单主动核对。
收银页有效期默认 2 分钟(以返回的 expire_at 为准)。过期后需用新的单号重新下单。
安全提醒:带 uid 下单意味着密钥持有方可以给任意用户充值。请严格保管 app_secret,一旦泄露立即联系星潮重置。

H5 收银台 · 下单

POST/cashier/v1/create

请求体(JSON)

字段类型必填说明
out_order_nostring是你方订单号,≤64 位,仅限字母、数字及 _ - . :。同一接入方内唯一,用于幂等
amountstring是金额,单位元,最多两位小数,如 "10.00"。带 uid 时必须为整数元。单笔范围 1 ~ 5000 元(实际以星潮配置为准)
uidstring否星潮用户 ID(纯数字)。用户须存在且状态正常。金币按 1 元 = 10 金币 到账。不传则按付款人自动生成账号并发放金币
subjectstring否商品标题,≤64 位
notify_urlstring否到账回调地址,必须是公网可访问的 http(s)。不传则使用接入方默认通知地址
device_idstring否买家设备标识,用于风控,≤128 位

请求示例

POST /cashier/v1/create HTTP/1.1
Host: appapi.cstfparty.com
Content-Type: application/json
X-App-Id: shop_a
X-Timestamp: 1760860800
X-Sign: AF5801E7C32EC2EE326AC42DF0BA1237

{"out_order_no":"ORDER20261019001","amount":"10.00","uid":"10001","subject":"充值10元"}

响应

{
  "code": 200,
  "message": "success",
  "data": {
    "order_no": "RC_4afc1326-84b3-4cac-bed4-ac3b019eab2d",
    "out_order_no": "ORDER20261019001",
    "token": "p3Qx...(43 位)",
    "pay_url": "https://cashier.xcparty.com/p/p3Qx...",
    "amount": 1000,
    "amount_yuan": "10.00",
    "expire_at": 1760860920000,
    "expire_seconds": 120
  }
}
字段说明
order_no星潮订单号
pay_url收银页链接,让买家在手机浏览器打开
amount金额,单位分
expire_at过期时间,Unix 毫秒
H5 收银台的响应使用 code: 200(数字)判断成功;星潮第三方接口使用 "code":"200"(字符串)。两者格式不同,请分别处理。

H5 收银台 · 查单

POST/cashier/v1/query

请求体 {"out_order_no":"ORDER20261019001"},同样需要签名。仅能查询自己名下的订单。

{
  "code": 200,
  "message": "success",
  "data": {
    "out_order_no": "ORDER20261019001",
    "order_no": "RC_4afc1326-...",
    "amount": 1000,
    "status": 2,
    "paid_at": 1760860860
  }
}

amount 单位分;paid_at 为 Unix 秒,未支付时为 0。

星潮第三方 · 流程

  1. 你的服务端调用下单,传入金额(分)与你方单号。
  2. 我们返回 pay_url(收银页链接)。
  3. 买家在手机浏览器打开链接付款。
  4. 付款成功后我们向 notify_url 发送到账回调。
第三方收银页有效期默认 30 分钟。

星潮第三方 · 下单

POST/openapi/v1/pay/create

请求体(JSON)

字段类型必填说明
out_order_nostring是你方订单号,≤64 位,同一接入方内唯一,用于幂等
amountinteger是金额,单位分,正整数(如 10 元传 1000)
subjectstring否商品标题,≤128 位
notify_urlstring否到账回调地址,必须是公网可访问的 http(s)。不传则使用接入方默认通知地址
client_ipstring否买家真实 IP,用于风控
device_idstring否买家设备标识,用于风控

请求示例

POST /openapi/v1/pay/create HTTP/1.1
Host: appapi.cstfparty.com
Content-Type: application/json
X-App-Id: shop_a
X-Timestamp: 1760860800
X-Sign: 87F82C843AFEBFC1CED42E20325C6AEB

{"out_order_no":"ORDER20261019002","amount":1000,"subject":"充值10元"}

响应

{
  "code": "200",
  "msg": "success",
  "content": {
    "out_order_no": "ORDER20261019002",
    "mch_order_no": "RC_8d1f...",
    "pay_url": "https://pay.xcparty.com/p/Zk3...",
    "cashier_url": "",
    "scan_url": ""
  }
}

让买家打开 pay_url 付款。cashier_url / scan_url 为保留字段,通常为空,请勿依赖。

星潮第三方 · 查单

POST/openapi/v1/pay/query

请求体 {"out_order_no":"ORDER20261019002"},同样需要签名。

{
  "code": "200",
  "msg": "success",
  "content": {
    "out_order_no": "ORDER20261019002",
    "mch_order_no": "RC_8d1f...",
    "amount": 1000,
    "status": 2,
    "paid_at": 1760860860
  }
}

到账回调

订单支付成功后,我们向 notify_url 发送一次 HTTP POST(JSON)。两个入口的回调格式一致。

请求头

请求头说明
X-App-Id你的接入方标识
X-TimestampUnix 秒,等于请求体中的 timestamp
X-SignUPPER(MD5(请求体原文 + X-Timestamp + app_secret)),与下单签名同一算法

请求体

{"app_id":"shop_a","out_order_no":"ORDER20261019001","mch_order_no":"RC_4afc1326-...","amount":1000,"status":2,"paid_at":1760860860,"timestamp":1760860861}
字段说明
out_order_no你方订单号
mch_order_no星潮订单号
amount实付金额,单位分
status固定为 2(支付成功)
paid_at支付时间,Unix 秒

你需要做的

  1. 验签:用请求体原文重新计算签名并与 X-Sign 比对,不一致一律丢弃。
  2. 幂等处理:同一订单可能收到多次回调,以 out_order_no 去重。
  3. 处理成功后返回 HTTP 2xx,且响应体为纯文本 SUCCESS(不区分大小写,首尾空白会被忽略)。
返回其它任何内容、非 2xx 或超时,都视为未确认,我们会按 10 秒 → 20 秒 → 40 秒 → 1 分 → 1.5 分 → 2 分 → 5 分 → 10 分 → 30 分 → 1 小时 → 4 小时 → 12 小时 → 24 小时 递增间隔重试。回调只是通知,请务必配合查单接口兜底。
notify_url 必须是公网可访问的 http(s) 地址,内网、本机地址会被拒绝。

错误码

失败时 H5 收银台返回 {"code":错误码,"message":"..."};星潮第三方返回 {"code":"错误码","msg":"..."}。

错误码含义处理建议
400参数错误(缺字段、金额格式不对、notify_url 不合法、out_order_no 格式不对等)按 message 修正后重试
401鉴权失败:缺请求头、接入方不存在、时间戳过期、签名错误核对 app_id、密钥、时钟、签名基串
403接入方被禁用 / 来源 IP 不在白名单 / 未开通该入口联系星潮开通
404订单不存在(查单时)确认 out_order_no 与入口一致
10001用户不存在(H5 下单传了 uid)核对用户 ID
12002同一 out_order_no 已存在,但金额或用户与首次不一致换新单号
12003支付渠道繁忙稍后重试
12004被风控拦截联系星潮
12005订单已过期用新单号重新下单
500系统繁忙稍后用同一单号重试(幂等)

示例代码

以下示例演示下单签名。请求体字符串必须与参与签名的字符串完全一致。

import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class StarTidePay {
    static String sign(String body, String ts, String secret) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] d = md.digest((body + ts + secret).getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte b : d) sb.append(String.format("%02X", b));
        return sb.toString();
    }

    public static void main(String[] a) throws Exception {
        String appId = "shop_a", secret = "你的密钥";
        String body = "{\"out_order_no\":\"ORDER20261019001\",\"amount\":\"10.00\",\"uid\":\"10001\"}";
        String ts = String.valueOf(System.currentTimeMillis() / 1000);
        HttpRequest req = HttpRequest.newBuilder(URI.create("https://appapi.cstfparty.com/cashier/v1/create"))
            .header("Content-Type", "application/json")
            .header("X-App-Id", appId).header("X-Timestamp", ts)
            .header("X-Sign", sign(body, ts, secret))
            .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build();
        System.out.println(HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()).body());
    }
}
import hashlib, time, requests

app_id, secret = "shop_a", "你的密钥"
body = '{"out_order_no":"ORDER20261019001","amount":"10.00","uid":"10001"}'
ts = str(int(time.time()))
sign = hashlib.md5((body + ts + secret).encode("utf-8")).hexdigest().upper()

r = requests.post(
    "https://appapi.cstfparty.com/cashier/v1/create",
    data=body.encode("utf-8"),            # 注意:发送的就是参与签名的那串字节
    headers={"Content-Type": "application/json",
             "X-App-Id": app_id, "X-Timestamp": ts, "X-Sign": sign})
print(r.json())
<?php
$appId = 'shop_a'; $secret = '你的密钥';
$body = '{"out_order_no":"ORDER20261019001","amount":"10.00","uid":"10001"}';
$ts = (string) time();
$sign = strtoupper(md5($body . $ts . $secret));

$ch = curl_init('https://appapi.cstfparty.com/cashier/v1/create');
curl_setopt_array($ch, [
  CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Content-Type: application/json',
    "X-App-Id: $appId", "X-Timestamp: $ts", "X-Sign: $sign"],
]);
echo curl_exec($ch);
const crypto = require('crypto');

const appId = 'shop_a', secret = '你的密钥';
const body = '{"out_order_no":"ORDER20261019001","amount":"10.00","uid":"10001"}';
const ts = String(Math.floor(Date.now() / 1000));
const sign = crypto.createHash('md5').update(body + ts + secret, 'utf8').digest('hex').toUpperCase();

fetch('https://appapi.cstfparty.com/cashier/v1/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-App-Id': appId, 'X-Timestamp': ts, 'X-Sign': sign },
  body,                                   // 与参与签名的字符串完全一致
}).then(r => r.json()).then(console.log);
APP_ID=shop_a; SECRET='你的密钥'
BODY='{"out_order_no":"ORDER20261019001","amount":"10.00","uid":"10001"}'
TS=$(date +%s)
SIGN=$(printf '%s%s%s' "$BODY" "$TS" "$SECRET" | md5sum | cut -d' ' -f1 | tr 'a-f' 'A-F')

curl -s https://appapi.cstfparty.com/cashier/v1/create \
  -H 'Content-Type: application/json' \
  -H "X-App-Id: $APP_ID" -H "X-Timestamp: $TS" -H "X-Sign: $SIGN" \
  --data-binary "$BODY"

回调验签示例(Python / Flask)

from flask import Flask, request
import hashlib

app = Flask(__name__)
SECRET = "你的密钥"

@app.post("/pay/notify")
def notify():
    raw = request.get_data(as_text=True)            # 必须用原文,不要先解析再序列化
    ts = request.headers.get("X-Timestamp", "")
    sign = hashlib.md5((raw + ts + SECRET).encode()).hexdigest().upper()
    if sign != request.headers.get("X-Sign", "").upper():
        return "bad sign", 400
    data = request.get_json()
    # 以 data["out_order_no"] 幂等处理;status == 2 才发货/记账
    return "SUCCESS"                                  # 纯文本,2xx

常见问题

总是提示签名错误(401)?

H5 下单提示「充值到账号时金额须为整元」?

传了 uid 时,金币按 1 元 = 10 金币整数到账,因此金额必须是整数元(如 "10.00"、"38.00")。

买家付款了但我没收到回调?

先调用查单确认 status。回调失败会自动重试,但你的服务必须按要求返回 SUCCESS;同时请确认回调地址公网可达、证书有效。

收银页过期了怎么办?

已过期的订单不能再支付。请使用新的 out_order_no 重新下单。若买家在过期前已发起付款,到账仍会正常处理并通知你,请以查单/回调结果为准。

可以用同一个密钥同时接两个入口吗?

可以,前提是星潮已为你的接入方开通了对应入口。签名规则完全相同,只是请求路径和金额单位不同。