π Problem Statement
The MicroAI Paygate receipt system stores signed payment receipts in Redis (receipt:{id} keys). The only way to retrieve a receipt is GET /api/receipts/{id} β a single-receipt lookup by ID.
For a payment gateway, operators need to:
- Export all receipts for a time period for accounting
- Audit which wallets paid how much over the last month
- Resolve payment disputes by proving a receipt was issued
- Build analytics dashboards on payment volume
Currently, there is no batch receipts endpoint and no export functionality. Operators with thousands of receipts have no way to retrieve them at scale.
β
Proposed Solution
1. Update gateway/ to add bulk receipts endpoint
// gateway/routes/receipts.go
// GET /api/receipts β paginated receipt listing with filters
func (h *ReceiptHandler) ListReceipts(c *gin.Context) {
// Query parameters
limit := c.DefaultQuery("limit", "50")
cursor := c.Query("cursor") // Redis cursor for pagination
walletFilter := c.Query("wallet") // filter by wallet address
since := c.Query("since") // ISO timestamp filter
format := c.DefaultQuery("format", "json") // json | csv
limitInt, err := strconv.Atoi(limit)
if err != nil || limitInt > 200 {
limitInt = 50
}
ctx := c.Request.Context()
var receipts []models.SignedReceipt
var nextCursor string
switch h.config.ReceiptStore {
case "redis":
receipts, nextCursor, err = h.redisStore.ScanReceipts(ctx, cursor, limitInt, walletFilter, since)
case "memory":
receipts, nextCursor, err = h.memoryStore.ListReceipts(ctx, limitInt, walletFilter, since)
}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve receipts"})
return
}
// CSV export format
if format == "csv" {
c.Header("Content-Type", "text/csv")
c.Header("Content-Disposition",
fmt.Sprintf("attachment; filename=receipts-%s.csv", time.Now().Format("2006-01-02")))
w := csv.NewWriter(c.Writer)
// Write CSV headers
w.Write([]string{
"receipt_id", "payment_context_hash", "signer_address",
"signed_at", "chain_id", "amount", "response_hash"
})
for _, r := range receipts {
w.Write([]string{
r.ID,
r.PaymentContextHash,
r.SignerAddress,
strconv.FormatInt(r.SignedAt, 10),
strconv.Itoa(r.ChainID),
r.Amount,
r.ResponseHash,
})
}
w.Flush()
return
}
// JSON format (default)
c.JSON(http.StatusOK, gin.H{
"receipts": receipts,
"count": len(receipts),
"nextCursor": nextCursor,
"hasMore": nextCursor != "0" && nextCursor != "",
})
}
// GET /api/receipts/stats β aggregate payment statistics
func (h *ReceiptHandler) GetStats(c *gin.Context) {
ctx := c.Request.Context()
receipts, _, _ := h.redisStore.ScanReceipts(ctx, "0", 10000, "", "")
type WalletStat struct {
WalletAddress string `json:"walletAddress"`
RequestCount int `json:"requestCount"`
TotalAmount string `json:"totalAmount"`
}
walletStats := make(map[string]*WalletStat)
for _, r := range receipts {
if _, ok := walletStats[r.SignerAddress]; !ok {
walletStats[r.SignerAddress] = &WalletStat{
WalletAddress: r.SignerAddress,
}
}
walletStats[r.SignerAddress].RequestCount++
}
stats := make([]WalletStat, 0, len(walletStats))
for _, ws := range walletStats {
stats = append(stats, *ws)
}
// Sort by request count descending
sort.Slice(stats, func(i, j int) bool {
return stats[i].RequestCount > stats[j].RequestCount
})
c.JSON(http.StatusOK, gin.H{
"totalReceipts": len(receipts),
"uniqueWallets": len(walletStats),
"topWallets": stats[:min(10, len(stats))],
"asOf": time.Now().UTC().Format(time.RFC3339),
})
}
2. Add Redis scan helper in the receipt store
// gateway/store/redis_receipt_store.go
func (s *RedisReceiptStore) ScanReceipts(
ctx context.Context,
cursor string,
limit int,
walletFilter string,
sinceTimestamp string,
) ([]models.SignedReceipt, string, error) {
cursorInt, _ := strconv.ParseUint(cursor, 10, 64)
// Scan Redis for receipt keys
var keys []string
var nextCursor uint64
var err error
for {
var batch []string
batch, nextCursor, err = s.client.Scan(ctx, cursorInt, "receipt:*", int64(limit)).Result()
if err != nil {
return nil, "", fmt.Errorf("redis scan failed: %w", err)
}
keys = append(keys, batch...)
if nextCursor == 0 || len(keys) >= limit {
break
}
cursorInt = nextCursor
}
// Fetch receipt data for each key
var receipts []models.SignedReceipt
for _, key := range keys[:min(limit, len(keys))] {
data, err := s.client.Get(ctx, key).Result()
if err != nil {
continue
}
var receipt models.SignedReceipt
if err := json.Unmarshal([]byte(data), &receipt); err != nil {
continue
}
// Apply wallet filter
if walletFilter != "" && !strings.EqualFold(receipt.SignerAddress, walletFilter) {
continue
}
receipts = append(receipts, receipt)
}
return receipts, strconv.FormatUint(nextCursor, 10), nil
}
3. Register the new endpoints in gateway/main.go
// gateway/main.go
receipts := api.Group("/receipts")
{
receipts.GET("/:id", receiptHandler.GetReceipt) // existing
receipts.GET("", receiptHandler.ListReceipts) // NEW: bulk listing
receipts.GET("/stats", receiptHandler.GetStats) // NEW: aggregate stats
}
4. Update gateway/openapi.yaml
# gateway/openapi.yaml β add new endpoints
/api/receipts:
get:
summary: List stored receipts with optional filters and export formats
parameters:
- name: limit
in: query
schema: { type: integer, default: 50, maximum: 200 }
- name: cursor
in: query
schema: { type: string }
description: Redis cursor for pagination
- name: wallet
in: query
schema: { type: string }
description: Filter by EVM wallet address
- name: format
in: query
schema: { type: string, enum: [json, csv], default: json }
responses:
'200':
description: List of receipts
/api/receipts/stats:
get:
summary: Aggregate payment statistics
responses:
'200':
description: Payment statistics
5. Add Go tests
// gateway/routes/receipts_test.go
func TestListReceipts_DefaultJSON(t *testing.T) {
// Setup test server with memory receipt store
// POST a few test receipts
// Call GET /api/receipts
// Assert response structure
}
func TestListReceipts_CSVExport(t *testing.T) {
// Call GET /api/receipts?format=csv
// Assert Content-Type: text/csv
// Parse CSV and validate columns
}
func TestGetStats(t *testing.T) {
// Call GET /api/receipts/stats
// Assert totalReceipts, uniqueWallets fields
}
π Files to Create / Modify
| File |
Change |
gateway/routes/receipts.go |
Add ListReceipts and GetStats handlers |
gateway/store/redis_receipt_store.go |
Add ScanReceipts method with pagination |
gateway/store/memory_receipt_store.go |
Add ListReceipts method for test compatibility |
gateway/main.go |
Register new endpoints |
gateway/openapi.yaml |
Document new endpoints |
gateway/routes/receipts_test.go |
Go tests for new endpoints |
Suggested labels: enhancement, backend, go, feature, level: intermediate
I would like to work on this. Could you please assign it to me?
π Problem Statement
The MicroAI Paygate receipt system stores signed payment receipts in Redis (
receipt:{id}keys). The only way to retrieve a receipt isGET /api/receipts/{id}β a single-receipt lookup by ID.For a payment gateway, operators need to:
Currently, there is no batch receipts endpoint and no export functionality. Operators with thousands of receipts have no way to retrieve them at scale.
β Proposed Solution
1. Update
gateway/to add bulk receipts endpoint2. Add Redis scan helper in the receipt store
3. Register the new endpoints in
gateway/main.go4. Update
gateway/openapi.yaml5. Add Go tests
π Files to Create / Modify
gateway/routes/receipts.goListReceiptsandGetStatshandlersgateway/store/redis_receipt_store.goScanReceiptsmethod with paginationgateway/store/memory_receipt_store.goListReceiptsmethod for test compatibilitygateway/main.gogateway/openapi.yamlgateway/routes/receipts_test.goSuggested labels:
enhancement,backend,go,feature,level: intermediateI would like to work on this. Could you please assign it to me?