ユニバーサルゲートウェイ すべてのメタバース、一つの通貨
ユニバーサルREST APIとすぐに使えるSDKで、あらゆる仮想世界、ゲームエンジン、メタバースにZoneCoinを統合します。
🔧 ユニバーサルアーキテクチャ
すべてのプラットフォームは単一のREST APIゲートウェイを通じてZoneCoin Joomlaバックエンドに接続します。
⚡ クイックスタート
任意のプラットフォームにZoneCoinを統合する4つのステップ。
🌐 OpenSimulator
ZoneCoinは、OpenSimulatorグリッドに直接統合されるネイティブC# .NETモジュールを提供します。リアルタイムウォレット管理、アバター間送金、土地購入、マーケットプレイス取引が可能で、すべてZoneCoin PoAブロックチェーンで検証されます。
🔵 Second Life
LSLスクリプトを使用してZoneCoinをSecond Lifeに統合します。HTTPリクエストでZoneCoin REST APIに接続する決済端末、残高チェッカー、自動販売機を作成できます。
決済端末
// ZoneCoin Payment Terminal — Second Life
// Drop this script into any prim to create a payment terminal
string API_URL = "https://zonecoin.zonenations.com/api/index.php/v1/zonecoin";
string API_KEY = "YOUR_API_KEY";
string MERCHANT = "your-merchant-id";
key httpReq;
default {
state_entry() {
llSetText("ZoneCoin Payment Terminal\nTouch to pay", <0.4, 0.9, 1.0>, 1.0);
llSetTimerEvent(300.0); // Heartbeat every 5 min
}
touch_start(integer n) {
key buyer = llDetectedKey(0);
string name = llDetectedName(0);
llTextBox(buyer, "Enter amount in ZC:", 1);
}
listen(integer ch, string name, key id, string msg) {
float amount = (float)msg;
if (amount <= 0) { llInstantMessage(id, "Invalid amount."); return; }
string body = llList2Json(JSON_OBJECT, [
"action", "pay",
"amount", (string)amount,
"ref", "SL-" + (string)llGetUnixTime(),
"buyer", (string)id,
"merchant", MERCHANT
]);
httpReq = llHTTPRequest(API_URL + "/pay", [
HTTP_METHOD, "POST",
HTTP_MIMETYPE, "application/json",
HTTP_CUSTOM_HEADER, "Authorization", "Bearer " + API_KEY,
HTTP_CUSTOM_HEADER, "X-ZoneCoin-Timestamp", (string)llGetUnixTime()
], body);
}
http_response(key req, integer status, list meta, string body) {
if (req != httpReq) return;
if (status == 200) {
string txId = llJsonGetValue(body, ["txId"]);
llSay(0, "✅ Payment confirmed! TX: " + txId);
} else {
llSay(0, "❌ Payment failed: " + body);
}
}
timer() {
llHTTPRequest(API_URL + "/heartbeat", [
HTTP_METHOD, "GET",
HTTP_CUSTOM_HEADER, "Authorization", "Bearer " + API_KEY
], "");
}
}残高チェッカー
// ZoneCoin Balance Checker — Second Life
// Touch to check your ZoneCoin wallet balance
string API_URL = "https://zonecoin.zonenations.com/api/index.php/v1/zonecoin";
string API_KEY = "YOUR_API_KEY";
key httpReq;
key requester;
default {
state_entry() {
llSetText("ZoneCoin Balance\nTouch to check", <0.2, 0.8, 0.6>, 1.0);
}
touch_start(integer n) {
requester = llDetectedKey(0);
httpReq = llHTTPRequest(API_URL + "/balance/" + (string)requester, [
HTTP_METHOD, "GET",
HTTP_CUSTOM_HEADER, "Authorization", "Bearer " + API_KEY
], "");
}
http_response(key req, integer status, list meta, string body) {
if (req != httpReq) return;
if (status == 200) {
string bal = llJsonGetValue(body, ["balance"]);
string cur = llJsonGetValue(body, ["currency"]);
llInstantMessage(requester, "💰 Your ZoneCoin balance: " + bal + " " + cur);
} else {
llInstantMessage(requester, "❌ Could not retrieve balance.");
}
}
}⬛ Unity
ZoneCoin Unity SDKは、完全なasync/coroutineサポートを備えたMonoBehaviourベースのシングルトンを提供します。自動HMAC-SHA256署名で決済、残高照会、P2P送金を処理します。
ZoneCoinSDK.cs
using System;
using System.Collections;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
namespace ZoneCoin.SDK
{
/// <summary>
/// ZoneCoin SDK for Unity — handles payments, balance queries
/// and webhook verification via the ZoneCoin REST API.
/// </summary>
public class ZoneCoinSDK : MonoBehaviour
{
[Header("Configuration")]
[SerializeField] private string apiUrl = "https://zonecoin.zonenations.com/api/index.php/v1/zonecoin";
[SerializeField] private string apiKey = "";
[SerializeField] private string secret = "";
[SerializeField] private string merchant = "";
public static ZoneCoinSDK Instance { get; private set; }
private void Awake()
{
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
// ── Balance ───────────────────────────────────────────
public void GetBalance(string wallet, Action<decimal> onSuccess, Action<string> onError = null)
{
StartCoroutine(GetRequest($"{apiUrl}/balance/{wallet}", json => {
var balance = decimal.Parse(JsonUtility.FromJson<BalanceResponse>(json).balance);
onSuccess?.Invoke(balance);
}, onError));
}
// ── Pay ───────────────────────────────────────────────
public void Pay(decimal amount, string reference, bool isFiat,
Action<PayResult> onSuccess, Action<string> onError = null)
{
var body = JsonUtility.ToJson(new PayRequest {
amount = amount.ToString("F2"),
ref_id = reference,
merchant = merchant,
isFiat = isFiat
});
StartCoroutine(PostRequest($"{apiUrl}/pay", body, json => {
onSuccess?.Invoke(JsonUtility.FromJson<PayResult>(json));
}, onError));
}
// ── Transfer ──────────────────────────────────────────
public void Transfer(string toWallet, decimal amount,
Action<string> onSuccess, Action<string> onError = null)
{
var body = JsonUtility.ToJson(new TransferRequest {
to = toWallet,
amount = amount.ToString("F2")
});
StartCoroutine(PostRequest($"{apiUrl}/transfer", body, json => {
onSuccess?.Invoke(JsonUtility.FromJson<TxResponse>(json).txId);
}, onError));
}
// ── HMAC Signing ──────────────────────────────────────
private string Sign(string timestamp, string nonce, string body)
{
var payload = timestamp + nonce + body;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
// ── HTTP Helpers ──────────────────────────────────────
private IEnumerator GetRequest(string url, Action<string> onOk, Action<string> onErr)
{
using var req = UnityWebRequest.Get(url);
AddHeaders(req, "");
yield return req.SendWebRequest();
if (req.result == UnityWebRequest.Result.Success) onOk(req.downloadHandler.text);
else onErr?.Invoke(req.error);
}
private IEnumerator PostRequest(string url, string json, Action<string> onOk, Action<string> onErr)
{
using var req = new UnityWebRequest(url, "POST");
req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("Content-Type", "application/json");
AddHeaders(req, json);
yield return req.SendWebRequest();
if (req.result == UnityWebRequest.Result.Success) onOk(req.downloadHandler.text);
else onErr?.Invoke(req.error);
}
private void AddHeaders(UnityWebRequest req, string body)
{
var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var nonce = Guid.NewGuid().ToString("N").Substring(0, 12);
req.SetRequestHeader("Authorization", "Bearer " + apiKey);
req.SetRequestHeader("X-ZoneCoin-Timestamp", ts);
req.SetRequestHeader("X-ZoneCoin-Nonce", nonce);
req.SetRequestHeader("X-ZoneCoin-Signature", Sign(ts, nonce, body));
}
// ── DTOs ──────────────────────────────────────────────
[Serializable] public class BalanceResponse { public string balance; public string currency; }
[Serializable] public class PayRequest { public string amount, ref_id, merchant; public bool isFiat; }
[Serializable] public class PayResult { public string txId; public string amount; }
[Serializable] public class TransferRequest { public string to, amount; }
[Serializable] public class TxResponse { public string txId; }
}
}使用例
// Example: In-game shop purchase
using ZoneCoin.SDK;
public class ShopUI : MonoBehaviour
{
public void OnBuyItem(string itemId, decimal price)
{
ZoneCoinSDK.Instance.Pay(price, $"GAME-{itemId}", isFiat: false,
onSuccess: result => {
Debug.Log($"Payment OK! TX: {result.txId}");
InventoryManager.Instance.AddItem(itemId);
UIManager.Instance.ShowToast("Purchase complete! ⚡");
},
onError: err => {
Debug.LogError($"Payment failed: {err}");
UIManager.Instance.ShowToast("Payment failed. Try again.");
}
);
}
}🔷 Unreal Engine
Unreal Engine統合はGameInstanceSubsystemを使用し、決済、残高照会、送金のBlueprintCallable関数を公開します。すべてのリクエストはHMAC署名され、非同期処理されます。
ZoneCoinSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "Http.h"
#include "ZoneCoinSubsystem.generated.h"
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnZCPayResult, bool, bSuccess, const FString&, TxId);
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnZCBalance, bool, bSuccess, float, Balance);
UCLASS()
class YOURPROJECT_API UZoneCoinSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZoneCoin")
FString ApiUrl = TEXT("https://zonecoin.zonenations.com/api/index.php/v1/zonecoin");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZoneCoin")
FString ApiKey;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZoneCoin")
FString Secret;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZoneCoin")
FString MerchantId;
UFUNCTION(BlueprintCallable, Category = "ZoneCoin")
void Pay(float Amount, const FString& Reference, bool bIsFiat, FOnZCPayResult OnResult);
UFUNCTION(BlueprintCallable, Category = "ZoneCoin")
void GetBalance(const FString& Wallet, FOnZCBalance OnResult);
UFUNCTION(BlueprintCallable, Category = "ZoneCoin")
void Transfer(const FString& ToWallet, float Amount, FOnZCPayResult OnResult);
private:
TSharedRef<IHttpRequest> CreateRequest(const FString& Verb, const FString& Endpoint, const FString& Body = TEXT(""));
FString SignRequest(const FString& Timestamp, const FString& Nonce, const FString& Body);
};ZoneCoinSubsystem.cpp
#include "ZoneCoinSubsystem.h"
#include "Serialization/JsonSerializer.h"
#include "Misc/SecureHash.h"
void UZoneCoinSubsystem::Pay(float Amount, const FString& Reference, bool bIsFiat, FOnZCPayResult OnResult)
{
TSharedPtr<FJsonObject> Json = MakeShareable(new FJsonObject);
Json->SetNumberField(TEXT("amount"), Amount);
Json->SetStringField(TEXT("ref"), Reference);
Json->SetStringField(TEXT("merchant"), MerchantId);
Json->SetBoolField(TEXT("isFiat"), bIsFiat);
FString Body;
auto Writer = TJsonWriterFactory<>::Create(&Body);
FJsonSerializer::Serialize(Json.ToSharedRef(), Writer);
auto Request = CreateRequest(TEXT("POST"), TEXT("/pay"), Body);
Request->OnProcessRequestComplete().BindLambda(
[OnResult](FHttpRequestPtr Req, FHttpResponsePtr Res, bool bOk) {
if (bOk && Res->GetResponseCode() == 200) {
TSharedPtr<FJsonObject> R;
auto Reader = TJsonReaderFactory<>::Create(Res->GetContentAsString());
FJsonSerializer::Deserialize(Reader, R);
OnResult.ExecuteIfBound(true, R->GetStringField(TEXT("txId")));
} else {
OnResult.ExecuteIfBound(false, TEXT(""));
}
});
Request->ProcessRequest();
}
void UZoneCoinSubsystem::GetBalance(const FString& Wallet, FOnZCBalance OnResult)
{
auto Request = CreateRequest(TEXT("GET"), FString::Printf(TEXT("/balance/%s"), *Wallet));
Request->OnProcessRequestComplete().BindLambda(
[OnResult](FHttpRequestPtr Req, FHttpResponsePtr Res, bool bOk) {
if (bOk && Res->GetResponseCode() == 200) {
TSharedPtr<FJsonObject> R;
auto Reader = TJsonReaderFactory<>::Create(Res->GetContentAsString());
FJsonSerializer::Deserialize(Reader, R);
OnResult.ExecuteIfBound(true, R->GetNumberField(TEXT("balance")));
} else {
OnResult.ExecuteIfBound(false, 0.f);
}
});
Request->ProcessRequest();
}🔹 Godot
Godot 4 ZoneCoin SDKは、非同期通信にシグナルを使用するGDScriptオートロードシングルトンです。HMACContextによるHMAC-SHA256署名で決済、残高照会、送金をサポートします。
zonecoin_sdk.gd
extends Node
class_name ZoneCoinSDK
## ZoneCoin SDK for Godot 4 — REST API integration
## Add this as an autoload singleton: Project → Settings → Autoload
@export var api_url : String = "https://zonecoin.zonenations.com/api/index.php/v1/zonecoin"
@export var api_key : String = ""
@export var secret : String = ""
@export var merchant : String = ""
signal payment_completed(tx_id: String, amount: float)
signal payment_failed(error: String)
signal balance_received(balance: float, currency: String)
var _http : HTTPRequest
func _ready():
_http = HTTPRequest.new()
add_child(_http)
# ── Pay ────────────────────────────────────────────────────
func pay(amount: float, reference: String, is_fiat: bool = false) -> void:
var body = JSON.stringify({
"amount": amount,
"ref": reference,
"merchant": merchant,
"isFiat": is_fiat
})
var headers = _build_headers(body)
_http.request_completed.connect(_on_pay_response, CONNECT_ONE_SHOT)
_http.request(api_url + "/pay", headers, HTTPClient.METHOD_POST, body)
func _on_pay_response(result, code, headers, body):
var json = JSON.parse_string(body.get_string_from_utf8())
if code == 200:
payment_completed.emit(json.txId, float(json.amount))
else:
payment_failed.emit(json.get("error", "Unknown error"))
# ── Balance ────────────────────────────────────────────────
func get_balance(wallet: String) -> void:
var headers = _build_headers("")
_http.request_completed.connect(_on_balance_response, CONNECT_ONE_SHOT)
_http.request(api_url + "/balance/" + wallet, headers, HTTPClient.METHOD_GET)
func _on_balance_response(result, code, headers, body):
var json = JSON.parse_string(body.get_string_from_utf8())
if code == 200:
balance_received.emit(float(json.balance), json.currency)
# ── Transfer ───────────────────────────────────────────────
func transfer(to_wallet: String, amount: float) -> void:
var body = JSON.stringify({"to": to_wallet, "amount": amount})
var headers = _build_headers(body)
_http.request(api_url + "/transfer", headers, HTTPClient.METHOD_POST, body)
# ── HMAC Signing ───────────────────────────────────────────
func _build_headers(body: String) -> PackedStringArray:
var ts = str(int(Time.get_unix_time_from_system()))
var nonce = _rand_hex(12)
var sig = _hmac_sha256(ts + nonce + body)
return PackedStringArray([
"Content-Type: application/json",
"Authorization: Bearer " + api_key,
"X-ZoneCoin-Timestamp: " + ts,
"X-ZoneCoin-Nonce: " + nonce,
"X-ZoneCoin-Signature: " + sig
])
func _hmac_sha256(message: String) -> String:
var ctx = HMACContext.new()
ctx.start(HashingContext.HASH_SHA256, secret.to_utf8_buffer())
ctx.update(message.to_utf8_buffer())
return ctx.finish().hex_encode()
func _rand_hex(length: int) -> String:
var bytes = Crypto.new().generate_random_bytes(length)
return bytes.hex_encode().substr(0, length)🟫 Minecraft
Minecraft ZoneCoinプラグインはSpigotとPaperサーバーに統合されます。プレイヤーは/zcbalanceと/zcpayでウォレットを管理します。非同期HTTPコールとHMAC認証をサポート。
plugin.yml
name: ZoneCoinPlugin
version: 1.0.0
main: com.zonenations.zonecoin.ZoneCoinPlugin
api-version: '1.20'
description: ZoneCoin virtual currency for Minecraft servers
authors: [ZoneNations]
commands:
zcbalance:
description: Check your ZoneCoin balance
usage: /zcbalance
permission: zonecoin.balance
zcpay:
description: Send ZoneCoin to a player
usage: /zcpay <player> <amount>
permission: zonecoin.pay
zcshop:
description: Open the ZoneCoin shop
usage: /zcshop
permission: zonecoin.shop
permissions:
zonecoin.balance:
default: true
zonecoin.pay:
default: true
zonecoin.shop:
default: true
zonecoin.admin:
default: opZoneCoinPlugin.java
package com.zonenations.zonecoin;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.google.gson.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.http.*;
import java.net.URI;
import java.time.Instant;
import java.util.UUID;
public class ZoneCoinPlugin extends JavaPlugin {
private String apiUrl, apiKey, secret, merchant;
private final HttpClient http = HttpClient.newHttpClient();
private final Gson gson = new Gson();
@Override
public void onEnable() {
saveDefaultConfig();
apiUrl = getConfig().getString("api-url", "https://zonecoin.zonenations.com/api/index.php/v1/zonecoin");
apiKey = getConfig().getString("api-key", "");
secret = getConfig().getString("secret", "");
merchant = getConfig().getString("merchant", "");
getCommand("zcbalance").setExecutor(this::onBalance);
getCommand("zcpay").setExecutor(this::onPay);
getLogger().info("ZoneCoin plugin enabled ⚡");
}
private boolean onBalance(CommandSender s, Command c, String l, String[] a) {
if (!(s instanceof Player p)) { s.sendMessage("§cPlayers only."); return true; }
getServer().getScheduler().runTaskAsynchronously(this, () -> {
try {
var res = apiGet("/balance/" + p.getUniqueId());
var json = JsonParser.parseString(res).getAsJsonObject();
var bal = json.get("balance").getAsString();
p.sendMessage("§6§l💰 ZoneCoin Balance: §f" + bal + " ZC");
} catch (Exception e) {
p.sendMessage("§c❌ Could not retrieve balance.");
getLogger().warning("Balance error: " + e.getMessage());
}
});
return true;
}
private boolean onPay(CommandSender s, Command c, String l, String[] a) {
if (!(s instanceof Player p)) return false;
if (a.length < 2) { p.sendMessage("§cUsage: /zcpay <player> <amount>"); return true; }
Player target = getServer().getPlayer(a[0]);
if (target == null) { p.sendMessage("§cPlayer not found."); return true; }
double amount;
try { amount = Double.parseDouble(a[1]); } catch (Exception e) { p.sendMessage("§cInvalid amount."); return true; }
getServer().getScheduler().runTaskAsynchronously(this, () -> {
try {
var body = gson.toJson(new PayReq(amount, "MC-" + UUID.randomUUID().toString().substring(0, 8),
p.getUniqueId().toString(), target.getUniqueId().toString()));
var res = apiPost("/transfer", body);
var txId = JsonParser.parseString(res).getAsJsonObject().get("txId").getAsString();
p.sendMessage("§a§l✅ Sent " + amount + " ZC to " + target.getName() + " (TX: " + txId + ")");
target.sendMessage("§a§l💰 Received " + amount + " ZC from " + p.getName());
} catch (Exception e) {
p.sendMessage("§c❌ Transfer failed: " + e.getMessage());
}
});
return true;
}
// ── HTTP Helpers with HMAC ─────────────────────────────────
private String apiGet(String endpoint) throws Exception {
var ts = String.valueOf(Instant.now().getEpochSecond());
var nonce = UUID.randomUUID().toString().substring(0, 12);
var sig = hmacSha256(ts + nonce);
var req = HttpRequest.newBuilder(URI.create(apiUrl + endpoint))
.header("Authorization", "Bearer " + apiKey)
.header("X-ZoneCoin-Timestamp", ts)
.header("X-ZoneCoin-Nonce", nonce)
.header("X-ZoneCoin-Signature", sig)
.GET().build();
return http.send(req, HttpResponse.BodyHandlers.ofString()).body();
}
private String apiPost(String endpoint, String body) throws Exception {
var ts = String.valueOf(Instant.now().getEpochSecond());
var nonce = UUID.randomUUID().toString().substring(0, 12);
var sig = hmacSha256(ts + nonce + body);
var req = HttpRequest.newBuilder(URI.create(apiUrl + endpoint))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.header("X-ZoneCoin-Timestamp", ts)
.header("X-ZoneCoin-Nonce", nonce)
.header("X-ZoneCoin-Signature", sig)
.POST(HttpRequest.BodyPublishers.ofString(body)).build();
return http.send(req, HttpResponse.BodyHandlers.ofString()).body();
}
private String hmacSha256(String data) throws Exception {
var mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
var hex = new StringBuilder();
for (byte b : mac.doFinal(data.getBytes())) hex.append(String.format("%02x", b));
return hex.toString();
}
record PayReq(double amount, String ref, String from, String to) {}
}🔴 Roblox
Roblox ZoneCoin SDKは、API通信にHttpServiceを使用するサーバーサイドModuleScriptです。残高照会、決済、P2P送金をpcallエラー処理でサポートします。
ZoneCoinSDK (ModuleScript)
-- ZoneCoin SDK for Roblox — Server-side ModuleScript
-- Place in ServerScriptService/ZoneCoinSDK
local HttpService = game:GetService("HttpService")
local ZoneCoin = {}
ZoneCoin.__index = ZoneCoin
local API_URL = "https://zonecoin.zonenations.com/api/index.php/v1/zonecoin"
local API_KEY = "" -- Set in config
local SECRET = ""
local MERCHANT = ""
function ZoneCoin.Init(config)
API_URL = config.apiUrl or API_URL
API_KEY = config.apiKey or API_KEY
SECRET = config.secret or SECRET
MERCHANT = config.merchant or MERCHANT
end
function ZoneCoin.GetBalance(playerId)
local ok, res = pcall(function()
return HttpService:RequestAsync({
Url = API_URL .. "/balance/" .. tostring(playerId),
Method = "GET",
Headers = ZoneCoin._Headers("")
})
end)
if ok and res.StatusCode == 200 then
local data = HttpService:JSONDecode(res.Body)
return tonumber(data.balance), data.currency
end
return nil, "Request failed"
end
function ZoneCoin.Pay(amount, reference, isFiat)
local body = HttpService:JSONEncode({
amount = amount,
ref = reference,
merchant = MERCHANT,
isFiat = isFiat or false
})
local ok, res = pcall(function()
return HttpService:RequestAsync({
Url = API_URL .. "/pay",
Method = "POST",
Headers = ZoneCoin._Headers(body),
Body = body
})
end)
if ok and res.StatusCode == 200 then
local data = HttpService:JSONDecode(res.Body)
return data.txId
end
return nil
end
function ZoneCoin.Transfer(toPlayer, amount)
local body = HttpService:JSONEncode({to = tostring(toPlayer), amount = amount})
local ok, res = pcall(function()
return HttpService:RequestAsync({
Url = API_URL .. "/transfer",
Method = "POST",
Headers = ZoneCoin._Headers(body),
Body = body
})
end)
if ok and res.StatusCode == 200 then
return HttpService:JSONDecode(res.Body).txId
end
return nil
end
function ZoneCoin._Headers(body)
local ts = tostring(os.time())
local nonce = HttpService:GenerateGUID(false):sub(1, 12)
return {
["Content-Type"] = "application/json",
["Authorization"] = "Bearer " .. API_KEY,
["X-ZoneCoin-Timestamp"] = ts,
["X-ZoneCoin-Nonce"] = nonce,
}
end
return ZoneCoinサーバースクリプト — ショップハンドラ
-- Server Script — Shop handler
local ZoneCoin = require(game.ServerScriptService.ZoneCoinSDK)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
ZoneCoin.Init({
apiUrl = "https://zonecoin.zonenations.com/api/index.php/v1/zonecoin",
apiKey = "YOUR_API_KEY",
secret = "YOUR_SECRET",
merchant = "your-merchant"
})
-- Remote event for purchase requests
local BuyEvent = ReplicatedStorage:WaitForChild("BuyItem")
BuyEvent.OnServerEvent:Connect(function(player, itemId, price)
local txId = ZoneCoin.Pay(price, "RBLX-" .. itemId .. "-" .. player.UserId, false)
if txId then
-- Grant item to player
player:FindFirstChild("Inventory"):FindFirstChild(itemId).Value = true
BuyEvent:FireClient(player, true, txId)
else
BuyEvent:FireClient(player, false, "Payment failed")
end
end)🟣 VRChat
VRChat統合はUdonSharpを使用してVRChatワールドにインタラクティブな決済端末を作成します。API通信にVRCStringDownloaderを活用し、マルチユーザー状態の手動同期をサポートします。
ZoneCoinTerminal.cs (UdonSharp)
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3.StringLoading;
using VRC.Udon.Common.Interfaces;
/// <summary>
/// ZoneCoin Payment Terminal for VRChat worlds.
/// Attach to a UI Canvas with display and button references.
/// </summary>
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class ZoneCoinTerminal : UdonSharpBehaviour
{
[Header("ZoneCoin Config")]
public string apiUrl = "https://zonecoin.zonenations.com/api/index.php/v1/zonecoin";
public string apiKey = "";
public string merchant = "";
[Header("UI References")]
public UnityEngine.UI.Text balanceText;
public UnityEngine.UI.Text statusText;
[UdonSynced] private string lastTxId = "";
public override void Interact()
{
// Check balance on interact
VRCStringDownloader.LoadUrl(
new VRCUrl($"{apiUrl}/balance/{Networking.LocalPlayer.displayName}"),
(IUdonEventReceiver)this
);
statusText.text = "⏳ Checking balance...";
}
public override void OnStringLoadSuccess(IVRCStringDownload result)
{
// Parse simple JSON response
string body = result.Result;
// Basic JSON extraction (VRChat limitation: no full JSON parser)
string balance = ExtractJsonValue(body, "balance");
string currency = ExtractJsonValue(body, "currency");
balanceText.text = $"💰 {balance} {currency}";
statusText.text = "✅ Balance loaded";
}
public override void OnStringLoadError(IVRCStringDownload result)
{
statusText.text = "❌ Connection error";
}
private string ExtractJsonValue(string json, string key)
{
int start = json.IndexOf($"\"{key}\":\"") + key.Length + 4;
int end = json.IndexOf("\"", start);
return (start > 0 && end > start) ? json.Substring(start, end - start) : "?";
}
}🏙️ Decentraland
SDK 7を使用してDecentralandシーンにZoneCoinを統合します。決済端末はPointerEventsとsignedFetchを使用した認証APIコールでインタラクティブエンティティとして作成されます。
ZoneCoinScene.ts
import { engine, Transform, TextShape, PointerEvents,
InputAction, pointerEventsSystem } from '@dcl/sdk/ecs'
import { signedFetch } from '~system/SignedFetch'
const API_URL = 'https://zonecoin.zonenations.com/api/index.php/v1/zonecoin'
const API_KEY = 'YOUR_API_KEY'
const MERCHANT = 'your-merchant-id'
// ── Payment Terminal Entity ───────────────────────────────
const terminal = engine.addEntity()
Transform.create(terminal, { position: { x: 8, y: 1.5, z: 8 } })
TextShape.create(terminal, {
text: '⚡ ZoneCoin Terminal\nClick to interact',
fontSize: 3,
textColor: { r: 0.4, g: 0.9, b: 1.0, a: 1 }
})
PointerEvents.create(terminal, {
pointerEvents: [{ eventType: 1, eventInfo: { button: InputAction.IA_POINTER, hoverText: 'Pay with ZoneCoin' } }]
})
// ── Click Handler ─────────────────────────────────────────
pointerEventsSystem.onPointerDown({ entity: terminal, opts: { button: InputAction.IA_POINTER } },
async () => {
try {
const res = await signedFetch({
url: `${API_URL}/pay`,
init: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
amount: 10,
ref: `DCL-${Date.now()}`,
merchant: MERCHANT,
isFiat: false
})
}
})
const data = JSON.parse(res.body || '{}')
const ts = TextShape.getMutable(terminal)
ts.text = data.txId
? `✅ Paid! TX: ${data.txId}`
: '❌ Payment failed'
} catch (e) {
const ts = TextShape.getMutable(terminal)
ts.text = '❌ Network error'
}
}
)🏖️ The Sandbox
The Sandbox Game Maker統合はゲーム内購入のScriptComponentを提供します。APIコールにHTTPRequestを使用し、視覚フィードバック付きクリックトリガー決済フローをサポートします。
ゲーム内ショップコンポーネント
// ZoneCoin Integration for The Sandbox Game Maker
// Add this as a custom script component
import { ScriptComponent, Trigger, HTTPRequest } from '@sandbox/core'
const API_URL = 'https://zonecoin.zonenations.com/api/index.php/v1/zonecoin'
const API_KEY = 'YOUR_API_KEY'
export default class ZoneCoinShop extends ScriptComponent {
private apiUrl = API_URL
private apiKey = API_KEY
onInit() {
this.entity.on(Trigger.OnClick, () => this.purchaseItem())
}
async purchaseItem() {
const request = new HTTPRequest(`${this.apiUrl}/pay`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
amount: 50,
ref: `SAND-${Date.now()}`,
merchant: 'your-merchant',
isFiat: false
})
})
const res = await request.send()
if (res.status === 200) {
const data = JSON.parse(res.body)
this.showNotification(`✅ Payment OK! TX: ${data.txId}`)
this.grantReward()
} else {
this.showNotification('❌ Payment failed')
}
}
showNotification(msg: string) {
// Use The Sandbox notification system
this.entity.getComponent('TextRenderer').text = msg
}
grantReward() {
// Grant in-game item/reward to player
}
}🔌 REST APIクイックリファレンス
すべてのプラットフォームは同じREST APIを使用します。HMAC-SHA256で認証します。