Tài liệu tích hợp
Trang này chưa có bản Tiếng Việt; bên dưới là bản English.

SDK downloads

The payout API std1 has SDKs in six languages: Java, PHP, Python, Go, Node.js and .NET. All six behave identically: the same parameters produce the same request bytes, and the same response is classified the same way with the same hint. None has an address built in; you configure the API address, merchant ID and key. The collection API compat1 (legacy protocol) has no SDK.

Downloads

LanguageMinimum versionFileSizeSHA-256
JavaJava 8std1-sdk-java-1.0.0.zip89 KB6e5c3980a03f04cd48968b6425159f5110b1a0171fd89769fef7b342d91e8c4c
PHPPHP 7.4std1-sdk-php-1.0.0.zip93 KB340dfd49cc9c89e90aea527fbe91eaa5de39b5c694ee20bc6fce0902a589ee12
PythonPython 3.7std1-sdk-python-1.0.0.zip63 KBdec28311467ae757b22299e6f9d4e40db9cf0134f41c0b95f3ad84d2ad836d47
GoGo 1.18std1-sdk-go-1.0.0.zip82 KBd3e680e4fe1f85d511319d90016e7c233caab934f72377753b15b2eadff628fc
Node.js(TypeScript)Node.js 16std1-sdk-node-1.0.0.zip120 KB9dafafaafaf666cc404149596e56d3ca6bb561e9344f2f696472fbf5ac86d327
.NET.NET Standard 2.0std1-sdk-dotnet-1.0.0.zip99 KB3e322b359bcb2800303166022d21c924cc72f7690e3a0078b626ac001ca99fe8
all six—std1-sdk-all-1.0.0.zip398 KB79097c0dd230e8638c732f309049bc3612ba3ab5ef4868795d2ee896a29f2fc1

Configuration

Only five outcomes

OutcomeMeaningWhat to do
SUCCESSapply: accepted and funds frozen, not paid yet; query: found, see status; balance: returnedwait for the callback or query
NOT_PROCESSEDnothing was done (bad parameter, bad signature, IP not allowed, rate limited, unknown merchant ID)fix it and resend with the same order number
REJECTEDthe order is rejected (final) and this merchant order number is used up (except MERCHANT_DISABLED "商户未开通代付接口": not used up, resubmit with the same one once enabled)to pay out, submit with a new merchant order number
UNKNOWNresult unknown (timeout, broken connection, HTTP not 200, signature does not verify, system busy, order number conflict)retry with the same order number and identical parameters, or query
NOT_FOUNDthe query found nothingthis does not prove the order was never created

Examples

The examples below come from the README in each package.

Java

Apply for a payout

ApplyRequest request = ApplyRequest.builder()
        .mchOrderNo("ORDER-20260917-0001")
        .amountCents(Amounts.yuanToCents("19.99"))
        .accountName("张三")
        .accountNo("6222021234567890123")
        .bankName("中国工商银行")
        .notifyUrl("https://your-callback-host/std1/notify")
        .extra("productId", "2")   // optional payout product code, sent as an extra field; omit it to use your default payout product
        .build();
Std1Result result = client.applyPayout(request);
// SUCCESS = accepted and funds frozen, NOT paid yet; see section 8 for the rest.

Receive callbacks

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    byte[] body = readAll(req.getInputStream());          // 只读正文字节,别碰 getParameter*
    try {
        PayoutNotification n = client.parseNotification(body);
        saveIfNewer(n.getPayoutNo(), n.getStatus(), n);    // 你的落库逻辑(按 payoutNo+status 去重)
        resp.setStatus(200);
        resp.getWriter().write(Std1Client.SUCCESS_REPLY);
    } catch (InvalidNotificationException e) {
        resp.setStatus(400);                               // e.getReason(): malformed / signature / merchant / fields
        resp.getWriter().write("fail");
    }
}

PHP

Apply for a payout

$request = new \Std1\Payout\ApplyRequest([
    'mchOrderNo'  => 'A20260917000001',                               // unique per merchant: the idempotency key
    'amount'      => \Std1\Payout\Amounts::yuanToCents('100.00'),     // '10000' minor units; never 100.00 * 100
    'accountName' => 'Nguyen Van A',
    'accountNo'   => '6222021234567890123',
    'bankName'    => '中国工商银行',
    'notifyUrl'   => 'https://<your-callback-host>/std1/notify',     // optional
    'remark'      => 'salary',                                        // optional, echoed back
], ['myBatch' => '0917',                                              // optional merchant-defined fields
    'productId' => '2']);                                             // optional payout product code; omit it to use your default payout product

// Store these parameters with the order BEFORE sending: an UNKNOWN result must be retried with exactly the same parameters.
$result = $client->applyPayout($request);
// SUCCESS = accepted and funds frozen, NOT paid; REJECTED = rejected (final), this merchant order number is used up, use a NEW one to pay out;
// NOT_PROCESSED = fix and resend with the SAME order number; UNKNOWN = retry the same order number with identical parameters, or query.

Receive callbacks

$raw = file_get_contents('php://input');          // never $_POST or $_REQUEST
try {
    $notification = $client->parseNotification($raw);   // or Notification::parse($raw, $mchId, $key) without a client
} catch (\Std1\Payout\InvalidNotificationException $e) {
    http_response_code(400);
    echo 'fail';
    exit;
}
// Persist first (de-duplicate by payoutNo + status; accept only forward moves via Statuses::isNewer), then:
echo \Std1\Payout\Client::SUCCESS_REPLY;

Python

Apply for a payout

request = std1.ApplyRequest(mch_order_no="ORDER-20260917-0001", amount=std1.yuan_to_cents("19.99"), account_name="张三",
                            account_no="6222021234567890123", bank_name="中国工商银行", notify_url="https://your-host/std1/notify",
                            extra={"productId": "2"})  # optional payout product code; omit it to use your default payout product
result = client.apply_payout(request)
# SUCCESS: accepted and funds frozen (NOT paid yet) · REJECTED: the order is rejected (final) and this merchant order number is used up; use a NEW merchant order number to pay out
# NOT_PROCESSED: nothing happened; fix it and resend with the SAME number · UNKNOWN: retry with the same number and identical parameters, or query

Receive callbacks

@app.post("/std1/notify")
def std1_notify():
    try:
        notification = client.parse_notification(request.get_data())   # raw body bytes
    except std1.InvalidNotification:
        return "fail", 400                                            # never reply success to an invalid callback
    save_once(notification.payout_no, notification.status, notification)   # persist first (dedupe on payoutNo + status)
    return std1.SUCCESS_REPLY, 200

Go

Apply for a payout

cents, _ := std1payout.YuanToCents("19.99")
result, err := client.ApplyPayout(ctx, std1payout.ApplyRequest{
    MchOrderNo: "ORDER-20260917-0001", Amount: cents,
    AccountName: "张三", AccountNo: "6222021234567890123", BankName: "中国工商银行",
    NotifyURL: "https://your-callback/std1/notify",
    Extra: []std1payout.Param{{Name: "productId", Value: "2"}}, // optional payout product code; omit it to use your default payout product
})
// err != nil only for invalid input (nothing was sent).
// result.Outcome: SUCCESS (accepted, funds frozen, NOT paid yet), REJECTED (use a NEW order number to pay),
// NOT_PROCESSED (fix and resend with the SAME order number), UNKNOWN (retry with the same order number and
// identical parameters, or query; never switch order numbers).

Receive callbacks

http.Handle("/std1/notify", client.NotificationHandler(func(n *std1payout.Notification) error {
    // de-duplicate by n.PayoutNo + n.Status, accept only forward moves (IsNewerStatus), store first
    return saveResult(n) // nil → HTTP 200 "success"; error → 500 and the gateway retries later
}))

Node.js(TypeScript)

Apply for a payout

const result = await client.applyPayout({
  mchOrderNo: 'ORDER-20260917-0001',  // retries MUST reuse the same order number and identical parameters
  amount: yuanToCents('100.00'),       // integer minor units only: '10000', 10000 or 10000n
  accountName: 'Nguyen Van A',
  accountNo: '6222021234567890123',
  bankName: 'BANK NAME',
  notifyUrl: 'https://YOUR_CALLBACK_HOST/std1/notify',
  remark: 'salary',
  extra: [
    ['myTraceId', 'T-1'],              // optional merchant-defined fields (signed, ignored by the gateway)
    ['productId', '2'],                // optional payout product code; omit it to use your default payout product
  ],
});

if (result.outcome === Outcome.SUCCESS) {
  // accepted and funds frozen, NOT paid yet — wait for the callback or query
} else if (result.outcome === Outcome.REJECTED) {
  // the order is rejected (final) and this merchant order number is used up; use a NEW merchant order number to pay out
} else if (result.outcome === Outcome.NOT_PROCESSED) {
  // nothing was done; fix the cause and resend with the SAME order number
} else {
  // UNKNOWN: retry with the same order number and identical parameters, or query; never switch order numbers
}

Receive callbacks

app.post('/std1/notify', express.raw({ type: '*/*', limit: '64kb' }), async (req, res) => {
  let notification;
  try {
    notification = client.parseNotification(req.body); // Buffer — never the parsed object, never merged with req.query
  } catch (error) {
    return res.status(400).type('text/plain').send('fail'); // do not reply success; the gateway will retry
  }
  await saveOnce(notification.payoutNo, notification.status, notification); // store first, then reply
  res.status(200).type('text/plain').send(SUCCESS_REPLY);
});

.NET

Apply for a payout

var request = new ApplyRequest
{
    MchOrderNo = "ORDER-20260917-0001",      // your order number: unique per merchant, the idempotency key
    Amount = Amounts.YuanToCents("100.00"),  // "10000" — always minor units; or request.SetAmountCents(10000)
    AccountName = "Nguyen Van A",
    AccountNo = "6222021234567890123",
    BankName = "中国工商银行",
    NotifyUrl = "https://回调地址/std1/notify",  // optional; without it there is no callback, only queries
    Remark = "salary",                         // optional; echoed by queries and callbacks
};
request.AddExtra("myTag", "vip");             // optional merchant fields: signed, ignored by the gateway
request.AddExtra("productId", "2");           // optional payout product code; omit it to use your default payout product

// Store the order number and these exact parameters first: an UNKNOWN result must be resent unchanged
Std1Result result = await client.ApplyPayoutAsync(request);
switch (result.Outcome)
{
    case Outcome.Success:       // accepted and frozen, NOT paid yet: wait for the callback or query
        SavePayoutNo(request.MchOrderNo, result.PayoutNo);
        break;
    case Outcome.Rejected:      // rejected: the order is rejected (final) and this merchant order number is used up; use a NEW merchant order number to pay out
        MarkRejected(request.MchOrderNo, result.Code, result.Message);
        break;
    case Outcome.NotProcessed:  // not processed: fix the cause and resend with the SAME order number
        Alert(result.HintEn);
        break;
    default:                    // Unknown: resend with the same number and identical parameters, or query
        ScheduleRetryOrQuery(request.MchOrderNo);
        break;
}

Receive callbacks

app.MapPost("/std1/notify", async (HttpRequest http) =>
{
    byte[] body;
    using (var buffer = new MemoryStream())
    {
        await http.Body.CopyToAsync(buffer);
        body = buffer.ToArray();
    }
    PayoutNotification n;
    try
    {
        n = client.ParseNotification(body);
    }
    catch (InvalidNotificationException e)       // e.Reason: malformed / signature / merchant / fields
    {
        Console.Error.WriteLine("invalid std1 callback: " + e.Reason);
        return Results.Text("fail", statusCode: 400);
    }
    await SaveIfNewerAsync(n);                   // dedupe by PayoutNo + Status; accept forward moves only
    return Results.Text(Std1Client.SuccessReply);
});