> ## Documentation Index
> Fetch the complete documentation index at: https://twigz.arqqin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Store Wallet

> Financial management system for store earnings and payouts

## Overview

The Store Wallet API manages financial operations for store owners including balance tracking, payout requests, transaction history, and earnings management. This system automatically processes order earnings and handles marketplace commission deductions.

**Location:** `convex/stores/wallet.ts`

## Wallet Structure

<CardGroup cols={2}>
  <Card title="Available Balance" icon="wallet">
    Funds available for immediate payout requests
  </Card>

  <Card title="Pending Balance" icon="clock">
    Earnings from recent orders (held for security period)
  </Card>

  <Card title="Total Earnings" icon="chart-line">
    Lifetime earnings from all completed orders
  </Card>

  <Card title="Payout History" icon="history">
    Complete record of all payout transactions
  </Card>
</CardGroup>

## Get Store Wallet Balance

Retrieve comprehensive wallet information and statistics for a store.

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID to get wallet information for
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const wallet = await convex.query(api.stores.wallet.getStoreWalletBalance, { 
    storeId: "j123456789" 
  });
  ```

  ```javascript JavaScript theme={null}
  const wallet = await convex.query("stores.wallet.getStoreWalletBalance", { 
    storeId: "j123456789" 
  });
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      f"{CONVEX_URL}/api/query",
      json={
          "path": "stores.wallet.getStoreWalletBalance",
          "args": {
              "storeId": "j123456789"
          }
      },
      headers={"Authorization": f"Bearer {token}"}
  )

  wallet = response.json()
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "_id": "w123456789",
    "storeId": "j123456789",
    "availableBalance": 1250.75,
    "pendingBalance": 150.25,
    "totalEarnings": 5000.00,
    "totalOrders": 42,
    "totalPayouts": 3500.00,
    "totalCommissionPaid": 245.50,
    "minPayoutAmount": 50.00,
    "maxPayoutAmount": 2000.00,
    "lastOrderDate": 1640995800000,
    "lastPayoutDate": 1640990000000,
    "_creationTime": 1640980000000
  }
  ```
</ResponseExample>

## Request Payout

Request a payout from the store wallet to the registered bank account.

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID requesting the payout
</ParamField>

<ParamField path="amount" type="number" required>
  Payout amount (must be within available balance and limits)
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const payoutId = await convex.mutation(api.stores.wallet.requestPayout, {
    storeId: "j123456789",
    amount: 500.00
  });
  ```

  ```javascript JavaScript theme={null}
  const payoutId = await convex.mutation("stores.wallet.requestPayout", {
    storeId: "j123456789",
    amount: 750.00
  });
  ```

  ```python Python theme={null}
  response = requests.post(
      f"{CONVEX_URL}/api/mutation",
      json={
          "path": "stores.wallet.requestPayout",
          "args": {
              "storeId": "j123456789",
              "amount": 500.00
          }
      },
      headers={"Authorization": f"Bearer {token}"}
  )

  payout_id = response.json()
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  "p123456789"
  ```
</ResponseExample>

## Get Payout History

Retrieve payout-related transactions by filtering wallet transactions.

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID
</ParamField>

<ParamField path="limit" type="number">
  Maximum number of payout records to return
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const payoutHistory = await convex.query(api.stores.wallet.getWalletTransactionHistory, {
    storeId: "j123456789",
    type: "payout",
    limit: 20
  });
  ```

  ```javascript JavaScript theme={null}
  const payoutHistory = await convex.query("stores.wallet.getWalletTransactionHistory", {
    storeId: "j123456789",
    type: "payout"
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  [
    {
      "_id": "wt987654321",
      "storeWalletId": "w123456789",
      "type": "payout",
      "amount": -500.00,
      "description": "Payout to bank account",
      "payoutId": "p123456789",
      "balanceAfter": 750.75,
      "_creationTime": 1640990000000
    }
  ]
  ```
</ResponseExample>

## Get Wallet Transaction History

Retrieve detailed transaction history for a store wallet.

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID
</ParamField>

<ParamField path="limit" type="number">
  Maximum number of transaction records
</ParamField>

<ParamField path="type" type="string">
  Filter by transaction type: `order_funding`, `commission_deduction`, `payout`, `payout_fee`, `refund`, `adjustment`, `penalty`, `bonus`
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Get all transactions
  const allTransactions = await convex.query(api.stores.wallet.getWalletTransactionHistory, {
    storeId: "j123456789",
    limit: 50
  });

  // Get only order funding transactions
  const orderTransactions = await convex.query(api.stores.wallet.getWalletTransactionHistory, {
    storeId: "j123456789",
    type: "order_funding",
    limit: 30
  });
  ```

  ```javascript JavaScript theme={null}
  const transactions = await convex.query("stores.wallet.getWalletTransactionHistory", {
    storeId: "j123456789",
    type: "payout"
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  [
    {
      "_id": "wt123456789",
      "storeWalletId": "w123456789",
      "type": "order_funding",
      "amount": 45.50,
      "description": "Order #o123456789 earnings",
      "orderId": "o123456789",
      "balanceAfter": 1250.75,
      "metadata": {
        "orderTotal": 50.97,
        "platformCommission": 5.47,
        "storeEarnings": 45.50
      },
      "_creationTime": 1640995800000
    },
    {
      "_id": "wt987654321",
      "storeWalletId": "w123456789",
      "type": "commission_deduction",
      "amount": -5.47,
      "description": "Platform commission for order #o123456789",
      "orderId": "o123456789",
      "balanceAfter": 1205.25,
      "_creationTime": 1640995800000
    },
    {
      "_id": "wt555666777",
      "storeWalletId": "w123456789",
      "type": "payout",
      "amount": -500.00,
      "description": "Payout to bank account",
      "payoutId": "p123456789",
      "balanceAfter": 750.75,
      "_creationTime": 1640990000000
    }
  ]
  ```
</ResponseExample>

## Wallet Dashboard Component

Complete wallet management interface:

<CodeGroup>
  ```typescript React Wallet Dashboard theme={null}
  import { useState } from 'react';
  import { useQuery, useMutation } from 'convex/react';
  import { api } from './convex/_generated/api';
  import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';

  interface WalletDashboardProps {
    storeId: string;
  }

  function WalletDashboard({ storeId }: WalletDashboardProps) {
    const [payoutAmount, setPayoutAmount] = useState<string>('');
    const [showPayoutForm, setShowPayoutForm] = useState(false);

    const wallet = useQuery(api.stores.wallet.getStoreWalletBalance, { storeId });
    const payoutHistory = useQuery(api.stores.wallet.getWalletTransactionHistory, { 
      storeId, 
      type: 'payout',
      limit: 10 
    });
    const transactions = useQuery(api.stores.wallet.getWalletTransactionHistory, { 
      storeId, 
      limit: 20 
    });

    const requestPayout = useMutation(api.stores.wallet.requestPayout);

    const handlePayoutRequest = async () => {
      if (!wallet || !payoutAmount) return;

      const amount = parseFloat(payoutAmount);
      
      if (amount < wallet.minPayoutAmount) {
        alert(`Minimum payout amount is $${wallet.minPayoutAmount}`);
        return;
      }
      
      if (amount > wallet.availableBalance) {
        alert('Insufficient available balance');
        return;
      }

      try {
        const payoutId = await requestPayout({ storeId, amount });
        console.log('Payout requested:', payoutId);
        setPayoutAmount('');
        setShowPayoutForm(false);
      } catch (error) {
        console.error('Payout request failed:', error);
        alert('Payout request failed. Please try again.');
      }
    };

    if (!wallet) {
      return <div>Loading wallet...</div>;
    }

    // Prepare chart data
    const chartData = transactions?.filter(t => t.type === 'order_funding')
      .slice(0, 10)
      .reverse()
      .map(t => ({
        date: new Date(t._creationTime).toLocaleDateString(),
        earnings: t.amount,
        balance: t.balanceAfter
      })) || [];

    return (
      <div className="space-y-6">
        {/* Wallet Overview */}
        <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-green-100 rounded-lg">
                <svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Available Balance</p>
                <p className="text-2xl font-bold text-green-600">${wallet.availableBalance.toFixed(2)}</p>
              </div>
            </div>
          </div>

          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-yellow-100 rounded-lg">
                <svg className="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Pending Balance</p>
                <p className="text-2xl font-bold text-yellow-600">${wallet.pendingBalance.toFixed(2)}</p>
              </div>
            </div>
          </div>

          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-blue-100 rounded-lg">
                <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Total Earnings</p>
                <p className="text-2xl font-bold text-blue-600">${wallet.totalEarnings.toFixed(2)}</p>
              </div>
            </div>
          </div>

          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-purple-100 rounded-lg">
                <svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Total Payouts</p>
                <p className="text-2xl font-bold text-purple-600">${wallet.totalPayouts.toFixed(2)}</p>
              </div>
            </div>
          </div>
        </div>

        {/* Payout Request Section */}
        <div className="bg-white rounded-lg shadow p-6">
          <div className="flex justify-between items-center mb-4">
            <h2 className="text-lg font-semibold text-gray-900">Request Payout</h2>
            {!showPayoutForm && (
              <button
                onClick={() => setShowPayoutForm(true)}
                disabled={wallet.availableBalance < wallet.minPayoutAmount}
                className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Request Payout
              </button>
            )}
          </div>

          {wallet.availableBalance < wallet.minPayoutAmount && (
            <div className="bg-yellow-50 p-4 rounded-lg mb-4">
              <p className="text-yellow-800 text-sm">
                ⚠️ Minimum payout amount is ${wallet.minPayoutAmount.toFixed(2)}. 
                You need ${(wallet.minPayoutAmount - wallet.availableBalance).toFixed(2)} more to request a payout.
              </p>
            </div>
          )}

          {showPayoutForm && (
            <div className="space-y-4">
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-2">
                  Payout Amount ($)
                </label>
                <input
                  type="number"
                  step="0.01"
                  min={wallet.minPayoutAmount}
                  max={wallet.availableBalance}
                  value={payoutAmount}
                  onChange={(e) => setPayoutAmount(e.target.value)}
                  placeholder={`Min: $${wallet.minPayoutAmount} • Max: $${wallet.availableBalance.toFixed(2)}`}
                  className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
                />
                <p className="text-xs text-gray-500 mt-1">
                  Available balance: ${wallet.availableBalance.toFixed(2)}
                </p>
              </div>

              <div className="bg-gray-50 p-4 rounded">
                <h3 className="font-medium text-gray-900 mb-2">Payout Details</h3>
                <div className="text-sm space-y-1">
                  <div className="flex justify-between">
                    <span>Requested Amount:</span>
                    <span>${parseFloat(payoutAmount || '0').toFixed(2)}</span>
                  </div>
                  <div className="flex justify-between">
                    <span>Processing Fee:</span>
                    <span>$5.00</span>
                  </div>
                  <div className="flex justify-between font-medium">
                    <span>Net Amount:</span>
                    <span>${Math.max(0, parseFloat(payoutAmount || '0') - 5).toFixed(2)}</span>
                  </div>
                </div>
              </div>

              <div className="flex space-x-3">
                <button
                  onClick={handlePayoutRequest}
                  disabled={!payoutAmount || parseFloat(payoutAmount) < wallet.minPayoutAmount}
                  className="flex-1 bg-green-600 text-white py-2 px-4 rounded hover:bg-green-700 disabled:opacity-50"
                >
                  Request Payout
                </button>
                <button
                  onClick={() => {
                    setShowPayoutForm(false);
                    setPayoutAmount('');
                  }}
                  className="px-4 py-2 text-gray-600 hover:text-gray-800"
                >
                  Cancel
                </button>
              </div>
            </div>
          )}
        </div>

        {/* Earnings Chart */}
        {chartData.length > 0 && (
          <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-semibold text-gray-900 mb-4">Earnings Trend</h2>
            <ResponsiveContainer width="100%" height={300}>
              <LineChart data={chartData}>
                <CartesianGrid strokeDasharray="3 3" />
                <XAxis dataKey="date" />
                <YAxis />
                <Tooltip />
                <Line 
                  type="monotone" 
                  dataKey="balance" 
                  stroke="#3B82F6" 
                  strokeWidth={2}
                  dot={{ fill: '#3B82F6' }}
                />
              </LineChart>
            </ResponsiveContainer>
          </div>
        )}

        {/* Recent Payouts */}
        {payoutHistory && payoutHistory.length > 0 && (
          <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-semibold text-gray-900 mb-4">Recent Payouts</h2>
            <div className="space-y-3">
              {payoutHistory.map((payout) => (
                <div key={payout._id} className="flex items-center justify-between p-4 bg-gray-50 rounded">
                  <div>
                    <p className="font-medium text-gray-900">
                      ${payout.amount.toFixed(2)}
                    </p>
                    <p className="text-sm text-gray-600">
                      {new Date(payout.requestedAt).toLocaleDateString()}
                      {payout.transactionReference && ` • ${payout.transactionReference}`}
                    </p>
                  </div>
                  <span className={`px-3 py-1 rounded-full text-sm font-medium ${
                    payout.status === 'completed' ? 'bg-green-100 text-green-800' :
                    payout.status === 'processing' ? 'bg-blue-100 text-blue-800' :
                    payout.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
                    'bg-red-100 text-red-800'
                  }`}>
                    {payout.status.charAt(0).toUpperCase() + payout.status.slice(1)}
                  </span>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Transaction History */}
        {transactions && transactions.length > 0 && (
          <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-semibold text-gray-900 mb-4">Transaction History</h2>
            <div className="space-y-2">
              {transactions.map((transaction) => (
                <div key={transaction._id} className="flex items-center justify-between p-3 border-b last:border-b-0">
                  <div>
                    <p className="font-medium text-gray-900">{transaction.description}</p>
                    <p className="text-sm text-gray-600">
                      {new Date(transaction._creationTime).toLocaleString()}
                    </p>
                  </div>
                  <div className="text-right">
                    <p className={`font-medium ${
                      transaction.amount > 0 ? 'text-green-600' : 'text-red-600'
                    }`}>
                      {transaction.amount > 0 ? '+' : ''}${transaction.amount.toFixed(2)}
                    </p>
                    <p className="text-sm text-gray-500">
                      Balance: ${transaction.balanceAfter.toFixed(2)}
                    </p>
                  </div>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
    );
  }

  export default WalletDashboard;
  ```
</CodeGroup>

## Payout Processing Timeline

<Steps>
  <Step title="Request Submitted">
    Store owner submits payout request through dashboard
  </Step>

  <Step title="Admin Review">
    Platform admin reviews request and bank account details
  </Step>

  <Step title="Processing">
    Payment is initiated to store's registered bank account
  </Step>

  <Step title="Completed">
    Funds transferred successfully, confirmation sent to store
  </Step>
</Steps>

## Transaction Types

<CardGroup cols={2}>
  <Card title="Order Funding" icon="plus-circle" color="#10b981">
    **Type**: `order_funding`\
    **Description**: Earnings from completed orders\
    **Effect**: Increases wallet balance
  </Card>

  <Card title="Commission Deduction" icon="minus-circle" color="#ef4444">
    **Type**: `commission_deduction`\
    **Description**: Platform commission on orders\
    **Effect**: Decreases wallet balance
  </Card>

  <Card title="Payout" icon="bank">
    **Type**: `payout`\
    **Description**: Funds transferred to bank account\
    **Effect**: Decreases wallet balance
  </Card>

  <Card title="Payout Fee" icon="receipt">
    **Type**: `payout_fee`\
    **Description**: Processing fee for payouts\
    **Effect**: Decreases wallet balance
  </Card>

  <Card title="Refund" icon="undo">
    **Type**: `refund`\
    **Description**: Order refunds processed\
    **Effect**: Decreases wallet balance
  </Card>

  <Card title="Adjustment" icon="edit">
    **Type**: `adjustment`\
    **Description**: Manual balance adjustments\
    **Effect**: Can increase or decrease balance
  </Card>
</CardGroup>

## Error Handling

<AccordionGroup>
  <Accordion title="Insufficient balance">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Insufficient available balance",
      "requested": 1000.00,
      "available": 750.50
    }
    ```
  </Accordion>

  <Accordion title="Below minimum payout">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Amount below minimum payout threshold",
      "requested": 25.00,
      "minimum": 50.00
    }
    ```
  </Accordion>

  <Accordion title="Missing bank details">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Bank account details required for payout",
      "message": "Please add your bank account information in store settings"
    }
    ```
  </Accordion>

  <Accordion title="Wallet not found">
    **Status Code**: `404`

    ```json theme={null}
    {
      "error": "Store wallet not found",
      "storeId": "j123456789"
    }
    ```
  </Accordion>
</AccordionGroup>

<Note>
  Wallet balances are updated in real-time as orders are processed. Pending balances are held for a security period before becoming available for payout.
</Note>

<Tip>
  Set up bank account details before requesting payouts. Processing typically takes 1-3 business days depending on the bank and amount.
</Tip>
