Skip to content
Merged
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ NEXT_PUBLIC_API_TERMS_CONDITIONS="https://request.network/api-terms"

FEE_PERCENTAGE_FOR_PAYMENT=""
FEE_ADDRESS_FOR_PAYMENT=""
REDIS_URL=redis://localhost:7379

# Optional
# NEXT_PUBLIC_GTM_ID=""
# NEXT_PUBLIC_CRYPTO_TO_FIAT_TRUSTED_ORIGINS=""

INVOICE_PROCESSING_TTL=""
23 changes: 17 additions & 6 deletions src/components/payment-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export function PaymentSection({ serverInvoice }: PaymentSectionProps) {
const { mutateAsync: payRequest } = api.invoice.payRequest.useMutation();
const { mutateAsync: sendPaymentIntent } =
api.invoice.sendPaymentIntent.useMutation();
const { mutateAsync: setInvoiceAsProcessing } =
api.invoice.setInvoiceAsProcessing.useMutation();

const {
data: paymentRoutesData,
refetch,
Expand Down Expand Up @@ -375,6 +378,18 @@ export function PaymentSection({ serverInvoice }: PaymentSectionProps) {
} else {
await handleDirectPayments(paymentData, signer);
}

try {
await setInvoiceAsProcessing({
id: invoice.id,
});
} catch (statusError) {
console.error("Status update failed:", statusError);
toast("Payment Successful", {
description:
"Payment confirmed but status update failed. Please refresh.",
});
}
} catch (error) {
console.error("Error : ", error);
toast("Payment Failed", {
Expand Down Expand Up @@ -468,7 +483,7 @@ export function PaymentSection({ serverInvoice }: PaymentSectionProps) {
</div>

{/* Payment Steps */}
{paymentStatus !== "paid" && (
{paymentStatus === "pending" && (
<div className="space-y-8">
{/* Step indicators */}
<div className="flex justify-center">
Expand Down Expand Up @@ -652,11 +667,7 @@ export function PaymentSection({ serverInvoice }: PaymentSectionProps) {
<Button
onClick={handlePayment}
className="w-full bg-black hover:bg-zinc-800 text-white"
disabled={
paymentProgress !== "idle" ||
!hasRoutes ||
paymentStatus === "processing"
}
disabled={paymentProgress !== "idle" || !hasRoutes}
>
{!hasRoutes ? (
"No payment routes available"
Expand Down
17 changes: 17 additions & 0 deletions src/lib/redis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Redis from "ioredis";

let redis: Redis | null = null;

export function getRedis(): Redis {
if (!redis) {
redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379", {
lazyConnect: true,
});

redis.on("error", (err) => {
console.error("Redis connection error:", err);
});
}

return redis;
}
52 changes: 51 additions & 1 deletion src/server/routers/invoice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { apiClient } from "@/lib/axios";
import { toTRPCError } from "@/lib/errors";
import { getRedis } from "@/lib/redis";
import { invoiceFormSchema } from "@/lib/schemas/invoice";
import {
type PaymentDetailsPayers,
Expand Down Expand Up @@ -280,7 +281,24 @@ export const invoiceRouter = router({
});
}

return invoice;
try {
const redis = getRedis();
const isProcessing = await redis.get(`processing:${invoice.id}`);

return {
...invoice,
status:
isProcessing && invoice.status === "pending"
? "processing"
: invoice.status,
};
} catch (error) {
console.warn(
"[invoice.getById] Redis unavailable, falling back to DB status",
error,
);
return invoice;
}
}),
payRequest: publicProcedure
.input(
Expand Down Expand Up @@ -462,4 +480,36 @@ export const invoiceRouter = router({

return response.data;
}),
setInvoiceAsProcessing: publicProcedure
.input(
z.object({
id: z.string().ulid(),
}),
)
.mutation(async ({ ctx, input }) => {
const { db } = ctx;

try {
const invoice = await db.query.requestTable.findFirst({
where: (requestTable, { eq }) => eq(requestTable.id, input.id),
});

if (!invoice) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Invoice not found",
});
}

const redis = getRedis();

await redis.setex(
`processing:${invoice.id}`,
Number(process.env.INVOICE_PROCESSING_TTL) || 60,
"true",
);
} catch (error) {
throw toTRPCError(error);
}
}),
});