Complete API documentation for the points-based wallet system with P2P trading, escrow, and charity donations.
System architecture and key concepts
All endpoints require JWT Bearer token unless marked as Public
Authorization: Bearer <jwt_token>
/wallet
View wallet balance and transaction history
Get current user's wallet balance
{
"points": 50000,
"knots": 50,
"frozenPoints": 5000,
"frozenKnots": 5,
"availablePoints": 45000,
"availableKnots": 45
}
Get another user's wallet balance (public info)
Get current user's transaction history
| Query Param | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20, max: 100) |
Purchase points using Thawani payment gateway (default)
Initiate a top-up to buy points (default gateway: Thawani). To force EdfaPay, pass gateway=edfapay.
| Field | Type | Description |
|---|---|---|
points | number | Points to purchase (min: 1000) |
gateway | enum | Payment gateway (default: thawani). Allowed: thawani, edfapay. |
termUrl3ds | string | 3DS return URL for your app |
currency | string | Currency code (default: OMR) |
payerFirstName | string | Payer's first name |
payerLastName | string | Payer's last name |
payerEmail | string | Payer's email |
payerPhone | string | Payer's phone |
payerAddress | string | Payer's address |
payerCity | string | Payer's city |
payerCountry | string | Country code (default: OM) |
payerZip | string | Postal code |
payerIp | string | Payer's IP address |
{
"topUpId": "uuid",
"redirectUrl": "https://checkout.thawani.om/pay/...",
"pointsToCredit": 5000,
"amountInCurrency": 51.50,
"currency": "OMR"
}
amountInCurrency: Stored/returned in OMR major units (2 decimals) and includes the 3% commission.
redirectUrl. After payment, Thawani calls the webhook to credit points. (EdfaPay callbacks remain supported for legacy flows.)
Get user's top-up history
Send points to other users
Transfer points to another user
| Field | Type | Description |
|---|---|---|
toUserId | uuid | Recipient user ID |
amount | number | Points to transfer (min: 1) |
description | string | Transfer note/memo |
Pay for orders with points using escrow protection. Carriers can set custom shipping/escrow durations.
Set custom escrow/shipping duration for an order (must be called before checkout)
| Field | Type | Description |
|---|---|---|
hours | number | Escrow duration in hours (1-168, i.e., 1 hour to 7 days) |
isCarrier: true can set escrow hours. This allows carriers to set realistic shipping timeframes based on distance and logistics.
Pay for an order with points (creates escrow)
Confirm delivery (buyer only). For points-based orders, delivery confirmation must be done by the assigned carrier via /shipments/:shipmentId/confirm-delivery
Carrier confirms pickup from merchant/company (must be the assigned carrier unless admin)
Carrier confirms delivery to customer. For points-based orders, this releases escrow and completes the order
Mark order as shipped (seller only)
Cancel order and refund escrow (buyer or seller)
| Field | Type | Description |
|---|---|---|
reason | string | Cancellation reason |
Buy and sell points with other users (price guard: 950-1000 baisas/Knot)
List all open trade offers
List user's trades
| Query Param | Type | Description |
|---|---|---|
role | string | Filter: seller | buyer | all |
status | string | Filter: open | pending | approved | completed | cancelled |
Create a trade offer (sell points)
| Field | Type | Description |
|---|---|---|
pointsAmount | number | Points to sell (min: 1000) |
pricePerKnot | number | Price in baisas (950-1000) |
expiresInHours | number | Offer expiry (default: 24) |
Request to buy from a trade offer
Seller approves buyer's request (freezes seller's points)
Buyer completes trade (after external payment)
Cancel trade (unfreezes points if approved)
Admins can mint new points directly for users
Mint points directly for a user
| Field | Type | Description |
|---|---|---|
userId | UUID | Target user's ID |
points | number | Points to mint (min: 1) |
reason | string | Reason for minting |
{
"success": true,
"message": "Minted 1000 points for user",
"transaction": { "id": "...", "amount": "1000", ... },
"newBalance": "5000"
}
Create charity profiles and accept point donations
List verified charities
Get charity by slug (for donation pages/QR codes)
Get charity details
Get charity's donation history
Register a new charity profile
| Field | Type | Description |
|---|---|---|
name | string | Charity name |
slug | string | Unique URL-friendly identifier |
description | string | Charity description |
websiteUrl | string | Website URL |
registrationNumber | string | Official registration number |
Update charity profile (owner only)
Donate points to a charity
| Field | Type | Description |
|---|---|---|
charityId | uuid | Charity ID |
amount | number | Points to donate (min: 100) |
message | string | Donation message |
Donate by charity slug (for QR code donations)
| Field | Type | Description |
|---|---|---|
charitySlug | string | Charity slug |
amount | number | Points to donate |
message | string | Donation message |
Convert points back to real money via bank transfer
Create a withdrawal request (convert points to bank transfer)
| Field | Type | Description |
|---|---|---|
amount | number | Points to withdraw (min: 1000) |
bankName | string | Bank name (max: 100) |
accountNumber | string | Bank account number (max: 50) |
accountHolderName | string | Account holder name (max: 200) |
iban | string | IBAN (max: 50) |
reason | enum | Reason: selling_plastic | selling_metals | selling_paper | selling_glass | selling_electronics | selling_various_materials | transport_service | landfill_service | other |
reasonNotes | string | Additional notes for reason (max: 1000, recommended if reason is "other") |
attachmentUrl | string | URL of supporting document or image |
userNote | string | Note to admin (max: 500) |
{
"id": "uuid",
"amount": "10000",
"status": "pending",
"bankName": "Bank Muscat",
"accountNumber": "1234567890",
"reason": "selling_various_materials",
"attachmentUrl": "https://example.com/doc.pdf",
"reasonNotes": null,
"currencyAmount": "10000",
"currency": "OMR"
}
Get user's withdrawal request history
| Query Param | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20) |
Get details of a specific withdrawal request
Cancel a pending withdrawal request (restores frozen points)
Administrative functions (requires admin privileges)
Get wallet system statistics
{
"totalCirculatingPoints": 50000000,
"totalFrozenPoints": 1000000,
"totalUsersWithWallet": 1250,
"totalTopUpAmountInCurrency": 45000000,
"currency": "OMR"
}
Get platform profit reports (weekly / monthly / yearly) aggregated by period and side (buyer/seller)
| Query Param | Type | Description |
|---|---|---|
period | string | weekly | monthly | yearly |
from | string | Start date (ISO), optional |
to | string | End date (ISO), optional |
GET /admin/profit/reports?period=monthly&from=2026-01-01&to=2026-12-31
[
{
"period": "2026-01-01T00:00:00.000Z",
"side": "buyer",
"totalQuantity": 1500,
"totalSar": 15,
"totalPoints": 1500
},
{
"period": "2026-01-01T00:00:00.000Z",
"side": "seller",
"totalQuantity": 1500,
"totalSar": 15,
"totalPoints": 1500
}
]
Get full dashboard with wallet and audit stats
Get detailed dashboard with date range
| Query Param | Type | Description |
|---|---|---|
startDate | ISO date | Start of period |
endDate | ISO date | End of period |
List all mint requests
| Query Param | Type | Description |
|---|---|---|
status | string | Filter: pending | partially_approved | approved | rejected |
Approve a mint request (requires 2 different admins)
Reject a mint request
| Field | Type | Description |
|---|---|---|
reason | string | Rejection reason |
List all charities (including pending)
| Query Param | Type | Description |
|---|---|---|
status | string | Filter: pending | verified | suspended |
Verify a charity (enables donations)
List all withdrawal requests
| Query Param | Type | Description |
|---|---|---|
status | string | Filter: pending | processing | completed | rejected | cancelled |
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20) |
Get count of pending withdrawal requests (for dashboard badge)
5
Mark withdrawal as completed after bank transfer
| Field | Type | Description |
|---|---|---|
receiptImageUrl | string | URL of uploaded bank transfer receipt image |
adminNote | string | Note visible to user (max: 500) |
referenceNumber | string | Bank transaction reference (max: 50) |
Reject a withdrawal request (restores user's frozen points)
| Field | Type | Description |
|---|---|---|
reason | string | Rejection reason (max: 500) |
Get audit logs with filtering
| Query Param | Type | Description |
|---|---|---|
userId | uuid | Filter by user |
action | string | Filter by action type |
severity | string | Filter: info | warning | critical |
Advanced audit log search
Get critical security events
Get all audit logs for a specific user
Get audit statistics
Get detailed user wallet info (balance, transactions, audit logs)
Freeze points in user's wallet
| Field | Type | Description |
|---|---|---|
userId | uuid | Target user ID |
points | number | Points to freeze |
reason | string | Reason for freeze |
Unfreeze points in user's wallet
Adjust user's balance (for corrections)
| Field | Type | Description |
|---|---|---|
userId | uuid | Target user ID |
amount | number | Amount to adjust (can be negative) |
reason | string | Reason for adjustment |
Payment gateway callback endpoints
EdfaPay payment callback (called by EdfaPay servers)
Thawani payment callback (called by Thawani servers)
EdfaPay 3DS return handler (user redirect after 3DS)
Core data models for the wallet system
User's point balance and statistics
| Field | Type | Description |
|---|---|---|
balancePoints | bigint | Total points in wallet |
frozenPoints | bigint | Points held in escrow/trades |
totalEarnedPoints | bigint | Lifetime earned points |
totalSpentPoints | bigint | Lifetime spent points |
Record of all point movements
| Field | Type | Description |
|---|---|---|
type | enum | TOPUP, TRANSFER, PURCHASE, PURCHASE_COMPLETE, REFUND, DONATION, MINT |
status | enum | PENDING, COMPLETED, FAILED, CANCELLED |
amount | bigint | Points transferred |
sender | User | Source user (null for system) |
receiver | User | Destination user |
Payment gateway top-up records
| Field | Type | Description |
|---|---|---|
gateway | enum | EDFAPAY, THAWANI, STRIPE |
status | enum | PENDING, COMPLETED, FAILED, EXPIRED |
amountInCurrency | bigint | Amount in minor units (baisas) |
pointsToCredit | bigint | Points to add on success |
Buyer protection for order payments
| Field | Type | Description |
|---|---|---|
status | enum | HOLDING, RELEASED, REFUNDED |
amount | bigint | Frozen points |
expiresAt | timestamp | Auto-refund deadline (carrier-set or 12h default) |
Order entity fields related to escrow
| Field | Type | Description |
|---|---|---|
escrowId | uuid | Reference to escrow record (if paid with points) |
escrowHours | int | null | Carrier-set escrow duration (1-168 hours, null = 12h default) |
Peer-to-peer point trading offers
| Field | Type | Description |
|---|---|---|
status | enum | OPEN, PENDING, APPROVED, COMPLETED, CANCELLED, EXPIRED |
pointsAmount | bigint | Points for sale |
pricePerKnot | int | Price in baisas (950-1000) |
totalPrice | bigint | Total price in baisas |
Merchant requests to mint new points
| Field | Type | Description |
|---|---|---|
status | enum | PENDING, PARTIALLY_APPROVED, APPROVED, REJECTED |
requestedPoints | bigint | Points to mint |
justification | text | Business reason |
approvedBy1/2 | User | Two admins required |
year | int | For annual limit tracking |
Verified charity profiles for donations
| Field | Type | Description |
|---|---|---|
status | enum | PENDING, VERIFIED, SUSPENDED |
slug | string | URL-friendly unique ID |
totalDonationsReceived | bigint | Cumulative donations |
donorCount | int | Number of donors |
User requests to withdraw points to bank account
| Field | Type | Description |
|---|---|---|
status | enum | PENDING, PROCESSING, COMPLETED, REJECTED, CANCELLED |
amount | bigint | Points to withdraw |
bankName | string | User's bank name |
accountNumber | string | Bank account number |
accountHolderName | string | Account holder name |
iban | string | Optional IBAN |
receiptImageUrl | text | Admin's uploaded receipt |
processedBy | User | Admin who resolved |
processedAt | timestamp | When resolved |
rejectionReason | text | Why rejected (if applicable) |
currencyAmount | bigint | Equivalent in currency |
Audit trail for all wallet actions
| Field | Type | Description |
|---|---|---|
action | enum | 40+ action types (topup, transfer, escrow, trade, mint, etc.) |
severity | enum | INFO, WARNING, CRITICAL |
pointsAmount | bigint | Points involved |
metadata | jsonb | Additional context |
Automatic email alerts for wallet events
All wallet actions trigger email notifications to relevant users
| Event | Recipient | Description |
|---|---|---|
Top-Up Initiated | Buyer | Payment process started |
Top-Up Completed | Buyer | Points credited to wallet |
Top-Up Failed | Buyer | Payment failed or expired |
Transfer Sent | Sender | Points sent to another user |
Transfer Received | Receiver | Points received from user |
Escrow Created | Buyer | Order payment held |
Escrow Released | Seller | Payment released after delivery |
Escrow Refunded | Buyer | Payment refunded (cancel/timeout) |
Trade Created | Seller | Trade offer listed |
Trade Requested | Seller | Buyer wants to purchase |
Trade Approved | Buyer | Seller approved request |
Trade Completed | Both | Points transferred |
Trade Cancelled | Both | Trade cancelled |
Donation Sent | Donor | Thank you for donating |
Donation Received | Charity | New donation alert |
Mint Approved | Merchant | Points minted to wallet |
Mint Rejected | Merchant | Request rejected with reason |
Charity Verified | Owner | Charity approved for donations |
Withdrawal Requested | User | Withdrawal request submitted |
Withdrawal Request (Admin) | All Admins | New withdrawal needs processing |
Withdrawal Completed | User | Bank transfer completed with receipt |
Withdrawal Rejected | User | Request rejected, points restored |
Security Alert | User | Suspicious activity detected |
Comprehensive activity tracking for compliance and security
All wallet operations are logged with full context
| Category | Actions |
|---|---|
| Wallet | wallet_created |
| Top-Up | topup_initiated, topup_completed, topup_failed |
| Transfer | transfer_sent, transfer_received |
| Escrow | escrow_created, escrow_released, escrow_refunded, escrow_expired |
| P2P Trade | trade_created, trade_requested, trade_approved, trade_completed, trade_cancelled |
| Charity | charity_created, charity_verified, donation_sent, donation_received |
| Admin | admin_freeze, admin_unfreeze, admin_adjustment, admin_mint |
| Withdrawal | withdrawal_requested, withdrawal_completed, withdrawal_rejected, withdrawal_cancelled |
Environment variables and system settings
# SMTP Configuration (Email)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-username
SMTP_PASS=your-password
MAIL_FROM=Invare <support@invare.sa>
# App Settings
APP_NAME=Invare
SUPPORT_EMAIL=support@invare.sa
# EdfaPay Configuration
EDFAPAY_API_BASE=https://api.edfapay.com
EDFAPAY_MERCHANT_ID=your-merchant-id
EDFAPAY_PASSWORD=your-password
EDFAPAY_CURRENCY=OMR
| Constant | Value | Description |
|---|---|---|
POINTS_PER_KNOT | 100 | Points per Knot (display unit) |
DEFAULT_ESCROW_HOURS | 12 | Default hours before auto-refund (carrier can override per order) |
ANNUAL_MINT_LIMIT | 1,000,000 | Max points per merchant/year |
P2P_PRICE_MIN | 950 | Min baisas per Knot |
P2P_PRICE_MAX | 1000 | Max baisas per Knot |
MIN_DONATION | 100 | Minimum donation points |
| Job | Schedule | Description |
|---|---|---|
refundExpiredEscrows | Every 5 min | Auto-refund escrows past 12 hours |
cleanupExpiredTopUps | Daily midnight | Mark pending top-ups as expired (24h) |