+
Batch Expire Invoices
+
+
+ setIdInput(e.target.value)}
+ />
+
+
+
+ {loadError &&
{loadError}
}
+
+ {batchResult && (
+
+ {batchResult.success ? (
+ <>
+ Batch expire submitted.
+
+ Transaction hash:{" "}
+ {batchResult.hash}
+ >
+ ) : (
+ <>Batch expire failed: {batchResult.errorMsg}>
+ )}
+
+ )}
+
+ {errors.length > 0 && (
+
+
Some invoices could not be expired:
+
+ {errors.map((e) => (
+ - {e.msg}
+ ))}
+
+
+ )}
+
+ {progress && (
+
+
+ Verifying {progress.done} of {progress.total} invoices...
+
+
+
+ )}
+
+ {invoices.length > 0 && (
+ <>
+
+
+ {!walletAddress && (
+
Connect wallet to batch expire.
+ )}
+
+
+
+ >
+ )}
+
+ )
+}
diff --git a/comebackhere-frontend/src/components/EscrowRelease.tsx b/comebackhere-frontend/src/components/EscrowRelease.tsx
new file mode 100644
index 0000000..521602b
--- /dev/null
+++ b/comebackhere-frontend/src/components/EscrowRelease.tsx
@@ -0,0 +1,135 @@
+import { useState } from "react"
+import { useInvoice } from "../hooks/useInvoice"
+import { useWallet } from "../hooks/useWallet"
+import { StatusBadge } from "./StatusBadge"
+import { InvoiceStatus } from "../types"
+
+export function EscrowRelease() {
+ const { invoice, loading, error, loadInvoice, release } = useInvoice()
+ const { address, connected, connecting, connect } = useWallet()
+ const [invoiceId, setInvoiceId] = useState("")
+ const [submitting, setSubmitting] = useState(false)
+ const [result, setResult] = useState<{
+ success: boolean
+ hash?: string
+ errorMsg?: string
+ } | null>(null)
+
+ const handleLoadInvoice = async () => {
+ setResult(null)
+ await loadInvoice(Number(invoiceId))
+ }
+
+ const handleRelease = async () => {
+ if (!address) return
+ setSubmitting(true)
+ setResult(null)
+ const res = await release(address)
+ setSubmitting(false)
+ setResult({
+ success: res.success,
+ hash: res.transaction_hash,
+ errorMsg: res.error,
+ })
+ }
+
+ const canRelease = connected && invoice?.status === InvoiceStatus.Paid
+
+ return (
+
+
Escrow Release
+
+
+ setInvoiceId(e.target.value)}
+ />
+
+
+
+ {loading &&
Loading invoice...
}
+
+ {error &&
{error}
}
+
+ {result && (
+
+ {result.success ? (
+ <>
+ Escrow released successfully!
+
+ Transaction hash:{" "}
+ {result.hash}
+ >
+ ) : (
+ <>Release failed: {result.errorMsg}>
+ )}
+
+ )}
+
+ {invoice && (
+
+
+
Invoice #{invoice.id}
+
+
+
+
+
+ Merchant
+
+ {invoice.merchant}
+
+
+
+ Amount (USDC)
+ {invoice.amount_usdc}
+
+
+ Status
+
+
+
+
+
+ {!connected && (
+
+ )}
+
+ {connected && canRelease && (
+
+ )}
+
+ {connected && invoice.status !== InvoiceStatus.Paid && (
+
+ Escrow release is available on Paid invoices
+ (current status: {invoice.status}).
+
+ )}
+
+
+ )}
+
+ )
+}
diff --git a/comebackhere-frontend/src/hooks/useInvoice.ts b/comebackhere-frontend/src/hooks/useInvoice.ts
index c3ba9d9..0956823 100644
--- a/comebackhere-frontend/src/hooks/useInvoice.ts
+++ b/comebackhere-frontend/src/hooks/useInvoice.ts
@@ -1,6 +1,6 @@
import { useState, useCallback } from "react"
import type { Invoice, PaymentResult } from "../types"
-import { fetchInvoice, payInvoice, requestRefund, cancelInvoice } from "../utils/soroban"
+import { fetchInvoice, payInvoice, requestRefund, releaseEscrow } from "../utils/soroban"
const CONTRACT_ID = import.meta.env.VITE_INVOICE_CONTRACT_ID as string
@@ -11,7 +11,7 @@ interface UseInvoiceReturn {
loadInvoice: (id: number) => Promise