@gaian-sdk/payments
Version:
A powerful SDK for processing cryptocurrency payments via QR codes on the Solana blockchain.
580 lines (423 loc) • 15.8 kB
Markdown
# -sdk/payments Documentation
A powerful SDK for processing cryptocurrency payments via QR codes on the Solana blockchain.
## Installation
```bash
npm install -sdk/payments
# or
yarn add -sdk/payments
```
## Required Dependencies
```bash
npm install /web3.js @solana/spl-token bs58 dotenv
```
## Environment Setup
Create a `.env` file in your project root:
```env
SOLANA_PRIVATE_KEY=your_base58_encoded_private_key_here
SOLANA_RPC=https://api.devnet.solana.com # or your preferred RPC endpoint
NODE_ENV=development
```
### Getting Your Private Key
1. **Generate a new keypair** (if you don't have one):
```bash
solana-keygen new --outfile ~/.config/solana/id.json
```
2. **Export as base58** (required format):
```javascript
import { Keypair } from "@solana/web3.js";
import bs58 from "bs58";
// If you have a JSON keypair file
const keypair = Keypair.fromSecretKey(
new Uint8Array(JSON.parse(secretKeyArray))
);
const privateKeyBase58 = bs58.encode(keypair.secretKey);
console.log("SOLANA_PRIVATE_KEY=" + privateKeyBase58);
```
3. **Fund your wallet** with SOL and USDC:
- SOL: `solana airdrop 2 YOUR_WALLET_ADDRESS --url devnet`
- USDC: Visit [SPL Token Faucet](https://spl-token-faucet.com/)
## Basic Usage
### 1. Initialize the SDK
```typescript
import { InitializeScanToPay } from "@gaian-sdk/payments";
import { Environment } from "@gaian-sdk/payments/types/common";
const scanToPay = new InitializeScanToPay({
environment: Environment.DEV, // or Environment.PROD
paymentsUrl: "http://0.0.0.0:3000", // Your Payments API base URL
userUrl: "http://0.0.0.0:3000", // Your User API base URL
timeout: 30000, // 30 second timeout
solanaRpcUrl: process.env.SOLANA_RPC,
});
```
### 2. Set Up Solana Connection
```typescript
import { Connection, Keypair, Transaction } from "@solana/web3.js";
import bs58 from "bs58";
// Load your keypair from environment
function loadKeypairFromEnv() {
const privateKeyBase58 = process.env.SOLANA_PRIVATE_KEY;
if (!privateKeyBase58) throw new Error("SOLANA_PRIVATE_KEY required");
return Keypair.fromSecretKey(bs58.decode(privateKeyBase58));
}
const connection = new Connection(process.env.SOLANA_RPC || "");
```
## User Registration and KYC
Before processing payments, users need to be registered and complete KYC verification.
### User Registration
```typescript
const userResult = await scanToPay.registerUser({
email: "user@example.com",
walletAddress: "AYwJ2JCteMY2FxSJXjFpkCSxTvff1wzHgFrT1iDJpi8W", // Solana wallet address
});
console.log("User registered:", userResult);
// Returns: { user: { id: number, email: string, walletAddress: string } }
```
### Generate KYC Link
```typescript
const kycResult = await scanToPay.generateKYCLink({
userId: userResult.user.id.toString(),
email: userResult.user.email,
phone: "+1234567890", // Optional phone number
});
console.log("KYC link:", kycResult);
// Returns KYC link for user verification
```
### Complete User Registration Example
```typescript
import { InitializeScanToPay, Environment } from "@gaian-sdk/payments";
import "dotenv/config";
async function registerUserAndKYC() {
const scanToPay = new InitializeScanToPay({
environment: Environment.DEV,
paymentsUrl: "http://0.0.0.0:3000",
userUrl: "http://0.0.0.0:3000",
timeout: 30000,
});
try {
// 1. Register User
console.log("📝 Registering user...");
const userResult = await scanToPay.registerUser({
email: "test@example.com",
walletAddress: "AYwJ2JCteMY2FxSJXjFpkCSxTvff1wzHgFrT1iDJpi8W",
});
console.log("✅ User registered:", userResult);
// 2. Generate KYC Link
console.log("🔗 Generating KYC link...");
const kycResult = await scanToPay.generateKYCLink({
userId: userResult.user.id.toString(),
email: userResult.user.email,
phone: "+1234567890",
});
console.log("✅ KYC link generated:", kycResult);
return { success: true, userResult, kycResult };
} catch (error) {
console.error("❌ Registration failed:", error);
return { success: false, error };
}
}
registerUserAndKYC();
```
## Complete Payment Flow
### Step 1: Place an Order
```typescript
const orderResult = await scanToPay.placeOrder({
qrString:
"00020101021138520010A000000727012200069704160108355454570208QRIBFTTA53037045802VN6304B345",
amount: 15000, // Amount in minor units (15000 = 15.000 VND)
fiatCurrency: "VND", // Target fiat currency
cryptoCurrency: "USDC", // Crypto currency to use
fromAddress: "YOUR_WALLET_ADDRESS", // Your Solana wallet address
chain: "Solana",
});
console.log("Order ID:", orderResult.orderId);
console.log("Status:", orderResult.status); // Should be "awaiting_crypto_transfer"
```
### Step 2: Decode and Prepare Transaction
```typescript
// The SDK returns a Legacy Transaction (not Versioned)
const transaction = await scanToPay.decodeTransaction(orderResult);
console.log("Fee Payer:", transaction.feePayer?.toString());
console.log("Instructions:", transaction.instructions.length);
console.log("Blockhash:", transaction.recentBlockhash);
```
### Step 3: Sign and Submit Transaction
```typescript
async function signAndSubmitTransaction(
transaction: Transaction,
connection: Connection
) {
const payer = loadKeypairFromEnv();
try {
// Get fresh blockhash for better reliability
const { blockhash } = await connection.getLatestBlockhash("confirmed");
// Sign the transaction
transaction.sign(payer);
console.log("✅ Transaction signed successfully");
// Submit transaction
const txid = await connection.sendRawTransaction(transaction.serialize(), {
skipPreflight: true,
maxRetries: 3,
});
console.log("📤 Transaction submitted:", txid);
return txid;
} catch (error) {
console.error("Transaction failed:", error);
throw error;
}
}
const transactionHash = await signAndSubmitTransaction(transaction, connection);
```
### Step 4: Verify the Order
```typescript
// Wait a moment for transaction confirmation
await new Promise((resolve) => setTimeout(resolve, 10000));
const verifyResult = await scanToPay.verifyOrder({
orderId: orderResult.orderId,
transactionProof: transactionHash,
});
console.log("Verified:", verifyResult.status);
```
### Step 5: Check Final Status
```typescript
const statusResult = await scanToPay.getStatus({
orderId: orderResult.orderId,
});
console.log("Final Status:", statusResult.status);
```
## Order History
The SDK provides methods to retrieve order history for users, which can be useful for displaying transaction records and tracking payment activity.
### Get Order History by User Email/ID
Retrieve order history using a user's email address or user ID:
```typescript
const orderHistory = await scanToPay.getOrderHistoryByUserName({
identifier: "user@example.com", // Email address or user ID
page: 1, // Page number (optional, defaults to 1)
limit: 20, // Number of orders per page (optional, defaults to 20)
});
console.log("Order history:", orderHistory);
// Returns: { status, data: { user, orders: { items, pagination } } }
```
### Get Order History by Wallet Address
Retrieve order history using a Solana wallet address:
```typescript
const orderHistory = await scanToPay.getOrderHistoryByWallet({
walletAddress: "AYwJ2JCteMY2FxSJXjFpkCSxTvff1wzHgFrT1iDJpi8W",
page: 1, // Page number (optional, defaults to 1)
limit: 20, // Number of orders per page (optional, defaults to 20)
});
console.log("Order history:", orderHistory);
// Returns: { status, data: { user, orders: { items, pagination } } }
```
### Complete Order History Example
```typescript
import { InitializeScanToPay, Environment } from "@gaian-sdk/payments";
import "dotenv/config";
async function getOrderHistory() {
const scanToPay = new InitializeScanToPay({
environment: Environment.DEV,
paymentsUrl: "https://gaian-payments-backend-3.onrender.com",
userUrl: "http://127.0.0.1:3000",
timeout: 30000,
});
try {
// Get order history by email
console.log("📋 Fetching order history by email...");
const historyByEmail = await scanToPay.getOrderHistoryByUserName({
identifier: "test@example.com",
page: 1,
limit: 10,
});
console.log("✅ Order history by email:", historyByEmail.data.orders.items);
console.log("📄 Pagination:", historyByEmail.data.orders.pagination);
// Get order history by wallet address
console.log("📋 Fetching order history by wallet...");
const historyByWallet = await scanToPay.getOrderHistoryByWallet({
walletAddress: "AYwJ2JCteMY2FxSJXjFpkCSxTvff1wzHgFrT1iDJpi8W",
page: 1,
limit: 10,
});
console.log("✅ Order history by wallet:", historyByWallet.data.orders.items);
console.log("📄 Pagination:", historyByWallet.data.orders.pagination);
} catch (error) {
console.error("❌ Failed to fetch order history:", error);
}
}
getOrderHistory();
```
## Error Handling
```typescript
try {
// Your payment flow code here
} catch (error) {
console.error("Payment failed:", error);
// Handle specific error types
if (error.message.includes("insufficient funds")) {
console.log("💡 Please fund your wallet with SOL and USDC");
} else if (error.message.includes("blockhash")) {
console.log("💡 Try again - blockhash expired");
}
}
```
## Complete Example
```typescript
import { InitializeScanToPay, Environment } from "@gaian-sdk/payments";
import { Connection, Keypair, Transaction } from "@solana/web3.js";
import bs58 from "bs58";
import "dotenv/config";
async function processPayment() {
// Initialize SDK
const scanToPay = new InitializeScanToPay({
environment: Environment.DEV,
paymentsUrl: "http://0.0.0.0:3000",
userUrl: "http://0.0.0.0:3000",
timeout: 30000,
solanaRpcUrl: process.env.SOLANA_RPC,
});
const connection = new Connection(process.env.SOLANA_RPC || "");
const payer = Keypair.fromSecretKey(
bs58.decode(process.env.SOLANA_PRIVATE_KEY!)
);
try {
// 1. Place order
const order = await scanToPay.placeOrder({
qrString: "YOUR_QR_CODE_STRING",
amount: 15000,
fiatCurrency: "VND",
cryptoCurrency: "USDC",
fromAddress: payer.publicKey.toString(),
chain: "Solana",
});
// 2. Decode transaction
const transaction = await scanToPay.decodeTransaction(order);
// 3. Sign and submit
transaction.sign(payer);
const txHash = await connection.sendRawTransaction(
transaction.serialize(),
{
skipPreflight: true,
maxRetries: 3,
}
);
// 4. Wait and verify
await new Promise((resolve) => setTimeout(resolve, 10000));
const verification = await scanToPay.verifyOrder({
orderId: order.orderId,
transactionProof: txHash,
});
// 5. Get final status
const status = await scanToPay.getStatus({ orderId: order.orderId });
console.log("Payment completed:", {
orderId: order.orderId,
transactionHash: txHash,
verified: verification.status,
finalStatus: status.status,
});
} catch (error) {
console.error("Payment failed:", error);
}
}
processPayment();
```
## API Reference
### InitializeScanToPay Constructor Options
| Property | Type | Description |
| -------------- | ------------------------------------- | ------------------------------- |
| `environment` | `Environment.DEV \| Environment.PROD` | SDK environment |
| `paymentsUrl` | `string` | Payments API base URL |
| `userUrl` | `string` | User API base URL |
| `timeout` | `number` | Request timeout in milliseconds |
| `solanaRpcUrl` | `string` | Solana RPC endpoint URL |
### registerUser(params)
Registers a new user in the system.
**Parameters:**
- `email`: User's email address
- `walletAddress`: User's Solana wallet address
**Returns:** User object with `id`, `email`, and `walletAddress`
### generateKYCLink(params)
Generates a KYC verification link for a registered user.
**Parameters:**
- `userId`: User ID from registration (as string)
- `email`: User's email address
- `phone`: User's phone number (optional)
**Returns:** KYC link object for user verification
### placeOrder(params)
Places a new payment order.
**Parameters:**
- `qrString`: QR code string to process
- `amount`: Payment amount in minor currency units
- `fiatCurrency`: Target fiat currency (e.g., "VND")
- `cryptoCurrency`: Cryptocurrency to use (e.g., "USDC")
- `fromAddress`: Your Solana wallet address
- `chain`: Blockchain name ("Solana")
**Returns:** Order object with `orderId` and `status`
### decodeTransaction(orderResult)
Decodes the payment transaction from an order.
**Parameters:**
- `orderResult`: Result from `placeOrder()`
**Returns:** Solana `Transaction` object (Legacy format)
### verifyOrder(params)
Verifies a completed transaction.
**Parameters:**
- `orderId`: Order ID from `placeOrder()`
- `transactionProof`: Transaction hash from blockchain
**Returns:** Verification result with status
### getStatus(params)
Gets the current status of an order.
**Parameters:**
- `orderId`: Order ID to check
**Returns:** Status object with current order state
### getOrderHistoryByUserName(params)
Gets order history for a user by email or user ID.
**Parameters:**
- `identifier`: User's email address or user ID
- `page`: Page number (optional, defaults to 1)
- `limit`: Number of orders per page (optional, defaults to 20)
**Returns:** Object containing user information and paginated order history
### getOrderHistoryByWallet(params)
Gets order history for a user by wallet address.
**Parameters:**
- `walletAddress`: User's Solana wallet address
- `page`: Page number (optional, defaults to 1)
- `limit`: Number of orders per page (optional, defaults to 20)
**Returns:** Object containing user information and paginated order history
## Troubleshooting
### Common Issues
1. **"SOLANA_PRIVATE_KEY required"**
- Ensure your `.env` file has the private key in base58 format
2. **"insufficient funds"**
- Fund your wallet with SOL for transaction fees
- Fund your wallet with USDC for payments
3. **"blockhash not found"**
- The transaction took too long - retry with a fresh transaction
4. **Network timeouts**
- Check your RPC endpoint is working
- Try increasing the timeout value
5. **User registration errors**
- Verify email format is valid
- Ensure wallet address is a valid Solana public key
- Check that userUrl is correctly configured
### Getting Test Tokens
For development/testing on devnet:
- **SOL**: `solana airdrop 2 YOUR_ADDRESS --url devnet`
- **USDC**: Visit [https://faucet.circle.com/](https://faucet.circle.com/)
### Checking Balances
```typescript
// Check SOL balance
const balance = await connection.getBalance(payer.publicKey);
console.log("SOL balance:", balance / 1e9);
// Check USDC balance
import { getAssociatedTokenAddress, getAccount } from "@solana/spl-token";
const USDC_MINT = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"; // Devnet
const ata = await getAssociatedTokenAddress(
new PublicKey(USDC_MINT),
payer.publicKey
);
const tokenAccount = await getAccount(connection, ata);
console.log("USDC balance:", Number(tokenAccount.amount) / 1e6);
```
## Support
For issues and questions:
- Check the troubleshooting section above
- Review your environment setup
- Ensure your wallet has sufficient funds
- Verify your RPC endpoint is responsive