單錢包 API · 客戶端 Demo

本頁配套 單錢包 API 文檔裡 8-10 號「PetaX 服務 APIs」。這些是商戶主動調用 PetaX 的接口,Base URL 是 {API_BASE_URL}{SW_PATH_PREFIX}。本頁 Demo 只演示請求 URL、Header、查詢參數和 JSON body 的基本拼裝;示例函數只做最基礎的錯誤處理(非 2xx 拋錯),不是生產級 SDK,也不包含商戶側 token 管理、重試策略、日誌、監控、金額精度封裝或業務編排。交易流水接口的響應字段(balanceChange/priorBalance/createdAt/id/userId/username/ccy)與 notify-change-balance 返回空字符串,均取自書面 API 文檔,尚未拿真實 staging PetaX 賬號做過實際請求核對——這一步待辦(需真實 bearer token)。

PHP

依賴:無(內置 curl)。

<?php
// client.php — PetaX single-wallet-api 客戶端 Demo(PHP,調用 PetaX 服務 API)
// 依賴:無(使用內置 curl)
// 僅用於 Demo:這裡演示請求構造和基礎非 2xx 錯誤處理;生產客戶端可按需補充 token 管理、重試/超時、結構化日誌和金額精度處理。

$API_BASE_URL   = getenv('PETAX_API_BASE_URL') ?: 'https://your-petax-host.example';
$SW_PATH_PREFIX = getenv('PETAX_SW_PATH_PREFIX') ?: '/api/0857-sw';
$BEARER_TOKEN   = getenv('PETAX_BEARER_TOKEN') ?: '';

function petax_request(string $method, string $path, ?array $query = null, ?array $body = null): array {
    global $API_BASE_URL, $SW_PATH_PREFIX, $BEARER_TOKEN;
    $url = $API_BASE_URL . $SW_PATH_PREFIX . $path;
    if ($query) {
        $url .= '?' . http_build_query(array_filter($query, fn($v) => $v !== null));
    }
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . $BEARER_TOKEN,
            'Content-Type: application/json',
        ],
    ]);
    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }
    $raw = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    // 這裡不調用 curl_close($ch):PHP 8.0 起它已是空操作,PHP 8.5 起會觸發棄用警告。
    if ($status < 200 || $status >= 300) {
        throw new RuntimeException("PetaX API error ($status): $raw");
    }
    return ['status' => $status, 'body' => $raw === '' ? null : json_decode($raw, true)];
}

function notify_change_balance(float $balance, string $ccy, string $username): void {
    petax_request('POST', '/a/sw/wallets/notify-change-balance', null, [
        'balance' => $balance, 'ccy' => $ccy, 'username' => $username,
    ]);
}

function list_transactions(?string $createdAtGte = null, ?string $createdAtLt = null, ?string $id = null, ?string $refId = null, int $page = 1, int $size = 10): array {
    $res = petax_request('GET', '/a/sw/transactions', [
        'createdAtGte' => $createdAtGte, 'createdAtLt' => $createdAtLt,
        'id' => $id, 'refId' => $refId, 'page' => $page, 'size' => $size,
    ]);
    return $res['body'];
}

function get_transaction_by_id(string $id): array {
    $res = petax_request('GET', "/a/sw/transactions/$id");
    return $res['body'];
}

Node.js

依賴:無(Node 18+ 內置 fetch)。

// client.js — PetaX single-wallet-api 客戶端 Demo(Node.js,調用 PetaX 服務 API)
// 運行:Node 18+(內置 fetch)
// 僅用於 Demo:這裡演示請求構造和基礎非 2xx 錯誤處理;生產客戶端可按需補充 token 管理、重試/超時、結構化日誌和金額精度處理。
const API_BASE_URL = process.env.PETAX_API_BASE_URL || 'https://your-petax-host.example';
const SW_PATH_PREFIX = process.env.PETAX_SW_PATH_PREFIX || '/api/0857-sw';
const BEARER_TOKEN = process.env.PETAX_BEARER_TOKEN || '';

async function petaxRequest(method, path, { query, body } = {}) {
  const url = new URL(API_BASE_URL + SW_PATH_PREFIX + path);
  if (query) {
    for (const [k, v] of Object.entries(query)) {
      if (v !== undefined && v !== null) url.searchParams.set(k, v);
    }
  }
  const res = await fetch(url, {
    method,
    headers: { Authorization: `Bearer ${BEARER_TOKEN}`, 'Content-Type': 'application/json' },
    body: body ? JSON.stringify(body) : undefined,
  });
  const text = await res.text();
  if (!res.ok) throw new Error(`PetaX API error (${res.status}): ${text}`);
  return text ? JSON.parse(text) : null;
}

async function notifyChangeBalance(balance, ccy, username) {
  return petaxRequest('POST', '/a/sw/wallets/notify-change-balance', { body: { balance, ccy, username } });
}

async function listTransactions({ createdAtGte, createdAtLt, id, refId, page = 1, size = 10 } = {}) {
  return petaxRequest('GET', '/a/sw/transactions', { query: { createdAtGte, createdAtLt, id, refId, page, size } });
}

async function getTransactionById(id) {
  return petaxRequest('GET', `/a/sw/transactions/${id}`);
}

module.exports = { notifyChangeBalance, listTransactions, getTransactionById };

Java

依賴:JDK 11+ 內置 java.net.http.HttpClient

package com.example.petax;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class PetaxClient {
    private static final String API_BASE_URL = System.getenv().getOrDefault("PETAX_API_BASE_URL", "https://your-petax-host.example");
    private static final String SW_PATH_PREFIX = System.getenv().getOrDefault("PETAX_SW_PATH_PREFIX", "/api/0857-sw");
    private static final String BEARER_TOKEN = System.getenv().getOrDefault("PETAX_BEARER_TOKEN", "");

    // 僅用於 Demo:這裡演示請求構造和基礎非 2xx 錯誤處理;生產客戶端可按需補充 token 管理、重試/超時、結構化日誌和金額精度處理。
    private final HttpClient http = HttpClient.newHttpClient();

    private String request(String method, String path, String query, String jsonBody) throws Exception {
        String url = API_BASE_URL + SW_PATH_PREFIX + path + (query != null ? "?" + query : "");
        var builder = HttpRequest.newBuilder(URI.create(url))
                .header("Authorization", "Bearer " + BEARER_TOKEN)
                .header("Content-Type", "application/json");
        builder = jsonBody != null
                ? builder.method(method, HttpRequest.BodyPublishers.ofString(jsonBody))
                : builder.method(method, HttpRequest.BodyPublishers.noBody());
        HttpResponse<String> res = http.send(builder.build(), HttpResponse.BodyHandlers.ofString());
        if (res.statusCode() < 200 || res.statusCode() >= 300) {
            throw new RuntimeException("PetaX API error (" + res.statusCode() + "): " + res.body());
        }
        return res.body();
    }

    public String notifyChangeBalance(double balance, String ccy, String username) throws Exception {
        String body = String.format("{\"balance\":%s,\"ccy\":\"%s\",\"username\":\"%s\"}", balance, ccy, username);
        return request("POST", "/a/sw/wallets/notify-change-balance", null, body);
    }

    public String listTransactions(String createdAtGte, String createdAtLt, String id, String refId, int page, int size) throws Exception {
        StringBuilder q = new StringBuilder("page=" + page + "&size=" + size);
        if (createdAtGte != null) q.append("&createdAtGte=").append(createdAtGte);
        if (createdAtLt != null) q.append("&createdAtLt=").append(createdAtLt);
        if (id != null) q.append("&id=").append(id);
        if (refId != null) q.append("&refId=").append(refId);
        return request("GET", "/a/sw/transactions", q.toString(), null);
    }

    public String getTransactionById(String id) throws Exception {
        return request("GET", "/a/sw/transactions/" + id, null, null);
    }
}

Go

依賴:無(標準庫 net/http)。放在獨立的 client 包裡,方便和你自己的服務代碼放在一起。

// client.go — PetaX single-wallet-api 客戶端 Demo(Go,調用 PetaX 服務 API)
package petaxclient

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
)

// 僅用於 Demo:這裡演示請求構造和基礎非 2xx 錯誤處理;生產客戶端可按需補充 token 管理、重試/超時、結構化日誌和金額精度處理。
var (
	apiBaseURL   = getenvOr("PETAX_API_BASE_URL", "https://your-petax-host.example")
	swPathPrefix = getenvOr("PETAX_SW_PATH_PREFIX", "/api/0857-sw")
	bearerToken  = getenvOr("PETAX_BEARER_TOKEN", "")
)

func getenvOr(k, def string) string {
	if v := os.Getenv(k); v != "" {
		return v
	}
	return def
}

func petaxRequest(method, path string, query url.Values, body interface{}) ([]byte, error) {
	u := apiBaseURL + swPathPrefix + path
	if query != nil {
		u += "?" + query.Encode()
	}
	var reader io.Reader
	if body != nil {
		b, _ := json.Marshal(body)
		reader = bytes.NewReader(b)
	}
	req, err := http.NewRequest(method, u, reader)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+bearerToken)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBody, _ := io.ReadAll(resp.Body)
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("PetaX API error (%d): %s", resp.StatusCode, respBody)
	}
	return respBody, nil
}

func NotifyChangeBalance(balance float64, ccy, username string) error {
	_, err := petaxRequest("POST", "/a/sw/wallets/notify-change-balance", nil, map[string]interface{}{
		"balance": balance, "ccy": ccy, "username": username,
	})
	return err
}

func ListTransactions(createdAtGte, createdAtLt, id, refId string, page, size int) ([]map[string]interface{}, error) {
	q := url.Values{}
	if createdAtGte != "" {
		q.Set("createdAtGte", createdAtGte)
	}
	if createdAtLt != "" {
		q.Set("createdAtLt", createdAtLt)
	}
	if id != "" {
		q.Set("id", id)
	}
	if refId != "" {
		q.Set("refId", refId)
	}
	q.Set("page", fmt.Sprintf("%d", page))
	q.Set("size", fmt.Sprintf("%d", size))
	body, err := petaxRequest("GET", "/a/sw/transactions", q, nil)
	if err != nil {
		return nil, err
	}
	var out []map[string]interface{}
	if err := json.Unmarshal(body, &out); err != nil {
		return nil, err
	}
	return out, nil
}

func GetTransactionByID(id string) (map[string]interface{}, error) {
	body, err := petaxRequest("GET", "/a/sw/transactions/"+id, nil, nil)
	if err != nil {
		return nil, err
	}
	var out map[string]interface{}
	if err := json.Unmarshal(body, &out); err != nil {
		return nil, err
	}
	return out, nil
}

Python

依賴:pip install requests

# client.py — PetaX single-wallet-api 客戶端 Demo(Python,調用 PetaX 服務 API)
import os

import requests

API_BASE_URL = os.environ.get("PETAX_API_BASE_URL", "https://your-petax-host.example")
SW_PATH_PREFIX = os.environ.get("PETAX_SW_PATH_PREFIX", "/api/0857-sw")
BEARER_TOKEN = os.environ.get("PETAX_BEARER_TOKEN", "")

# 僅用於 Demo:這裡演示請求構造和基礎非 2xx 錯誤處理;
# 生產客戶端可按需補充 token 管理、重試/超時、結構化日誌和金額精度處理。
def _request(method, path, params=None, json_body=None):
    url = API_BASE_URL + SW_PATH_PREFIX + path
    resp = requests.request(
        method, url,
        params={k: v for k, v in (params or {}).items() if v is not None},
        json=json_body,
        headers={"Authorization": f"Bearer {BEARER_TOKEN}"},
    )
    if not resp.ok:
        raise RuntimeError(f"PetaX API error ({resp.status_code}): {resp.text}")
    return resp.json() if resp.text else None


def notify_change_balance(balance, ccy, username):
    return _request("POST", "/a/sw/wallets/notify-change-balance", json_body={
        "balance": balance, "ccy": ccy, "username": username,
    })


def list_transactions(created_at_gte=None, created_at_lt=None, id=None, ref_id=None, page=1, size=10):
    return _request("GET", "/a/sw/transactions", params={
        "createdAtGte": created_at_gte, "createdAtLt": created_at_lt,
        "id": id, "refId": ref_id, "page": page, "size": size,
    })


def get_transaction_by_id(id):
    return _request("GET", f"/a/sw/transactions/{id}")