(invoiceId: string)
| 34 | }); |
| 35 | |
| 36 | export async function chargeInvoice(invoiceId: string) { |
| 37 | const invoice = await prisma.invoice.findUnique({ |
| 38 | where: { |
| 39 | id: invoiceId, |
| 40 | }, |
| 41 | }); |
| 42 | |
| 43 | if (invoice?.status !== "UNPAID" || Number(invoice.amount) < 1) { |
| 44 | return error("The invoice has already been paid."); |
| 45 | } |
| 46 | |
| 47 | const project = await prisma.project.findUnique({ |
| 48 | where: { |
| 49 | id: invoice.projectId, |
| 50 | }, |
| 51 | }); |
| 52 | |
| 53 | if (!project || !project.stripeCustomerId) { |
| 54 | return error("Add a default payment method."); |
| 55 | } |
| 56 | |
| 57 | let paymentMethodToUse = await getDefaultPaymentMethodId(project.stripeCustomerId); |
| 58 | |
| 59 | if (!paymentMethodToUse) { |
| 60 | // Search for available payment methods and use the first one |
| 61 | const paymentMethods = await getPaymentMethods(project.stripeCustomerId); |
| 62 | |
| 63 | if (paymentMethods && paymentMethods.data[0]?.id) { |
| 64 | paymentMethodToUse = paymentMethods.data[0]?.id; |
| 65 | } else { |
| 66 | // TODO: Replace it with a "Payment Failed" notification once we require a card to be added. |
| 67 | await sendToOwner(invoice.projectId, (email: string) => |
| 68 | sendInvoiceNotification( |
| 69 | invoice.id, |
| 70 | Number(invoice.amount), |
| 71 | invoice.description, |
| 72 | invoice.billingPeriod || "", |
| 73 | project.name, |
| 74 | project.slug, |
| 75 | email, |
| 76 | ), |
| 77 | ); |
| 78 | return error("Add a payment method."); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | try { |
| 83 | const paymentIntent = await createStripePaymentIntent({ |
| 84 | amount: usdToCents(invoice.amount), |
| 85 | invoiceId: invoiceId, |
| 86 | stripeCustomerId: project.stripeCustomerId, |
| 87 | paymentMethodId: paymentMethodToUse, |
| 88 | returnUrl: `${env.NEXT_PUBLIC_HOST}/p/${project?.slug}/billing/invoices`, |
| 89 | }); |
| 90 | |
| 91 | // Sometimes it will not work because processing may take some time. |
| 92 | // In this case we will handle it using webhooks (see app/src/pages/api/stripe/webhook.ts) |
| 93 | if (paymentIntent.status === "succeeded") { |
no test coverage detected