Skip to content
55 changes: 51 additions & 4 deletions src/app/api/webhook/route.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import { generateInvoiceNumber } from "@/lib/invoice/client";
import { db } from "@/server/db";
import {
paymentDetailsPayersTable,
recurringPaymentTable,
type requestStatusEnum,
requestTable,
userTable,
@@ -35,6 +36,43 @@ async function updateRequestStatus(
});
}

/**
* Adds a payment to the recurring payment's payments array
*/
async function addPaymentToRecurringPayment(
externalPaymentId: string,
payment: {
date: string;
txHash: string;
requestScanUrl?: string;
},
) {
await db.transaction(async (tx) => {
const recurringPayments = await tx
.select()
.from(recurringPaymentTable)
.where(eq(recurringPaymentTable.externalPaymentId, externalPaymentId));

if (!recurringPayments.length) {
throw new ResourceNotFoundError(
`No recurring payment found with external payment ID: ${externalPaymentId}`,
);
}

const recurringPayment = recurringPayments[0];
const currentPayments = recurringPayment.payments || [];
const updatedPayments = [...currentPayments, payment];

await tx
.update(recurringPaymentTable)
.set({
payments: updatedPayments,
currentNumberOfPayments: updatedPayments.length,
})
.where(eq(recurringPaymentTable.externalPaymentId, externalPaymentId));
});
}

export async function POST(req: Request) {
let webhookData: Record<string, unknown> = {};

@@ -69,10 +107,19 @@ export async function POST(req: Request) {

switch (event) {
case "payment.confirmed":
await updateRequestStatus(
requestId,
isCryptoToFiat ? "crypto_paid" : "paid",
);
// if this is defined, it's a payment that's part of a recurring payment
if (body.recurringPayment?.id) {
await addPaymentToRecurringPayment(body.recurringPayment.id, {
date: body.timestamp,
txHash: body.txHash || "",
requestScanUrl: body.explorer,
});
} else {
await updateRequestStatus(
requestId,
isCryptoToFiat ? "crypto_paid" : "paid",
);
}
break;
case "payment.processing":
switch (subStatus) {
2 changes: 1 addition & 1 deletion src/app/payouts/recurring/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CreateRecurringPayment } from "@/components/create-recurring-payment";
import { CreateRecurringPayment } from "@/components/create-recurring-payment/create-recurring-payment";

export const metadata = {
title: "Recurring Payments | Easy Invoice",
30 changes: 13 additions & 17 deletions src/components/batch-payout.tsx
Original file line number Diff line number Diff line change
@@ -56,14 +56,22 @@ import {
getPaymentCurrenciesForPayout,
} from "@/lib/constants/currencies";
import { handleBatchPayment } from "@/lib/invoice/batch-payment";
import {
type BatchPaymentFormValues,
batchPaymentFormSchema,
} from "@/lib/schemas/payment";
import { payoutSchema } from "@/lib/schemas/payment";
import { api } from "@/trpc/react";
import { z } from "zod";
import { PaymentSecuredUsingRequest } from "./payment-secured-using-request";

const MAX_PAYMENTS = 10;

const batchPaymentFormSchema = z.object({
payouts: z
.array(payoutSchema)
.min(1, "At least one payment is required")
.max(10, "Maximum 10 payments allowed"),
});

export type BatchPaymentFormValues = z.infer<typeof batchPaymentFormSchema>;

export function BatchPayout() {
const { mutateAsync: batchPay } = api.payment.batchPay.useMutation();

@@ -558,19 +566,7 @@ export function BatchPayout() {
</div>
</CardContent>
</Card>

{/* Security notice */}
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
<h3 className="font-semibold text-green-800 mb-1 text-sm">
Secure Batch Transaction
</h3>
<p className="text-xs text-green-700">
This batch payment is secured using Request Network. All
transactions will be processed safely and transparently on
the blockchain.
</p>
</div>

<PaymentSecuredUsingRequest />
<CardFooter className="flex justify-between items-center pt-2 pb-0 px-0">
<button
type="button"
28 changes: 0 additions & 28 deletions src/components/create-recurring-payment.tsx

This file was deleted.

Loading