Getting Started

Wallet-Pay Quick Start

Integration Sequence Diagram

Take an e-commerce application as an example:

flow

Dashboard Operation Instructions

Dashboard is the management backend for Wallet-Pay, providing functions such as creating applications, configuring applications, and managing orders.

You can consult our customer service for the access address of the Dashboard.

Technical Support TG: @Openweb3WalletPay

Dashboard entry link:

Telegram: https://tg-socialwallet.wallet-pay.openweb3.io

Dejoy: https://dejoy-socialwallet.wallet-pay.openweb3.io

Register Account

You can register with an email or log in with Google, and follow the instructions to complete the 2FA registration and binding.

Create Team

After registering an account, you will be redirected to the team list page by default.

If you don't have a team, you can contact Technical Support to apply to join an existing team or create a new one.

By clicking the Create team button, you can follow the instructions to create a new team.

create_team

After submitting the team information, you need to contact Technical Support for approval.

review_team

After approval, or after being invited to a team, you can manage applications and view information such as accounts, orders, and refunds in the Dashboard.

overview

Create Application

After logging into the Dashboard and entering a team, you can create a new application by clicking Apps -> New App.

create_app

Configure Application

Register API Key

If you need to use the SDK to make OpenApi interface calls, including creating orders, querying order status, etc., you need to register an API Key in the Dashboard first.

The ED25519 algorithm APIKEY is used here. You can refer to the following method to generate the key pair.

# Generate a private key and save it to the `private.key.pem` file
openssl genpkey -algorithm ed25519 -out private_key.pem

# Extract the public key from the private key and save it to the `public.key.pem` file
openssl pkey -in private_key.pem -pubout -out public_key.pem

# Export the private key in hexadecimal format
openssl pkey -in private_key.pem -text | \
grep 'priv:' -A 3 | tail -n +2 | tr -d ':\n '

# Export the public key in hexadecimal format
openssl pkey -pubin -in public_key.pem -text | \
grep 'pub:' -A 3 | tail -n +2 | tr -d ':\n '

After the key pair is generated, register the public key to the application through Apps -> App Settings -> Register API Key.

The private key needs to be kept properly by yourself and will be used when using the SDK.

api_key

Configure Webhook

If you need to use Webhook for order status callback notifications, you need to configure Webhook in the Dashboard first.

Add a webhook endpoint in Apps -> App Settings -> Add Webhook.

The detailed description of Notification Events can be found at Webhook Events.

webhook

SDK Integration

You can interact with the Wallet-Pay service through the SDK, which provides support for multiple languages, including Golang, NodeJS, and Java.

For the interfaces and their parameter descriptions involved in the SDK, please refer to the OpenAPI documentation WALLETPAY OPENAPI DOCUMENTATION .

Install SDK Dependencies

Golang

Add the following dependency to your go.mod file:

require github.com/openweb3-io/wallet-pay-openapi/go latest

Then run the go mod tidy command to download the dependencies.

NodeJS

Use the following command to install the SDK:

pnpm i @openweb3-io/wallet-pay

# or

yarn add @openweb3-io/wallet-pay

# or

npm i @openweb3-io/wallet-pay

Java

If you are using maven for project management, you can add the following dependency to your pom.xml file:

<dependency>
    <groupId>io.openweb3</groupId>
    <artifactId>walletpay</artifactId>
    <version>0.2.13</version>
</dependency>

If you are using gradle for project management, you can add the following dependency to your build.gradle file:

implementation 'io.openweb3:walletpay:0.2.13'

Initialize SDK

SDK initialization involves two parameters, ApiKey and Secret, which correspond to the public and private keys generated in the Register API Key step. ApiKey is the public key, and Secret is the private key.

Golang

import (
    "log"

    walletpay "github.com/openweb3-io/wallet-pay-openapi/go"
)

const ( 
    ApiKey = "YOUR_API_KEY"
    Secret = "YOUR_SECRET"
)

func main() {
    // Initialize ApiClient 
    client := walletpay.New(&walletpay.ApiClientOptions{
        ApiKey: ApiKey,
        Secret: Secret,
    })
}

NodeJS

import { ApiClient } from "@openweb3-io/wallet-pay";

const ApiKey = "YOUR_API_KEY";
const Secret = "YOUR_SECRET";

const client = new ApiClient(ApiKey, Secret, {});

Java

import io.openweb3.walletpay.ApiClient;
import io.openweb3.walletpay.ApiClientOptions;

public class WalletPayExample {
    public static void main(String[] args) {
        String apiKey = "YOUR_API_KEY";
        String secret = "YOUR_SECRET";
        ApiClient apiClient = new ApiClient(
                new ApiClientOptions()
                        .apiKey(apiKey)
                        .privateKey(secret)
        );
    }
}

PHP

WalletPay does not currently support an SDK for the PHP language.

The interface signature algorithm can refer to the description in Authentication.

Sample code is as follows:

<?php

// The sodium extension is required (PHP >= 7.2.0)
if (!extension_loaded('sodium')) {
    die("Sodium extension required");
}

$body = 'post body data';
$uri = 'request uri';
$timestamp = 'current timestamp';

$content = $body . $uri . $timestamp;

// SHA-256 hash calculation
$hash = hash('sha256', $content, true);

// Decode API secret
$apiSecret = hex2bin('YOUR_API_SECRET');

// Generate key pair from seed
$keyPair = sodium_crypto_sign_seed_keypair($apiSecret);
$secretKey = sodium_crypto_sign_secretkey($keyPair);

// Ed25519 signature
$signature = sodium_crypto_sign_detached($hash, $secretKey);
echo "Signature: " . bin2hex($signature) . PHP_EOL;

Create Order

Golang

import (
    "context"
    "encoding/json"
    "fmt"
    "log"

    walletpay "github.com/openweb3-io/wallet-pay-openapi/go"
)

const ( 
    ApiKey = "YOUR_API_KEY"
    Secret = "YOUR_SECRET"
)

func create() {
    // Initialize ApiClient 
    client := walletpay.New(&walletpay.ApiClientOptions{
        Debug:  true,
        ApiKey: ApiKey,
        Secret: Secret,
    })

    currencyCode := "USDT"
    amount := int32(1)

    currency, err := client.Currencies.FindByCode(context.Background(), currencyCode)
    if err != nil {
        log.Fatal(err)
    }

    order, err := client.Orders.Create(context.Background(), &walletpay.CreateOrderRequest{
        Currency:   currencyCode,
        Amount:     fmt.Sprintf("%d", amount*currency.Decimals), // 1 USDT
        Expiration: walletpay.PtrInt32(10),
        Creator:    walletpay.PtrString("creator_user_id"),
    })
    if err != nil {
        log.Fatal(err)
    }
    buf, _ := json.MarshalIndent(order, "", "  ")
    log.Printf(string(buf))
}

NodeJS


import { ApiClient } from "@openweb3-io/wallet-pay";

const ApiKey = "YOUR_API_KEY";
const Secret = "YOUR_SECRET";

const client = new ApiClient(ApiKey, Secret, {});

const USDT = "USDT";
const AMOUNT = 1;

const { decimals } = await client.currencies.findByCode(USDT);

const order = await client.orders.create({
  currency: USDT,
  amount: (AMOUNT * (10 ** decimals)).toString(),
  creator: "creator_user_id",
});

console.log(order);

Java


import io.openweb3.walletpay.*;
import io.openweb3.walletpay.models.*;

import java.math.BigInteger;

public class WalletPayExample {
    public static void main(String[] args) {
        String apiKey = "YOUR_API_KEY";
        String secret = "YOUR_SECRET";
        ApiClient apiClient = new ApiClient(
                new ApiClientOptions()
                        .apiKey(apiKey)
                        .privateKey(secret)
        );

        String currencyCode = "USDT";
        String amount = "1"; // readable amount

        // get currency info
        final Currency currency = apiClient.currencies().findByCode(currencyCode);
        System.out.println("currency: " + currency);

        // create order
        BigInteger amountInBaseUnit = new BigInteger(amount).multiply(BigInteger.TEN.pow(currency.getDecimals()));
        CreateOrderRequest orderIn = new CreateOrderRequest()
                .amount(amountInBaseUnit.toString())
                .currency(currencyCode)
                .creator("creator_user_id");
        final Order orderInfo = apiClient.orders().create(orderIn);
        System.out.println("orderInfo: " + orderInfo);

        // list orders
        final PageOrder orders = apiClient.orders().list(
                new OrderListOptions().currency("CoinBeta").size(20)
        );
        System.out.println("orderInfos: " + orders);
    }
}

Pay Order

Paying for an order requires launching the wallet for payment. The general process is as follows:

  1. The application frontend sends a request to the application backend to create an order.
  2. The application backend creates an order through the SDK and returns the order information to the frontend.
  3. After the application frontend receives the order information, it launches the wallet for payment through a specific URL.
  4. After the wallet receives the payment request, it will jump to the payment interface according to the order information and wait for the user to confirm the payment.
  5. After the payment is completed, the wallet will call back the webhook interface of the application backend. After the application backend receives the callback, it will perform subsequent processing according to the order status.

The frontend URL for launching the wallet application is as follows:

# .env environment configuration

# telegram mini app
TELEGRAM_MANIAPP_DOMAIN=https://t.me/socialwalletbot/SocialWallet

# openweb3 mini app
OPENWEB3_MINIAPP_DOMAIN=https://t.me/wallet_bot/wallet

On the frontend, by using the host above and appending parameters, you can launch the wallet for payment.
Taking the Telegram mini app as an example:

// example.tsx

const link = `${process.env.TELEGRAM_MANIAPP_DOMAIN}?startapp=Pay_${order_id}`;

window.open(link);

Refund Order

Golang

import (
    "context"
    "encoding/json"
    "log"
    "fmt"

    walletpay "github.com/openweb3-io/wallet-pay-openapi/go"
)
const ( 
    ApiKey = "YOUR_API_KEY"
    Secret = "YOUR_SECRET"
)

func createRefund() {
    // Initialize ApiClient 
    client := walletpay.New(&walletpay.ApiClientOptions{
        Debug:  true,
        ApiKey: ApiKey,
        Secret: Secret,
    })

    currencyCode := "USDT"
    amount := int32(1)

    currency, err := client.Currencies.FindByCode(context.Background(), currencyCode)
    if err != nil {
        log.Fatal(err)
    }

    refund, err := client.Refunds.Create(context.Background(), &walletpay.CreateRefundRequest{
        OrderId: "order_123",
        Amount: fmt.Sprintf("%d", amount*currency.Decimals), // 1 USDT
        Note:    walletpay.PtrString("test"),
    })
    if err != nil {
        log.Fatal(err)
    }

    buf, _ := json.MarshalIndent(refund, "", "  ")
    log.Printf(string(buf))
}

NodeJS


import { ApiClient } from "@openweb3-io/wallet-pay";

const ApiKey = "YOUR_API_KEY";
const Secret = "YOUR_SECRET";

const client = new ApiClient(ApiKey, Secret, {});

const USDT = "USDT";
const AMOUNT = 1;

const { decimals } = await client.currencies.findByCode(USDT);

const refund = await client.refunds.create({
    "orderId": "1234567890",
    "amount": (AMOUNT * (10 ** decimals)).toString(),
    "note": "test refund",
});

console.log(refund);

Java

import io.openweb3.walletpay.*;
import io.openweb3.walletpay.models.*;
import java.math.BigInteger;

public class WalletPayExample {
    public static void main(String[] args) {
        String apiKey = "YOUR_API_KEY";
        String secret = "YOUR_SECRET";
        ApiClient apiClient = new ApiClient(
                new ApiClientOptions()
                       .apiKey(apiKey)
                       .privateKey(secret)  
        );

        String currencyCode = "USDT";
        String amount = "1"; // readable amount

        // get currency info
        final Currency currency = apiClient.currencies().findByCode(currencyCode);
        System.out.println("currency: " + currency);

        BigInteger amountInBaseUnit = new BigInteger(amount).multiply(BigInteger.TEN.pow(currency.getDecimals()));
        CreateRefundRequest refundIn = new CreateRefundRequest().
               orderId("order_id").
               amount(amountInBaseUnit.toString());
        Refund refundInfo = apiClient.refunds().create(refundIn);
        System.out.println("refundInfo: " + refundInfo);
    }
}

Callback Handling

Callback handling mainly consists of 3 parts:

  1. Verify signature
  2. Event deduplication
  3. Process event

For a detailed description of callbacks, please refer to walletpay-introduction-to-webhooks.

The PublicKey required for Webhook callback verification can be found in the Dashboard under settings -> developer.

Sample code for webhook signature verification is as follows:

Golang

import (
    "log"
    walletpay "github.com/openweb3-io/wallet-pay-openapi/go"
)

const (
    publicKey = "YOUR_PUBLIC_KEY"
)

func webhook() {
    client, err := walletpay.NewWebhookClient(publicKey)
    if err != nil {
        log.Fatal(err)
    }

    payload := []byte("content from webhook request body")
    signature := "content from webhook request header: X-Signature"

    // Verify signature
    err = client.Verify(payload, signature)
    if err != nil {
        log.Fatal("signature verify failed")
    } 

    // TODO Signature verification successful, process related business
}

NodeJS


import { WebhookClient } from "@openweb3-io/wallet-pay";

const PUBLICKEY = `-----BEGIN RSA PUBLIC KEY-----
YOUR KEY CONTENT
-----END RSA PUBLIC KEY-----`;

// Request headers with X-Signature and Payload, Verify the signature method
const payload = 'content from webhook request body';
const signature = 'content from webhook request header: X-Signature'
const webhookClient = new WebhookClient(PUBLICKEY);
const result = webhookClient.verify(payload, signature);
if (result) {
  // TODO Signature verification successful, process related business
} else {
  // Signature verification failed, callback is not credible, please ignore
}

Java

import io.openweb3.walletpay.*;

public class WalletPayExample {
    public static void main(String[] args) {
        String publicKey = "YOUR_PUBLIC_KEY";
        String payload = "content from webhook request body";
        String signature = "content from webhook request header: X-Signature";

        // verify the webhook payload
        WebhookClient webhookClient = new WebhookClient(publicKey);
        boolean ok = webhookClient.verify(payload, signature);
        System.out.println("verify " + (ok? "success" : "fail"));
        if (ok) {
            // TODO process the webhook event
        } else {
            // TODO handle the verification failure
        }
    }
}

PHP

function verify($data, $publicKey, $signature) {
    // PEM decode (PHP will automatically handle PEM format)
    $pubKey = openssl_pkey_get_public($publicKey);
    if ($pubKey === false) {
        throw new Exception("Public key error: " . openssl_error_string());
    }

    // Base64 decode the signature
    $signBytes = base64_decode($signature, true);
    if ($signBytes === false) {
        error_log("Signature contains invalid characters: $signature");
        throw new Exception("Invalid base64 signature");
    }

    // SHA256 hash calculation
    $hashed = hash('sha256', $data, true);

    // Verify signature
    $result = openssl_verify(
        $hashed, // Use the hashed data for verification
        $signBytes,
        $pubKey,
        OPENSSL_ALGO_SHA256
    );

    // Free key resource
    openssl_free_key($pubKey);

    // Handle verification result
    if ($result === 1) {
        return null; // Verification successful
    } elseif ($result === 0) {
        return new Exception("Invalid signature");
    } else {
        throw new Exception("Verification error: " . openssl_error_string());
    }
}

// Test execution code
try {
    $data = "content from webhook request body";
    $publicKey = "your public key from wallet-pay dashboard";
    $signature = "content from webhook request header: X-Signature";
    
    $result = verify($data, $publicKey, trim($signature));
    
    if ($result === null) {
        echo "verify success\n";
    } else {
        echo "verify failed: " . $result->getMessage() . "\n";
    }
} catch (Exception $e) {
    echo "exception: " . $e->getMessage() . "\n";
}