{"id":88318,"date":"2024-12-15T06:18:44","date_gmt":"2024-12-15T02:48:44","guid":{"rendered":"https:\/\/nabfollower.com\/blog\/need-help-setting-up-flutterwave-with-typescript-expressjs-sequelize-and-postgresql-jee\/"},"modified":"2024-12-15T06:18:44","modified_gmt":"2024-12-15T02:48:44","slug":"need-help-setting-up-flutterwave-with-typescript-expressjs-sequelize-and-postgresql-jee","status":"publish","type":"post","link":"https:\/\/nabfollower.com\/blog\/need-help-setting-up-flutterwave-with-typescript-expressjs-sequelize-and-postgresql-jee\/","title":{"rendered":"\u0628\u0631\u0627\u06cc \u062a\u0646\u0638\u06cc\u0645 Flutterwave \u0628\u0627 TypeScript\u060c Express.js\u060c Sequelize \u0648 PostgreSQL \u0628\u0647 \u06a9\u0645\u06a9 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u06cc\u062f"},"content":{"rendered":"<p>Summarize this content to 400 words in Persian Lang<br \/>\n              \u0633\u0644\u0627\u0645 \u0627\u0646\u062c\u0645\u0646 DEV\u060c<\/p>\n<p>\u0645\u0646 \u062f\u0631 \u062d\u0627\u0644 \u062d\u0627\u0636\u0631 \u0631\u0648\u06cc \u0627\u062f\u063a\u0627\u0645 Flutterwave \u062f\u0631 \u0628\u0631\u0646\u0627\u0645\u0647 \u062e\u0648\u062f \u06a9\u0627\u0631 \u0645\u06cc \u06a9\u0646\u0645 \u06a9\u0647 \u0628\u0627 TypeScript\u060c Express.js\u060c Sequelize \u0648 PostgreSQL \u0633\u0627\u062e\u062a\u0647 \u0634\u062f\u0647 \u0627\u0633\u062a. \u0628\u0627 \u0627\u06cc\u0646 \u062d\u0627\u0644\u060c \u0645\u0646 \u0628\u0627 \u0686\u0646\u062f \u0686\u0627\u0644\u0634 \u0631\u0648\u0628\u0631\u0648 \u0628\u0648\u062f\u0647 \u0627\u0645 \u0648 \u0627\u0632 \u0647\u0631 \u06af\u0648\u0646\u0647 \u0631\u0627\u0647\u0646\u0645\u0627\u06cc\u06cc \u06cc\u0627 \u0645\u0646\u0628\u0639\u06cc \u06a9\u0647 \u0645\u06cc \u062a\u0648\u0627\u0646\u06cc\u062f \u0628\u0647 \u0627\u0634\u062a\u0631\u0627\u06a9 \u0628\u06af\u0630\u0627\u0631\u06cc\u062f \u0642\u062f\u0631\u062f\u0627\u0646\u06cc \u0645\u06cc \u06a9\u0646\u0645.<\/p>\n<p>\u062f\u0631 \u0627\u06cc\u0646\u062c\u0627 \u062e\u0644\u0627\u0635\u0647 \u0627\u06cc \u0627\u0632 \u06a9\u0627\u0631\u0647\u0627\u06cc\u06cc \u0627\u0633\u062a \u06a9\u0647 \u0645\u0646 \u062a\u0627\u06a9\u0646\u0648\u0646 \u0627\u0646\u062c\u0627\u0645 \u062f\u0627\u062f\u0647 \u0627\u0645:<\/p>\n<p>\u0628\u0633\u062a\u0647 flutterwave-node-v3 \u0631\u0627 \u0646\u0635\u0628 \u06a9\u0631\u062f\u0645 \u0648 \u0622\u0646 \u0631\u0627 \u0628\u0627 \u06a9\u0644\u06cc\u062f\u0647\u0627\u06cc \u0639\u0645\u0648\u0645\u06cc \u0648 \u0645\u062e\u0641\u06cc \u062e\u0648\u062f \u067e\u06cc\u06a9\u0631\u0628\u0646\u062f\u06cc \u06a9\u0631\u062f\u0645.<br \/>\n\u062a\u0648\u0627\u0628\u0639 TypeScript \u0631\u0627 \u0628\u0631\u0627\u06cc \u0645\u0642\u062f\u0627\u0631\u062f\u0647\u06cc \u0627\u0648\u0644\u06cc\u0647 \u0648 \u062a\u0623\u06cc\u06cc\u062f \u067e\u0631\u062f\u0627\u062e\u062a \u0627\u06cc\u062c\u0627\u062f \u06a9\u0631\u062f.<br \/>\n\u0627\u0632 Sequelize \u0628\u0631\u0627\u06cc \u0645\u062f\u0644 \u0633\u0627\u0632\u06cc \u062c\u062f\u0627\u0648\u0644 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u0645\u0646 (\u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u0645\u062b\u0627\u0644\u060c \u0628\u0644\u06cc\u0637 \u0647\u0627 \u0648 \u0631\u0648\u06cc\u062f\u0627\u062f\u0647\u0627) \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u06a9\u0631\u062f.<\/p>\n<p>\u0645\u0634\u06a9\u0644\u0627\u062a \u067e\u06cc\u0634 \u0622\u0645\u062f\u0647:\u0627\u0644\u0641 \u0647\u0646\u06af\u0627\u0645 \u062a\u0645\u0627\u0633 \u0628\u0627 flw.initializePayment\u060c \u0627\u06cc\u0646 \u062e\u0637\u0627 \u0631\u0627 \u062f\u0631\u06cc\u0627\u0641\u062a \u0645\u06cc \u06a9\u0646\u0645:TypeError: initializePayment \u06cc\u06a9 \u062a\u0627\u0628\u0639 \u0646\u06cc\u0633\u062a.\u0628 \u0627\u0641\u0632\u0648\u062f\u0646 \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc TypeScript \u062f\u0634\u0648\u0627\u0631 \u0627\u0633\u062a \u0632\u06cc\u0631\u0627 \u0647\u06cc\u0686 \u0646\u0648\u0639 \u0631\u0633\u0645\u06cc TypeScript \u0628\u0631\u0627\u06cc flutterwave-node-v3 \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f \u0648 \u0646\u0648\u0634\u062a\u0646 \u0627\u0639\u0644\u0627\u0646\u200c\u0647\u0627\u06cc \u0646\u0648\u0639 \u0633\u0641\u0627\u0631\u0634\u06cc \u0645\u0634\u06a9\u0644 \u0631\u0627 \u062d\u0644 \u0646\u06a9\u0631\u062f\u0647 \u0627\u0633\u062a.\u062c \u0628\u0631\u0627\u06cc \u062f\u0631\u06a9 \u0628\u0647\u062a\u0631\u06cc\u0646 \u0631\u0648\u0634\u200c\u0647\u0627 \u0628\u0631\u0627\u06cc \u0631\u0633\u06cc\u062f\u06af\u06cc \u0628\u0647 \u062a\u0645\u0627\u0633\u200c\u0647\u0627\u06cc \u0646\u0627\u0647\u0645\u0632\u0645\u0627\u0646 \u0648 \u0628\u0647\u200c\u0631\u0648\u0632\u0631\u0633\u0627\u0646\u06cc \u0648\u0636\u0639\u06cc\u062a \u0631\u0648\u06cc\u062f\u0627\u062f \u0648 \u0628\u0644\u06cc\u0637 \u062f\u0631 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u0628\u0647 \u06a9\u0645\u06a9 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u06cc\u062f.<\/p>\n<p>\u0622\u0646\u0686\u0647 \u0645\u0646 \u0627\u0645\u062a\u062d\u0627\u0646 \u06a9\u0631\u062f\u0647 \u0627\u0645:<\/p>\n<p>\/\/ src\/types\/flutterwave-node-v3.d.ts<br \/>\ndeclare module &#8216;flutterwave-node-v3&#8217; {<br \/>\n    \/\/ TypeScript interfaces for the Flutterwave response and request types<br \/>\n    export interface PaymentInitiateResponse {<br \/>\n      status: string;<br \/>\n      message: string;<br \/>\n      data: {<br \/>\n        link: string;<br \/>\n      };<br \/>\n    }<\/p>\n<p>    export interface TransactionVerifyResponse {<br \/>\n      status: string;<br \/>\n      message: string;<br \/>\n      data: {<br \/>\n        tx_ref: string;<br \/>\n        flw_ref: string;<br \/>\n        currency: string;<br \/>\n        status: string;<br \/>\n      };<br \/>\n    }<\/p>\n<p>    export interface Flutterwave {<br \/>\n      initializePayment(<br \/>\n        payload: {<br \/>\n          tx_ref: string;<br \/>\n          amount: number;<br \/>\n          currency: string;<br \/>\n          redirect_url: string;<br \/>\n          customer: {<br \/>\n            email: string;<br \/>\n          };<br \/>\n        }<br \/>\n      ): Promise;<\/p>\n<p>      TransactionVerify(<br \/>\n        payload: { id: string }<br \/>\n      ): Promise;<br \/>\n    }<\/p>\n<p>    const Flutterwave: new (publicKey: string, secretKey: string) =&gt; Flutterwave;<br \/>\n    export = Flutterwave;<br \/>\n  }<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>import Flutterwave from &#8220;flutterwave-node-v3&#8221;;<br \/>\nimport { FLWPUBK, FLWSECK } from &#8220;..\/config&#8221;;<\/p>\n<p>const flw = new Flutterwave(FLWPUBK, FLWSECK);<\/p>\n<p>export const initiatePayment = async (payload: {<br \/>\n  tx_ref: string;<br \/>\n  amount: number;<br \/>\n  currency: string;<br \/>\n  email: string;<br \/>\n  redirect_url: string;<br \/>\n}) =&gt; {<br \/>\n  try {<br \/>\n    const response = await flw.Charge.card({<br \/>\n      tx_ref: payload.tx_ref,<br \/>\n      amount: payload.amount,<br \/>\n      currency: payload.currency,<br \/>\n      redirect_url: payload.redirect_url,<br \/>\n      customer: {<br \/>\n        email: payload.email,<br \/>\n      },<br \/>\n      payment_options: &#8220;card&#8221;, \/\/ Optional: specify payment options<br \/>\n    });<\/p>\n<p>    if (response.status === &#8220;success&#8221;) {<br \/>\n      return response.meta.authorization.redirect; \/\/ Payment link<br \/>\n    } else {<br \/>\n      throw new Error(response.message || &#8220;Failed to initiate payment.&#8221;);<br \/>\n    }<br \/>\n  } catch (error) {<br \/>\n    console.error(&#8220;Payment initiation error:&#8221;, error);<br \/>\n    throw new Error(&#8220;Failed to initiate payment.&#8221;);<br \/>\n  }<br \/>\n};<br \/>\nexport const verifyPayment = async (transactionId: string) =&gt; {<br \/>\n  try {<br \/>\n    const response = await flw.Transaction.verify({ id: transactionId });<\/p>\n<p>    if (response.status === &#8220;success&#8221;) {<br \/>\n      return response.data;<br \/>\n    } else {<br \/>\n      throw new Error(&#8220;Payment verification failed.&#8221;);<br \/>\n    }<br \/>\n  } catch (error) {<br \/>\n    console.error(&#8220;Payment verification error:&#8221;, error);<br \/>\n    throw new Error(&#8220;Failed to verify payment.&#8221;);<br \/>\n  }<br \/>\n};<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>`import { Request, Response } from &#8220;express&#8221;;\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { EventAttribute, EventInstance } \u0627\u0632 &#8220;..\/models\/eventModel&#8221;;\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { TicketAttribute, TicketInstance } \u0627\u0632 &#8220;..\/models\/ticketModel&#8221;;\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { v4 \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 uuidv4 } \u0627\u0632 &#8220;uuid&#8221;;\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { JwtPayload } \u0627\u0632 &#8220;jsonwebtoken&#8221;;\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 QRCode \u0627\u0632 &#8220;qrcode&#8221;\u061b\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { NotificationInstance } \u0627\u0632 &#8220;..\/models\/notificationModel&#8221;\u061b\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { initiatePayment, verifyPayment } \u0627\u0632 &#8220;..\/interface\/payment.dto&#8221;;\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { BASE_URL, FRONTEND_URL } \u0627\u0632 &#8220;..\/config&#8221;\u061b\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 {UserAttribute, UserInstance } \u0627\u0632 &#8220;..\/models\/userModel&#8221;;<\/p>\n<p>\u0635\u0627\u062f\u0631\u0627\u062a \u0647\u0632\u06cc\u0646\u0647 \u062e\u0631\u06cc\u062f \u0628\u0644\u06cc\u0637 = \u0646\u0627\u0647\u0645\u06af\u0627\u0645 (\u062f\u0631\u062e\u0648\u0627\u0633\u062a: JwtPayload\u060c\u067e\u0627\u0633\u062e: \u067e\u0627\u0633\u062e): \u0642\u0648\u0644 => {const userId = req.user;const { eventId } = req.params;const { ticketType,currency } = req.body;<\/p>\n<p>\u0633\u0639\u06cc \u06a9\u0646 {const user = (\u0627\u0646\u062a\u0638\u0627\u0631 UserInstance.findOne({\u06a9\u062c\u0627: { id: userId }\u060c})) \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 UserAttribute \u0646\u0627\u0634\u0646\u0627\u062e\u062a\u0647.\u0627\u06af\u0631 (! \u06a9\u0627\u0631\u0628\u0631) {return res.status(404).json({ \u062e\u0637\u0627: &#8220;\u06a9\u0627\u0631\u0628\u0631 \u067e\u06cc\u062f\u0627 \u0646\u0634\u062f&#8221; });}<\/p>\n<p>const event = (await EventInstance.findOne({<br \/>\n  where: { id: eventId },<br \/>\n})) as unknown as EventAttribute;<br \/>\nif (!event) {<br \/>\n  return res.status(404).json({ error: &#8220;Event not found&#8221; });<br \/>\n}<\/p>\n<p>if (new Date() &gt; new Date(event.date)) {<br \/>\n  return res<br \/>\n    .status(400)<br \/>\n    .json({ error: &#8220;Cannot purchase tickets for expired events&#8221; });<br \/>\n}<\/p>\n<p>const ticketPrice = event.ticketType[ticketType];<br \/>\nif (!ticketPrice) {<br \/>\n  return res.status(400).json({ error: &#8220;Invalid ticket type&#8221; });<br \/>\n}<\/p>\n<p>const ticketId = uuidv4();<\/p>\n<p>const qrCodeData = {<br \/>\n  ticketId,<br \/>\n  userId,<br \/>\n  eventId: event.id,<br \/>\n  ticketType,<br \/>\n  price: ticketPrice,<br \/>\n  purchaseDate: new Date(),<br \/>\n};<br \/>\nconst qrCode = await QRCode.toDataURL(JSON.stringify(qrCodeData));<\/p>\n<p>const newTicket = await TicketInstance.create({<br \/>\n  id: ticketId,<br \/>\n  eventId: event.id,<br \/>\n  userId,<br \/>\n  ticketType,<br \/>\n  price: ticketPrice,<br \/>\n  purchaseDate: new Date(),<br \/>\n  qrCode,<br \/>\n  paid: false,<br \/>\n  currency,<br \/>\n  validationStatus: &#8220;Invalid&#8221;,<br \/>\n}) as unknown as TicketAttribute;<\/p>\n<p>const notification = await NotificationInstance.create({<br \/>\n  id: uuidv4(),<br \/>\n  title: &#8220;Ticket Purchase Successful&#8221;,<br \/>\n  message: `You have successfully purchased a ${ticketType} ticket for the event ${event.title}.`,<br \/>\n  userId,<br \/>\n  isRead: false,<br \/>\n});<\/p>\n<p>const paymentLink = await initiatePayment({<br \/>\n  tx_ref: newTicket.id,<br \/>\n  amount: newTicket.price,<br \/>\n  currency: newTicket.currency,<br \/>\n  email: user.email,<br \/>\n  redirect_url: `${BASE_URL}\/tickets\/callback`,<br \/>\n});<\/p>\n<p>return res.status(201).json({<br \/>\n  message: &#8220;Ticket created successfully&#8221;,<br \/>\n  paymentLink,<br \/>\n  ticket: newTicket,<br \/>\n});<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>} catch (\u062e\u0637\u0627: \u0647\u0631) {Result Res\u0648\u0636\u0639\u06cc\u062a .(500).json({ \u062e\u0637\u0627: &#8220;\u062e\u0631\u06cc\u062f \u0628\u0644\u06cc\u0637 \u0627\u0646\u062c\u0627\u0645 \u0646\u0634\u062f&#8221;\u060c \u062c\u0632\u0626\u06cc\u0627\u062a: error.message });}};<\/p>\n<p>\u0635\u0627\u062f\u0631\u0627\u062a \u0647\u0632\u06cc\u0646\u0647 \u067e\u0631\u062f\u0627\u062e\u062a \u062a\u0623\u06cc\u06cc\u062f = \u0646\u0627\u0647\u0645\u06af\u0627\u0645 (\u062f\u0631\u062e\u0648\u0627\u0633\u062a: JwtPayload\u060c\u067e\u0627\u0633\u062e: \u067e\u0627\u0633\u062e): \u0642\u0648\u0644 => {const {transaction_id, tx_ref } = req.query;<\/p>\n<p>\u0633\u0639\u06cc \u06a9\u0646 {\/\/ \u0628\u0631\u0631\u0633\u06cc \u067e\u0631\u062f\u0627\u062e\u062aconst paymentData = await verifyPayment(transaction_id as string);<\/p>\n<p>if (paymentData.tx_ref === tx_ref) {<br \/>\n  await TicketInstance.update(<br \/>\n    {<br \/>\n      paid: true,<br \/>\n      validationStatus: &#8220;Valid&#8221;,<br \/>\n      flwRef: paymentData.flw_ref,<br \/>\n      currency: paymentData.currency,<br \/>\n    },<br \/>\n    {<br \/>\n      where: { id: tx_ref },<br \/>\n    }<br \/>\n  );<\/p>\n<p>  const ticket = (await TicketInstance.findOne({<br \/>\n    where: { id: tx_ref },<br \/>\n  })) as unknown as TicketAttribute;<br \/>\n  const event = (await EventInstance.findOne({<br \/>\n    where: { id: ticket?.eventId },<br \/>\n  })) as unknown as EventAttribute;<\/p>\n<p>  if (event) {<br \/>\n    if (event.quantity &lt;= 0) {<br \/>\n      throw new Error(&#8220;Event is sold out&#8221;);<br \/>\n    }<\/p>\n<p>    await EventInstance.update(<br \/>\n      {<br \/>\n        quantity: event.quantity &#8211; 1,<br \/>\n        sold: event.sold + 1,<br \/>\n      },<br \/>\n      { where: { id: ticket?.eventId } }<br \/>\n    );<br \/>\n  }<\/p>\n<p>  res.redirect(`${FRONTEND_URL}\/payment-success`);<br \/>\n} else {<br \/>\n  throw new Error(&#8220;Transaction reference mismatch&#8221;);<br \/>\n}<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>} catch (\u062e\u0637\u0627) {console.error(error);res.redirect(${FRONTEND_URL}\/payment-failed)}};`<\/p>\n<p>\u062c\u0632\u0626\u06cc\u0627\u062a \u067e\u0634\u062a\u0647 \u0641\u0646\u0627\u0648\u0631\u06cc:<\/p>\n<p>\u0686\u0627\u0631\u0686\u0648\u0628 Backend: Express.js<br \/>\nORM: \u062f\u0646\u0628\u0627\u0644\u0647 \u062f\u0627\u0631 \u06a9\u0631\u062f\u0646<br \/>\n\u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647: PostgreSQL<br \/>\n\u0632\u0628\u0627\u0646: TypeScript<\/p>\n<p>\u0633\u0648\u0627\u0644\u0627\u062a:<\/p>\n<p>\u0622\u06cc\u0627 \u06a9\u0633\u06cc \u0628\u0627 \u0645\u0648\u0641\u0642\u06cc\u062a Flutterwave \u0631\u0627 \u062f\u0631 \u067e\u0631\u0648\u0698\u0647 TypeScript + Express.js \u0627\u062f\u063a\u0627\u0645 \u06a9\u0631\u062f\u0647 \u0627\u0633\u062a\u061f<br \/>\n\u0622\u06cc\u0627 \u0627\u0639\u0644\u0627\u0646\u200c\u0647\u0627\u06cc \u0646\u0648\u0639 \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc \u0634\u062f\u0647 \u062a\u0648\u0633\u0637 \u062c\u0627\u0645\u0639\u0647 \u06cc\u0627 \u0631\u0648\u06cc\u06a9\u0631\u062f\u0647\u0627\u06cc \u0628\u0647\u062a\u0631\u06cc \u0628\u0631\u0627\u06cc \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u0627\u06cc\u0646 \u0628\u0633\u062a\u0647 \u0648\u062c\u0648\u062f \u062f\u0627\u0631\u062f\u061f<br \/>\n\u0628\u0647\u062a\u0631\u06cc\u0646 \u0631\u0627\u0647 \u0628\u0631\u0627\u06cc \u0631\u0633\u06cc\u062f\u06af\u06cc \u0628\u0647 callbacktransaction_id \u0628\u0631\u0627\u06cc \u062a\u0623\u06cc\u06cc\u062f \u067e\u0631\u062f\u0627\u062e\u062a\u200c\u0647\u0627 \u0648 \u0628\u0647\u200c\u0631\u0648\u0632\u0631\u0633\u0627\u0646\u06cc \u0633\u0648\u0627\u0628\u0642 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u0686\u06cc\u0633\u062a\u061f<\/p>\n<p>\u0627\u06af\u0631 \u0628\u0627 \u0645\u0634\u06a9\u0644\u0627\u062a \u0645\u0634\u0627\u0628\u0647\u06cc \u0645\u0648\u0627\u062c\u0647 \u0634\u062f\u0647 \u0627\u06cc\u062f \u06cc\u0627 \u067e\u06cc\u0634\u0646\u0647\u0627\u062f\u0647\u0627\u06cc\u06cc \u062f\u0627\u0631\u06cc\u062f (\u0645\u0642\u0627\u0644\u0627\u062a\u060c \u0622\u0645\u0648\u0632\u0634 \u0647\u0627\u06cc \u06cc\u0648\u062a\u06cc\u0648\u0628 \u06cc\u0627 \u0645\u062e\u0627\u0632\u0646 GitHub)\u060c \u0627\u0632 \u06a9\u0645\u06a9 \u0634\u0645\u0627 \u0628\u0633\u06cc\u0627\u0631 \u0633\u067e\u0627\u0633\u06af\u0632\u0627\u0631\u0645.<\/p>\n<p>\u067e\u06cc\u0634\u0627\u067e\u06cc\u0634 \u0645\u062a\u0634\u06a9\u0631\u0645 \ud83d\ude80<\/p>\n<div data-article-id=\"2157165\" id=\"article-body\">\n<p><strong>\u0633\u0644\u0627\u0645 \u0627\u0646\u062c\u0645\u0646 DEV\u060c<\/strong><\/p>\n<p>\u0645\u0646 \u062f\u0631 \u062d\u0627\u0644 \u062d\u0627\u0636\u0631 \u0631\u0648\u06cc \u0627\u062f\u063a\u0627\u0645 Flutterwave \u062f\u0631 \u0628\u0631\u0646\u0627\u0645\u0647 \u062e\u0648\u062f \u06a9\u0627\u0631 \u0645\u06cc \u06a9\u0646\u0645 \u06a9\u0647 \u0628\u0627 TypeScript\u060c Express.js\u060c Sequelize \u0648 PostgreSQL \u0633\u0627\u062e\u062a\u0647 \u0634\u062f\u0647 \u0627\u0633\u062a. \u0628\u0627 \u0627\u06cc\u0646 \u062d\u0627\u0644\u060c \u0645\u0646 \u0628\u0627 \u0686\u0646\u062f \u0686\u0627\u0644\u0634 \u0631\u0648\u0628\u0631\u0648 \u0628\u0648\u062f\u0647 \u0627\u0645 \u0648 \u0627\u0632 \u0647\u0631 \u06af\u0648\u0646\u0647 \u0631\u0627\u0647\u0646\u0645\u0627\u06cc\u06cc \u06cc\u0627 \u0645\u0646\u0628\u0639\u06cc \u06a9\u0647 \u0645\u06cc \u062a\u0648\u0627\u0646\u06cc\u062f \u0628\u0647 \u0627\u0634\u062a\u0631\u0627\u06a9 \u0628\u06af\u0630\u0627\u0631\u06cc\u062f \u0642\u062f\u0631\u062f\u0627\u0646\u06cc \u0645\u06cc \u06a9\u0646\u0645.<\/p>\n<p><strong>\u062f\u0631 \u0627\u06cc\u0646\u062c\u0627 \u062e\u0644\u0627\u0635\u0647 \u0627\u06cc \u0627\u0632 \u06a9\u0627\u0631\u0647\u0627\u06cc\u06cc \u0627\u0633\u062a \u06a9\u0647 \u0645\u0646 \u062a\u0627\u06a9\u0646\u0648\u0646 \u0627\u0646\u062c\u0627\u0645 \u062f\u0627\u062f\u0647 \u0627\u0645:<\/strong><\/p>\n<ol>\n<li>\u0628\u0633\u062a\u0647 flutterwave-node-v3 \u0631\u0627 \u0646\u0635\u0628 \u06a9\u0631\u062f\u0645 \u0648 \u0622\u0646 \u0631\u0627 \u0628\u0627 \u06a9\u0644\u06cc\u062f\u0647\u0627\u06cc \u0639\u0645\u0648\u0645\u06cc \u0648 \u0645\u062e\u0641\u06cc \u062e\u0648\u062f \u067e\u06cc\u06a9\u0631\u0628\u0646\u062f\u06cc \u06a9\u0631\u062f\u0645.<\/li>\n<li>\u062a\u0648\u0627\u0628\u0639 TypeScript \u0631\u0627 \u0628\u0631\u0627\u06cc \u0645\u0642\u062f\u0627\u0631\u062f\u0647\u06cc \u0627\u0648\u0644\u06cc\u0647 \u0648 \u062a\u0623\u06cc\u06cc\u062f \u067e\u0631\u062f\u0627\u062e\u062a \u0627\u06cc\u062c\u0627\u062f \u06a9\u0631\u062f.<\/li>\n<li>\u0627\u0632 Sequelize \u0628\u0631\u0627\u06cc \u0645\u062f\u0644 \u0633\u0627\u0632\u06cc \u062c\u062f\u0627\u0648\u0644 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u0645\u0646 (\u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u0645\u062b\u0627\u0644\u060c \u0628\u0644\u06cc\u0637 \u0647\u0627 \u0648 \u0631\u0648\u06cc\u062f\u0627\u062f\u0647\u0627) \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u06a9\u0631\u062f.<\/li>\n<\/ol>\n<p><strong>\u0645\u0634\u06a9\u0644\u0627\u062a \u067e\u06cc\u0634 \u0622\u0645\u062f\u0647:<\/strong><br \/>\u0627\u0644\u0641 \u0647\u0646\u06af\u0627\u0645 \u062a\u0645\u0627\u0633 \u0628\u0627 flw.initializePayment\u060c \u0627\u06cc\u0646 \u062e\u0637\u0627 \u0631\u0627 \u062f\u0631\u06cc\u0627\u0641\u062a \u0645\u06cc \u06a9\u0646\u0645:<br \/>TypeError: initializePayment \u06cc\u06a9 \u062a\u0627\u0628\u0639 \u0646\u06cc\u0633\u062a.<br \/>\u0628 \u0627\u0641\u0632\u0648\u062f\u0646 \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc TypeScript \u062f\u0634\u0648\u0627\u0631 \u0627\u0633\u062a \u0632\u06cc\u0631\u0627 \u0647\u06cc\u0686 \u0646\u0648\u0639 \u0631\u0633\u0645\u06cc TypeScript \u0628\u0631\u0627\u06cc flutterwave-node-v3 \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f \u0648 \u0646\u0648\u0634\u062a\u0646 \u0627\u0639\u0644\u0627\u0646\u200c\u0647\u0627\u06cc \u0646\u0648\u0639 \u0633\u0641\u0627\u0631\u0634\u06cc \u0645\u0634\u06a9\u0644 \u0631\u0627 \u062d\u0644 \u0646\u06a9\u0631\u062f\u0647 \u0627\u0633\u062a.<br \/>\u062c \u0628\u0631\u0627\u06cc \u062f\u0631\u06a9 \u0628\u0647\u062a\u0631\u06cc\u0646 \u0631\u0648\u0634\u200c\u0647\u0627 \u0628\u0631\u0627\u06cc \u0631\u0633\u06cc\u062f\u06af\u06cc \u0628\u0647 \u062a\u0645\u0627\u0633\u200c\u0647\u0627\u06cc \u0646\u0627\u0647\u0645\u0632\u0645\u0627\u0646 \u0648 \u0628\u0647\u200c\u0631\u0648\u0632\u0631\u0633\u0627\u0646\u06cc \u0648\u0636\u0639\u06cc\u062a \u0631\u0648\u06cc\u062f\u0627\u062f \u0648 \u0628\u0644\u06cc\u0637 \u062f\u0631 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u0628\u0647 \u06a9\u0645\u06a9 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u06cc\u062f.<\/p>\n<p><strong>\u0622\u0646\u0686\u0647 \u0645\u0646 \u0627\u0645\u062a\u062d\u0627\u0646 \u06a9\u0631\u062f\u0647 \u0627\u0645:<\/strong><\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>\/\/ src\/types\/flutterwave-node-v3.d.ts\ndeclare module 'flutterwave-node-v3' {\n    \/\/ TypeScript interfaces for the Flutterwave response and request types\n    export interface PaymentInitiateResponse {\n      status: string;\n      message: string;\n      data: {\n        link: string;\n      };\n    }\n\n    export interface TransactionVerifyResponse {\n      status: string;\n      message: string;\n      data: {\n        tx_ref: string;\n        flw_ref: string;\n        currency: string;\n        status: string;\n      };\n    }\n\n    export interface Flutterwave {\n      initializePayment(\n        payload: {\n          tx_ref: string;\n          amount: number;\n          currency: string;\n          redirect_url: string;\n          customer: {\n            email: string;\n          };\n        }\n      ): Promise<paymentinitiateresponse>;\n\n      TransactionVerify(\n        payload: { id: string }\n      ): Promise<transactionverifyresponse>;\n    }\n\n    const Flutterwave: new (publicKey: string, secretKey: string) =&gt; Flutterwave;\n    export = Flutterwave;\n  }\n\n<\/transactionverifyresponse><\/paymentinitiateresponse><\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>import Flutterwave from \"flutterwave-node-v3\";\nimport { FLWPUBK, FLWSECK } from \"..\/config\";\n\nconst flw = new Flutterwave(FLWPUBK, FLWSECK);\n\nexport const initiatePayment = async (payload: {\n  tx_ref: string;\n  amount: number;\n  currency: string;\n  email: string;\n  redirect_url: string;\n}) =&gt; {\n  try {\n    const response = await flw.Charge.card({\n      tx_ref: payload.tx_ref,\n      amount: payload.amount,\n      currency: payload.currency,\n      redirect_url: payload.redirect_url,\n      customer: {\n        email: payload.email,\n      },\n      payment_options: \"card\", \/\/ Optional: specify payment options\n    });\n\n    if (response.status === \"success\") {\n      return response.meta.authorization.redirect; \/\/ Payment link\n    } else {\n      throw new Error(response.message || \"Failed to initiate payment.\");\n    }\n  } catch (error) {\n    console.error(\"Payment initiation error:\", error);\n    throw new Error(\"Failed to initiate payment.\");\n  }\n};\nexport const verifyPayment = async (transactionId: string) =&gt; {\n  try {\n    const response = await flw.Transaction.verify({ id: transactionId });\n\n    if (response.status === \"success\") {\n      return response.data;\n    } else {\n      throw new Error(\"Payment verification failed.\");\n    }\n  } catch (error) {\n    console.error(\"Payment verification error:\", error);\n    throw new Error(\"Failed to verify payment.\");\n  }\n};\n\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>`<br \/>import { Request, Response } from &#8220;express&#8221;;<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { EventAttribute, EventInstance } \u0627\u0632 &#8220;..\/models\/eventModel&#8221;;<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { TicketAttribute, TicketInstance } \u0627\u0632 &#8220;..\/models\/ticketModel&#8221;;<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { v4 \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 uuidv4 } \u0627\u0632 &#8220;uuid&#8221;;<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { JwtPayload } \u0627\u0632 &#8220;jsonwebtoken&#8221;;<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 QRCode \u0627\u0632 &#8220;qrcode&#8221;\u061b<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { NotificationInstance } \u0627\u0632 &#8220;..\/models\/notificationModel&#8221;\u061b<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { initiatePayment, verifyPayment } \u0627\u0632 &#8220;..\/interface\/payment.dto&#8221;;<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 { BASE_URL, FRONTEND_URL } \u0627\u0632 &#8220;..\/config&#8221;\u061b<br \/>\u0648\u0627\u0631\u062f \u06a9\u0631\u062f\u0646 {UserAttribute, UserInstance } \u0627\u0632 &#8220;..\/models\/userModel&#8221;;<\/p>\n<p>\u0635\u0627\u062f\u0631\u0627\u062a \u0647\u0632\u06cc\u0646\u0647 \u062e\u0631\u06cc\u062f \u0628\u0644\u06cc\u0637 = \u0646\u0627\u0647\u0645\u06af\u0627\u0645 (<br \/>\u062f\u0631\u062e\u0648\u0627\u0633\u062a: JwtPayload\u060c<br \/>\u067e\u0627\u0633\u062e: \u067e\u0627\u0633\u062e<br \/>): \u0642\u0648\u0644 => {<br \/>const userId = req.user;<br \/>const { eventId } = req.params;<br \/>const { ticketType,currency } = req.body;<\/p>\n<p>\u0633\u0639\u06cc \u06a9\u0646 {<br \/>const user = (\u0627\u0646\u062a\u0638\u0627\u0631 UserInstance.findOne({<br \/>\u06a9\u062c\u0627: { id: userId }\u060c<br \/>})) \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 UserAttribute \u0646\u0627\u0634\u0646\u0627\u062e\u062a\u0647.<br \/>\u0627\u06af\u0631 (! \u06a9\u0627\u0631\u0628\u0631) {<br \/>return res.status(404).json({ \u062e\u0637\u0627: &#8220;\u06a9\u0627\u0631\u0628\u0631 \u067e\u06cc\u062f\u0627 \u0646\u0634\u062f&#8221; });<br \/>}<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>const event = (await EventInstance.findOne({\n  where: { id: eventId },\n})) as unknown as EventAttribute;\nif (!event) {\n  return res.status(404).json({ error: \"Event not found\" });\n}\n\nif (new Date() &gt; new Date(event.date)) {\n  return res\n    .status(400)\n    .json({ error: \"Cannot purchase tickets for expired events\" });\n}\n\nconst ticketPrice = event.ticketType[ticketType];\nif (!ticketPrice) {\n  return res.status(400).json({ error: \"Invalid ticket type\" });\n}\n\nconst ticketId = uuidv4();\n\nconst qrCodeData = {\n  ticketId,\n  userId,\n  eventId: event.id,\n  ticketType,\n  price: ticketPrice,\n  purchaseDate: new Date(),\n};\nconst qrCode = await QRCode.toDataURL(JSON.stringify(qrCodeData));\n\nconst newTicket = await TicketInstance.create({\n  id: ticketId,\n  eventId: event.id,\n  userId,\n  ticketType,\n  price: ticketPrice,\n  purchaseDate: new Date(),\n  qrCode,\n  paid: false,\n  currency,\n  validationStatus: \"Invalid\",\n}) as unknown as TicketAttribute;\n\nconst notification = await NotificationInstance.create({\n  id: uuidv4(),\n  title: \"Ticket Purchase Successful\",\n  message: `You have successfully purchased a ${ticketType} ticket for the event ${event.title}.`,\n  userId,\n  isRead: false,\n});\n\nconst paymentLink = await initiatePayment({\n  tx_ref: newTicket.id,\n  amount: newTicket.price,\n  currency: newTicket.currency,\n  email: user.email,\n  redirect_url: `${BASE_URL}\/tickets\/callback`,\n});\n\nreturn res.status(201).json({\n  message: \"Ticket created successfully\",\n  paymentLink,\n  ticket: newTicket,\n});\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>} catch (\u062e\u0637\u0627: \u0647\u0631) {<br \/>Result Res<br \/>\u0648\u0636\u0639\u06cc\u062a .(500)<br \/>.json({ \u062e\u0637\u0627: &#8220;\u062e\u0631\u06cc\u062f \u0628\u0644\u06cc\u0637 \u0627\u0646\u062c\u0627\u0645 \u0646\u0634\u062f&#8221;\u060c \u062c\u0632\u0626\u06cc\u0627\u062a: error.message });<br \/>}<br \/>};<\/p>\n<p>\u0635\u0627\u062f\u0631\u0627\u062a \u0647\u0632\u06cc\u0646\u0647 \u067e\u0631\u062f\u0627\u062e\u062a \u062a\u0623\u06cc\u06cc\u062f = \u0646\u0627\u0647\u0645\u06af\u0627\u0645 (<br \/>\u062f\u0631\u062e\u0648\u0627\u0633\u062a: JwtPayload\u060c<br \/>\u067e\u0627\u0633\u062e: \u067e\u0627\u0633\u062e<br \/>): \u0642\u0648\u0644 => {<br \/>const {transaction_id, tx_ref } = req.query;<\/p>\n<p>\u0633\u0639\u06cc \u06a9\u0646 {<br \/>\/\/ \u0628\u0631\u0631\u0633\u06cc \u067e\u0631\u062f\u0627\u062e\u062a<br \/>const paymentData = await verifyPayment(transaction_id as string);<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>if (paymentData.tx_ref === tx_ref) {\n  await TicketInstance.update(\n    {\n      paid: true,\n      validationStatus: \"Valid\",\n      flwRef: paymentData.flw_ref,\n      currency: paymentData.currency,\n    },\n    {\n      where: { id: tx_ref },\n    }\n  );\n\n  const ticket = (await TicketInstance.findOne({\n    where: { id: tx_ref },\n  })) as unknown as TicketAttribute;\n  const event = (await EventInstance.findOne({\n    where: { id: ticket?.eventId },\n  })) as unknown as EventAttribute;\n\n  if (event) {\n    if (event.quantity &lt;= 0) {\n      throw new Error(\"Event is sold out\");\n    }\n\n    await EventInstance.update(\n      {\n        quantity: event.quantity - 1,\n        sold: event.sold + 1,\n      },\n      { where: { id: ticket?.eventId } }\n    );\n  }\n\n  res.redirect(`${FRONTEND_URL}\/payment-success`);\n} else {\n  throw new Error(\"Transaction reference mismatch\");\n}\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>} catch (\u062e\u0637\u0627) {<br \/>console.error(error);<br \/>res.redirect(<code>${FRONTEND_URL}\/payment-failed<\/code>)<br \/>}<br \/>};`<\/p>\n<p><strong>\u062c\u0632\u0626\u06cc\u0627\u062a \u067e\u0634\u062a\u0647 \u0641\u0646\u0627\u0648\u0631\u06cc:<\/strong><\/p>\n<ul>\n<li>\u0686\u0627\u0631\u0686\u0648\u0628 Backend: Express.js<\/li>\n<li>ORM: \u062f\u0646\u0628\u0627\u0644\u0647 \u062f\u0627\u0631 \u06a9\u0631\u062f\u0646<\/li>\n<li>\u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647: PostgreSQL<\/li>\n<li>\u0632\u0628\u0627\u0646: TypeScript<\/li>\n<\/ul>\n<p><strong>\u0633\u0648\u0627\u0644\u0627\u062a:<\/strong><\/p>\n<ol>\n<li>\u0622\u06cc\u0627 \u06a9\u0633\u06cc \u0628\u0627 \u0645\u0648\u0641\u0642\u06cc\u062a Flutterwave \u0631\u0627 \u062f\u0631 \u067e\u0631\u0648\u0698\u0647 TypeScript + Express.js \u0627\u062f\u063a\u0627\u0645 \u06a9\u0631\u062f\u0647 \u0627\u0633\u062a\u061f<\/li>\n<li>\u0622\u06cc\u0627 \u0627\u0639\u0644\u0627\u0646\u200c\u0647\u0627\u06cc \u0646\u0648\u0639 \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc \u0634\u062f\u0647 \u062a\u0648\u0633\u0637 \u062c\u0627\u0645\u0639\u0647 \u06cc\u0627 \u0631\u0648\u06cc\u06a9\u0631\u062f\u0647\u0627\u06cc \u0628\u0647\u062a\u0631\u06cc \u0628\u0631\u0627\u06cc \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u0627\u06cc\u0646 \u0628\u0633\u062a\u0647 \u0648\u062c\u0648\u062f \u062f\u0627\u0631\u062f\u061f<\/li>\n<li>\u0628\u0647\u062a\u0631\u06cc\u0646 \u0631\u0627\u0647 \u0628\u0631\u0627\u06cc \u0631\u0633\u06cc\u062f\u06af\u06cc \u0628\u0647 callbacktransaction_id \u0628\u0631\u0627\u06cc \u062a\u0623\u06cc\u06cc\u062f \u067e\u0631\u062f\u0627\u062e\u062a\u200c\u0647\u0627 \u0648 \u0628\u0647\u200c\u0631\u0648\u0632\u0631\u0633\u0627\u0646\u06cc \u0633\u0648\u0627\u0628\u0642 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u0686\u06cc\u0633\u062a\u061f<\/li>\n<\/ol>\n<p>\u0627\u06af\u0631 \u0628\u0627 \u0645\u0634\u06a9\u0644\u0627\u062a \u0645\u0634\u0627\u0628\u0647\u06cc \u0645\u0648\u0627\u062c\u0647 \u0634\u062f\u0647 \u0627\u06cc\u062f \u06cc\u0627 \u067e\u06cc\u0634\u0646\u0647\u0627\u062f\u0647\u0627\u06cc\u06cc \u062f\u0627\u0631\u06cc\u062f (\u0645\u0642\u0627\u0644\u0627\u062a\u060c \u0622\u0645\u0648\u0632\u0634 \u0647\u0627\u06cc \u06cc\u0648\u062a\u06cc\u0648\u0628 \u06cc\u0627 \u0645\u062e\u0627\u0632\u0646 GitHub)\u060c \u0627\u0632 \u06a9\u0645\u06a9 \u0634\u0645\u0627 \u0628\u0633\u06cc\u0627\u0631 \u0633\u067e\u0627\u0633\u06af\u0632\u0627\u0631\u0645.<\/p>\n<p>\u067e\u06cc\u0634\u0627\u067e\u06cc\u0634 \u0645\u062a\u0634\u06a9\u0631\u0645 \ud83d\ude80<\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Summarize this content to 400 words in Persian Lang \u0633\u0644\u0627\u0645 \u0627\u0646\u062c\u0645\u0646 DEV\u060c \u0645\u0646 \u062f\u0631 \u062d\u0627\u0644 \u062d\u0627\u0636\u0631 \u0631\u0648\u06cc \u0627\u062f\u063a\u0627\u0645 Flutterwave \u062f\u0631 \u0628\u0631\u0646\u0627\u0645\u0647 \u062e\u0648\u062f \u06a9\u0627\u0631 \u0645\u06cc \u06a9\u0646\u0645 \u06a9\u0647 \u0628\u0627 TypeScript\u060c Express.js\u060c Sequelize \u0648 PostgreSQL \u0633\u0627\u062e\u062a\u0647 \u0634\u062f\u0647 \u0627\u0633\u062a. \u0628\u0627 \u0627\u06cc\u0646 \u062d\u0627\u0644\u060c \u0645\u0646 \u0628\u0627 \u0686\u0646\u062f \u0686\u0627\u0644\u0634 \u0631\u0648\u0628\u0631\u0648 \u0628\u0648\u062f\u0647 \u0627\u0645 \u0648 \u0627\u0632 \u0647\u0631 \u06af\u0648\u0646\u0647 \u0631\u0627\u0647\u0646\u0645\u0627\u06cc\u06cc \u06cc\u0627 \u0645\u0646\u0628\u0639\u06cc \u06a9\u0647 \u0645\u06cc \u062a\u0648\u0627\u0646\u06cc\u062f &hellip;<\/p>\n","protected":false},"author":2,"featured_media":88319,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"","fifu_image_alt":"","footnotes":""},"categories":[339],"tags":[],"class_list":["post-88318","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dev"],"_links":{"self":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts\/88318","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/comments?post=88318"}],"version-history":[{"count":0,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts\/88318\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/media\/88319"}],"wp:attachment":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/media?parent=88318"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/categories?post=88318"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/tags?post=88318"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}