Skip to main content

Overview

Opinix Trade uses Cashfree Payment Gateway to process all financial transactions securely. You can deposit funds using UPI, cards, or net banking, and your balance is updated in real-time.
All payment processing is PCI-DSS compliant and uses bank-grade encryption to protect your financial information.

Payment gateway integration

Opinix integrates with Cashfree’s API version 2023-08-01:

Configuration

const CASHFREE_CONFIG = {
  url: process.env.CASHFREE_URL,
  clientId: process.env.CASHFREE_CLIENT_ID,
  clientSecret: process.env.CASHFREE_CLIENT_SECRET,
  webhookUrl: process.env.CASHFREE_WEBHOOK_URL,
  apiVersion: "2023-08-01"
};

Environment variables

CASHFREE_URL=https://api.cashfree.com/pg/orders
CASHFREE_CLIENT_ID=your_client_id
CASHFREE_CLIENT_SECRET=your_client_secret
CASHFREE_WEBHOOK_URL=https://yourdomain.com/api/webhooks/cashfree
Never expose Cashfree credentials in client-side code. All API calls must be made from your backend.

Deposit flow

Here’s the complete deposit process from user action to balance update:
1

User initiates deposit

Navigate to /wallet/deposit and enter amount:
const [depositAmount, setDepositAmount] = useState(0);

const handleQuickAdd = (amount: number) => {
  setDepositAmount(prev => prev + amount);
};

// Quick-add buttons
<Button onClick={() => handleQuickAdd(250)}>+250</Button>
<Button onClick={() => handleQuickAdd(500)}>+500</Button>
<Button onClick={() => handleQuickAdd(1000)}>+1000</Button>
2

Calculate transaction summary

System calculates GST and credits:
const rechargeAmount = depositAmount;
const gst = -(depositAmount * 0.18).toFixed(2);
const depositBalCredit = Number(
  (depositAmount - Math.abs(gst)).toFixed(2)
);
const promotionalBalCredit = Math.abs(gst);
const netBalance = depositAmount;

setSummary({
  rechargeAmount,
  gst,
  depositBalCredit,
  promotionalBalCredit,
  netBalance
});
Example for ₹500 deposit:
Recharge amount:         ₹500.00
GST applicable:         -₹90.00
Deposit bal. credit:     ₹410.00
Promotional bal. credit: +₹90.00
Net Balance:             ₹500.00
3

Click Recharge button

User confirms and initiates payment:
async function handleRechargeClick() {
  if (!data?.user) {
    redirect("/api/auth/signin");
  }
  
  const userid = data.user.id;
  const isRechargeDone = await DepositeMoneyInWallet(
    userid!,
    depositAmount
  );
  
  if (isRechargeDone?.success) {
    toast.success("Recharge Success");
  } else {
    toast.error("Error While Recharging");
  }
}
4

Create payment order

Backend calls Cashfree API to initiate payment:
export async function POST(req: NextRequest) {
  try {
    const body = await req.json();
    
    const response = await fetch(process.env.CASHFREE_URL!, {
      method: "POST",
      headers: {
        "accept": "application/json",
        "content-type": "application/json",
        "x-api-version": "2023-08-01",
        "x-client-id": process.env.CASHFREE_CLIENT_ID as string,
        "x-client-secret": process.env.CASHFREE_CLIENT_SECRET as string
      },
      body: JSON.stringify({
        customer_details: {
          customer_id: body.customer_id,
          customer_phone: body.customer_phone
        },
        order_id: body.order_id,
        order_amount: body.order_amount,
        order_currency: "INR",
        order_meta: {
          notify_url: process.env.CASHFREE_WEBHOOK_URL,
          payment_methods: "cc,dc,upi"
        }
      })
    });
    
    const data = await response.json();
    
    if (!response.ok) {
      return NextResponse.json(
        { message: data.message },
        { status: response.status }
      );
    }
    
    return NextResponse.json(data, { status: 200 });
  } catch (error) {
    return NextResponse.json(
      { message: "Something went wrong", error },
      { status: 500 }
    );
  }
}
5

Redirect to Cashfree

User is redirected to Cashfree’s secure payment page where they:
  • Choose payment method (UPI/Card/Net Banking)
  • Enter payment details
  • Complete authentication (OTP/PIN)
  • Receive payment confirmation
6

Payment verification

After payment, Cashfree redirects to status endpoint:
export async function GET(
  req: NextRequest,
  { params }: { params: { order_id: string } }
) {
  const { order_id } = params;
  
  const response = await fetch(
    process.env.CASHFREE_URL + order_id,
    {
      method: "GET",
      headers: {
        "accept": "application/json",
        "content-type": "application/json",
        "x-api-version": "2023-08-01",
        "x-client-id": process.env.CASHFREE_CLIENT_ID as string,
        "x-client-secret": process.env.CASHFREE_CLIENT_SECRET as string
      }
    }
  );
  
  const data = await response.json();
  
  if (data.order_status === "PAID") {
    return NextResponse.redirect(new URL("/", req.url));
  } else {
    return NextResponse.redirect(new URL("/404", req.url));
  }
}
7

Update wallet balance

On successful payment, wallet is credited:
export const DepositeMoneyInWallet = async (
  userId: string,
  amount: number
) => {
  try {
    const user = await prisma.user.findUnique({
      where: { id: userId }
    });
    
    if (!user) {
      return { success: false, message: "User not found." };
    }
    
    const newBalance = Math.max(0, user.balance + amount);
    
    await prisma.user.update({
      where: { id: userId },
      data: { balance: newBalance }
    });
    
    return { success: true, message: "Deposit successful." };
  } catch (e) {
    console.error("Error depositing money:", e);
    return { 
      success: false, 
      message: "Error while depositing money." 
    };
  }
};

Supported payment methods

Cashfree supports multiple payment options:
Unified Payments InterfaceSupported UPI apps:
  • Google Pay
  • PhonePe
  • Paytm
  • BHIM
  • Amazon Pay
  • All bank UPI apps
Process:
  1. Select UPI as payment method
  2. Choose your UPI app or enter VPA
  3. Approve payment in your UPI app
  4. Instant credit (typically < 10 seconds)
Limits:
  • Minimum: ₹100
  • Maximum: ₹1,00,000 per transaction
  • Daily limit: As per your bank
Fees:
  • No processing fees
  • 18% GST (credited back as promotional balance)

Payment methods configuration

Cashfree payment methods are specified in the order creation:
order_meta: {
  notify_url: process.env.CASHFREE_WEBHOOK_URL,
  payment_methods: "cc,dc,upi"  // Credit card, debit card, UPI
}
Available codes:
  • cc - Credit cards
  • dc - Debit cards
  • upi - UPI
  • nb - Net banking
  • app - Wallets (Paytm, etc.)
  • paylater - Pay later options

GST handling

India mandates 18% GST on online gaming and prediction markets:

GST calculation

const calculateGST = (amount: number) => {
  const gstRate = 0.18; // 18%
  const gstAmount = amount * gstRate;
  
  return {
    grossAmount: amount,
    gst: gstAmount,
    netAmount: amount - gstAmount,
    promotionalCredit: gstAmount,
    finalBalance: amount  // Net + promotional = gross
  };
};

Example calculation

1

User deposits ₹1000

Gross deposit amount: ₹1000
2

GST deducted

GST (18%): ₹1000 × 0.18 = ₹180
3

Deposit balance credited

Net amount: ₹1000 - ₹180 = ₹820
4

Promotional balance credited

Promotional credit: ₹180
5

Final balance

Total available: ₹820 + ₹180 = ₹1000
While GST is technically deducted, you receive the full amount through promotional credits, so your trading balance equals your deposit.

Webhooks

Cashfree sends payment status updates via webhooks:
export async function POST(req: NextRequest) {
  try {
    const body = await req.json();
    
    // Verify webhook signature (recommended)
    const isValid = verifyCashfreeSignature(
      body,
      req.headers.get('x-webhook-signature')
    );
    
    if (!isValid) {
      return NextResponse.json(
        { error: "Invalid signature" },
        { status: 401 }
      );
    }
    
    // Process payment based on event type
    switch (body.type) {
      case "PAYMENT_SUCCESS":
        await handleSuccessfulPayment(body.data);
        break;
        
      case "PAYMENT_FAILED":
        await handleFailedPayment(body.data);
        break;
        
      case "PAYMENT_PENDING":
        await handlePendingPayment(body.data);
        break;
    }
    
    return NextResponse.json({ success: true });
  } catch (error) {
    console.error("Webhook error:", error);
    return NextResponse.json(
      { error: "Webhook processing failed" },
      { status: 500 }
    );
  }
}

async function handleSuccessfulPayment(data: any) {
  const { order_id, customer_id, order_amount } = data;
  
  // Credit user wallet
  await DepositeMoneyInWallet(customer_id, order_amount);
  
  // Log transaction
  await prisma.transaction.create({
    data: {
      userId: customer_id,
      orderId: order_id,
      amount: order_amount,
      status: "SUCCESS",
      type: "DEPOSIT"
    }
  });
}

Payment statuses

Cashfree payments can have various statuses:
Order created, awaiting payment
  • User redirected to payment page
  • Payment method not yet selected
  • No funds transferred
Next steps: User completes payment

Transaction tracking

All payment transactions are logged:
interface Transaction {
  id: string;
  userId: string;
  orderId: string;
  amount: number;
  status: "SUCCESS" | "FAILED" | "PENDING";
  type: "DEPOSIT" | "WITHDRAWAL";
  createdAt: Date;
  updatedAt: Date;
}

// Fetch user transactions
export async function getTransactions(userId: string) {
  return await prisma.transaction.findMany({
    where: { userId },
    orderBy: { createdAt: 'desc' },
    take: 50
  });
}

Withdrawals

Withdrawal functionality is currently under development. Users can request manual withdrawals by contacting support.
Planned withdrawal flow:
1

Request withdrawal

User enters amount and bank details
2

Validate request

  • Check available balance
  • Verify bank account
  • Apply withdrawal limits
3

Process payout

Use Cashfree Payout API:
await fetch('https://payout-api.cashfree.com/payout', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    beneId: user.bankAccountId,
    amount: withdrawalAmount,
    transferId: uniqueId
  })
});
4

Deduct balance

Update user wallet:
await prisma.user.update({
  where: { id: userId },
  data: {
    balance: { decrement: withdrawalAmount }
  }
});
5

Confirm transfer

Bank transfer completes in 1-3 business days

Security measures

Cashfree is PCI-DSS Level 1 certified:
  • Card details never touch Opinix servers
  • All data encrypted in transit (TLS 1.2+)
  • Secure token-based transactions
  • Regular security audits
Verify webhook authenticity:
function verifyCashfreeSignature(
  payload: any,
  signature: string | null
): boolean {
  if (!signature) return false;
  
  const computed = crypto
    .createHmac('sha256', process.env.CASHFREE_CLIENT_SECRET!)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  return computed === signature;
}
Generate unique, unpredictable order IDs:
import { v4 as uuidv4 } from 'uuid';

const orderId = `OPN_${Date.now()}_${uuidv4().slice(0, 8)}`;
// Example: OPN_1709308800000_a1b2c3d4
Server-side validation prevents tampering:
if (amount < 100) {
  return { error: "Minimum deposit is ₹100" };
}

if (amount > 100000) {
  return { error: "Maximum deposit is ₹1,00,000" };
}

if (!Number.isInteger(amount)) {
  return { error: "Amount must be a whole number" };
}

Troubleshooting

Possible causes:
  • Webhook delay (wait 5-10 minutes)
  • Webhook failure (check server logs)
  • Database sync issue
Resolution:
  • Check transaction status via Cashfree dashboard
  • Verify webhook endpoint is accessible
  • Manually trigger balance update if needed
  • Contact support with order ID
What happened:
  • Bank authorized payment
  • Cashfree/Opinix rejected it
  • Bank holds amount as pending
Resolution:
  • Automatic reversal in 5-7 business days
  • Check with your bank for faster reversal
  • Don’t retry until reversal completes
Possible reasons:
  • VPA (UPI ID) entered incorrectly
  • UPI app not installed/updated
  • Network issues
Resolution:
  • Verify VPA format: username@bank
  • Update UPI app to latest version
  • Try different payment method
  • Use QR code option instead
Common reasons:
  • Insufficient balance
  • Daily limit exceeded
  • International transactions disabled
  • Incorrect CVV/OTP
Resolution:
  • Check card balance and limits
  • Enable online transactions in bank app
  • Verify CVV and try again
  • Contact your bank if issue persists

Payment limits

Payment MethodMinimumMaximum (Per Transaction)Processing Time
UPI₹100₹1,00,000Instant
Debit Card₹100As per bank2-5 minutes
Credit Card₹100As per credit limit2-5 minutes
Net Banking₹100As per bank5-15 minutes
Limits may vary based on your bank’s policies. Check with your bank for specific transaction limits.

Next steps