Last active
March 15, 2024 10:01
-
-
Save azivkovi/7ca41aee6a21db1616b917d57fa2757c to your computer and use it in GitHub Desktop.
webhooks.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Stripe from 'stripe'; | |
import dayjs from 'dayjs'; | |
import prisma from '@/lib/prisma.lib'; | |
import { evaluateRegistration } from '../evaluateRegistration'; | |
import { saveInvoice } from '@/helpers/save-invoice'; | |
import { sendInvoice } from '@/helpers/send-invoice'; | |
import { createFiscalizedInvoice } from '@/helpers/create-fiscalized-invoice'; | |
import { mailTransport } from '@/lib/nodemailer.lib'; | |
export const config = { | |
api: { | |
bodyParser: false, | |
}, | |
}; | |
async function buffer(readable) { | |
const chunks = []; | |
for await (const chunk of readable) { | |
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk); | |
} | |
return Buffer.concat(chunks); | |
} | |
// @desc Interact with Stripe subscription webhooks | |
// @route POST /api/v1/stripe/webhooks | |
// @access Private | |
const handler = async (req, res) => { | |
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); | |
if (req.method === 'POST') { | |
const buf = await buffer(req); | |
const sig = req.headers['stripe-signature']; | |
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; | |
try { | |
if (!sig || !webhookSecret) return; | |
const event = stripe.webhooks.constructEvent(buf, sig, webhookSecret); | |
const { type: eventType } = event; | |
if (eventType === 'payment_intent.created') { | |
} | |
if (eventType === 'payment_intent.succeeded') { | |
} | |
if (eventType === 'checkout.session.completed') { | |
const { | |
data: { | |
object: { | |
id: checkoutId, | |
subscription: subscriptionId, | |
metadata: { installments, connect }, | |
amount_total: amountTotal, | |
payment_intent: paymentIntentId, | |
}, | |
}, | |
} = event; | |
const registration = await prisma.registrations.findFirst({ | |
where: { | |
checkoutId, | |
}, | |
}); | |
const { | |
id, | |
email, | |
firstName, | |
lastName, | |
education, | |
company, | |
slug, | |
educationType, | |
level, | |
validFor, | |
} = registration; | |
const price = new Intl.NumberFormat('hr', { | |
minimumFractionDigits: 2, | |
maximumFractionDigits: 2, | |
}).format(amountTotal / 100); | |
// Napravi fiskalizirani racun na Solo | |
const invoice = await createFiscalizedInvoice({ | |
education, | |
company, | |
price, | |
customer: `${firstName} ${lastName}`, | |
}); | |
if (invoice.status !== 0) { | |
console.log('Invoice error', invoice); | |
//TODO: Prikazi gresku i zatrazi refund od Stripe? | |
} | |
// Sacuvaj racun u bazu | |
await saveInvoice({ | |
invoiceNumber: invoice?.racun?.broj_racuna, | |
email, | |
customer: `${firstName} ${lastName}`, | |
education, | |
fileUrl: invoice?.racun?.pdf, | |
}); | |
// Ako nije jednokratna uplata napravi subscription schedule | |
if (subscriptionId) { | |
let schedule = await stripe.subscriptionSchedules.create({ | |
from_subscription: subscriptionId, | |
}); | |
// Ako ima odredjeni broj uplata (pretplata ima 0), dodaj faze sa zeljenim brojem iteracija | |
if (parseInt(installments) > 0) { | |
const phases = schedule.phases.map((phase) => ({ | |
start_date: phase.start_date, | |
end_date: phase.end_date, | |
items: phase.items, | |
})); | |
schedule = await stripe.subscriptionSchedules.update(schedule.id, { | |
end_behavior: 'cancel', | |
phases: [ | |
...phases, | |
{ | |
items: phases[0].items, | |
iterations: parseInt(installments), | |
}, | |
], | |
}); | |
} | |
await prisma.registrations.update({ | |
where: { | |
checkoutId, | |
}, | |
data: { | |
subscriptionId, | |
}, | |
}); | |
} | |
let relevantValidFromDate = dayjs(); | |
if (educationType !== 'subscription') { | |
// Dobavi podatke o edukaciji | |
const educationRes = await fetch( | |
`${process.env.WP_LOCAL_URL}/wp-json/wp/v2/posts?slug=${slug}` | |
); | |
const educationData = await educationRes.json(); | |
relevantValidFromDate = | |
educationData[0].acf['1_datum'] && | |
educationData[0].acf['1_datum'] !== '' && | |
dayjs().isBefore(educationData[0].acf['1_datum']) | |
? dayjs(educationData[0].acf['1_datum']) | |
: dayjs(); | |
} | |
// Odobri prijavu s checkoutId | |
await evaluateRegistration({ | |
id, | |
slug, | |
email, | |
firstName, | |
lastName, | |
education, | |
educationType, | |
status: 'approved', | |
reviewedBy: 'App', | |
level, | |
validFor, | |
validFrom: relevantValidFromDate.toDate(), | |
invoiceUrl: invoice?.racun?.pdf || '--', | |
}); | |
// Posalji admin mail | |
const adminMailParams = { | |
from: `Izvrsnost.hr - Prijava <${process.env.EMAIL_ADDRESS}>`, | |
to: process.env.EMAIL_ADDRESS, | |
replyTo: email, | |
subject: `Prijava za - ${education} ${level ? `- ${level}` : ''}`, | |
html: ` | |
<table> | |
<thead> | |
<tr> | |
<th></th> | |
<th></th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td><strong>Ime</strong></td> | |
<td>${firstName}</td> | |
</tr> | |
<tr> | |
<td><strong>Prezime</strong></td> | |
<td>${lastName}</td> | |
</tr> | |
<tr> | |
<td><strong>Email</strong></td> | |
<td>${email}</td> | |
</tr> | |
<tr> | |
<td><strong>Način plaćanja</strong></td> | |
<td>Stripe</td> | |
</tr> | |
</tbody> | |
</table> | |
`, | |
}; | |
if (connect === 'true' && parseInt(installments) === 0) { | |
const stripeConnect = await prisma.stripe_connect.findFirst({ | |
where: { | |
slug, | |
}, | |
}); | |
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId); | |
await stripe.transfers.create({ | |
amount: Math.floor((paymentIntent.amount * stripeConnect.percent_share) / 100), | |
currency: paymentIntent.currency, | |
destination: stripeConnect.accountId, | |
transfer_group: paymentIntentId, | |
}); | |
} | |
// await mailTransport.sendMail(adminMailParams); | |
} | |
if (eventType === 'invoice.payment_succeeded') { | |
const { | |
data: { | |
object: { billing_reason, subscription: subscriptionId, amount_due: amountDue }, | |
}, | |
} = event; | |
if (billing_reason === 'subscription_create') { | |
// Pokriveno u checkout.session.completed | |
} | |
if (billing_reason === 'subscription_cycle') { | |
const registration = await prisma.registrations.findFirst({ | |
where: { | |
subscriptionId, | |
}, | |
}); | |
const { email, firstName, lastName, education, company } = registration; | |
const price = new Intl.NumberFormat('hr', { | |
minimumFractionDigits: 2, | |
maximumFractionDigits: 2, | |
}).format(amountDue / 100); | |
// Napravi fiskalizirani racun na Solo | |
const invoice = await createFiscalizedInvoice({ | |
education, | |
company, | |
price, | |
customer: `${firstName} ${lastName}`, | |
}); | |
if (invoice.status !== 0) { | |
console.log('Invoice error', invoice); | |
//TODO: Prikazi gresku i zatrazi refund od Stripe? | |
} | |
// Posalji racun na mail | |
await sendInvoice({ | |
email, | |
firstName, | |
lastName, | |
education, | |
invoiceUrl: invoice?.racun?.pdf || '--', | |
}); | |
// Sacuvaj racun u bazu | |
await saveInvoice({ | |
invoiceNumber: invoice?.racun?.broj_racuna, | |
email, | |
customer: `${firstName} ${lastName}`, | |
education, | |
fileUrl: invoice?.racun?.pdf, | |
}); | |
} | |
} | |
if (eventType === 'payment_intent.payment_failed') { | |
// The payment failed or the customer does not have a valid payment method. | |
// The subscription becomes past_due. Notify your customer and send them to the | |
// customer portal to update their payment information. | |
//TODO: Posalji mail korisniku da naplata nije uspijela. Ukljuci i customer portal link. Napisi kada ce biti sljedeci pokusaj naplate | |
} | |
if (eventType === 'checkout.session.expired') { | |
const { | |
data: { | |
object: { id: checkoutId }, | |
}, | |
} = event; | |
await prisma.registrations.update({ | |
where: { | |
checkoutId, | |
}, | |
data: { | |
status: 'expired', | |
}, | |
}); | |
} | |
if (eventType === 'customer.subscription.deleted') { | |
const { | |
data: { | |
object: { id: subscriptionId, current_period_end }, | |
}, | |
} = event; | |
await prisma.registrations.update({ | |
where: { | |
subscriptionId, | |
}, | |
data: { | |
expiresAt: dayjs(new Date(current_period_end * 1000)).toDate(), | |
}, | |
}); | |
} | |
} catch (error) { | |
console.log(`Webhook error: ${error.message}`); | |
return res.status(400).send(`Webhook error: ${error.message}`); | |
} | |
} | |
res.status(200).send(); | |
}; | |
export default handler; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment