From 76961c8312312609dbef0646274f6dd1f6c2bf19 Mon Sep 17 00:00:00 2001
From: Rafi Zadanly
Date: Fri, 3 Mar 2023 16:44:12 +0700
Subject: add midtrans payment email notification
---
src/lib/checkout/components/Checkout.jsx | 6 +-
src/lib/checkout/components/FinishCheckout.jsx | 18 +-
src/lib/checkout/email/FinishCheckoutEmail.jsx | 326 +++++++++++++++++++++++++
src/pages/api/shop/finish-checkout.js | 91 +++++++
src/pages/api/shop/midtrans-payment.js | 33 ++-
src/pages/shop/checkout/finish.jsx | 2 +-
6 files changed, 459 insertions(+), 17 deletions(-)
create mode 100644 src/lib/checkout/email/FinishCheckoutEmail.jsx
create mode 100644 src/pages/api/shop/finish-checkout.js
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 42608cef..8af3d996 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -126,7 +126,7 @@ const Checkout = () => {
`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/midtrans-payment?transactionId=${isCheckouted.id}`
)
for (const product of products) deleteItemCart({ productId: product.id })
- window.snap.pay(payment.data.token)
+ window.location.href = payment.data.redirectUrl
}
return (
@@ -252,7 +252,7 @@ const Checkout = () => {
{isLoading ? 'Loading...' : 'Bayar'}
@@ -261,7 +261,7 @@ const Checkout = () => {
>
)
diff --git a/src/lib/checkout/components/FinishCheckout.jsx b/src/lib/checkout/components/FinishCheckout.jsx
index a7d65dd0..f5346d67 100644
--- a/src/lib/checkout/components/FinishCheckout.jsx
+++ b/src/lib/checkout/components/FinishCheckout.jsx
@@ -1,8 +1,16 @@
import Link from '@/core/components/elements/Link/Link'
-import useTransaction from '@/lib/transaction/hooks/useTransaction'
+import axios from 'axios'
+import { useEffect } from 'react'
-const FinishCheckout = ({ id }) => {
- const { transaction } = useTransaction({ id })
+const FinishCheckout = ({ query }) => {
+ useEffect(() => {
+ if (query?.order_id) {
+ console.log(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/finish-checkout?orderName=${query.order_id}`);
+ axios.post(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/finish-checkout?orderId=${query.order_id}`
+ )
+ }
+ }, [query])
return (
@@ -13,11 +21,11 @@ const FinishCheckout = ({ id }) => {
Rincian belanja sudah kami kirimkan ke email anda. Mohon dicek kembali. jika tidak
menerima email, anda dapat menghubungi kami disini.
-
{transaction.data?.name}
+
{query?.order_id?.replaceAll('-', '/')}
No. Transaksi
Lihat detail pembelian Anda disini
diff --git a/src/lib/checkout/email/FinishCheckoutEmail.jsx b/src/lib/checkout/email/FinishCheckoutEmail.jsx
new file mode 100644
index 00000000..950fe318
--- /dev/null
+++ b/src/lib/checkout/email/FinishCheckoutEmail.jsx
@@ -0,0 +1,326 @@
+import currencyFormat from '@/core/utils/currencyFormat'
+import toTitleCase from '@/core/utils/toTitleCase'
+import {
+ Body,
+ Column,
+ Container,
+ Head,
+ Heading,
+ Hr,
+ Html,
+ Img,
+ Row,
+ Section,
+ Text
+} from '@react-email/components'
+
+const FinishCheckoutEmail = ({ transaction, payment, statusPayment }) => {
+ return (
+
+
+
+
+
+
+
+
+ {statusPayment == 'success' && 'Terimakasih untuk pembelian anda!'}
+ {statusPayment == 'pending' && 'Menunggu Pembayaran'}
+ {statusPayment == 'failed' && 'Pembayaran Tidak Berhasil'}
+
+
+ Hai {transaction.address.customer.name},
+
+ {statusPayment == 'success' && (
+ <>
+ Terima kasih atas kepercayaan anda berbelanja di Indoteknik. Dengan ini kami
+ informasikan transaksi yang anda lakukan sebesar{' '}
+ {currencyFormat(payment.grossAmount)} {' '}
+ sudah berhasil dilakukan. Pembelian anda akan segera kami proses dan kirim sesuai
+ dengan antrian pesanan yang masuk.
+ >
+ )}
+ {statusPayment == 'pending' && (
+ <>
+ Terima kasih atas kepercayaan anda berbelanja di Indoteknik. Dengan ini kami
+ informasikan transaksi yang anda lakukan sebesar{' '}
+ {currencyFormat(payment.grossAmount)} {' '}
+ belum dilakukan. Silahkan lakukan pembayaran pada hari ini sebelum stoknya
+ kehabisan.
+ >
+ )}
+ {statusPayment == 'failed' && (
+ <>
+ Terima kasih atas kepercayaan anda berbelanja di Indoteknik. Dengan ini kami
+ informasikan transaksi yang anda lakukan Tidak Berhasil. Mohon untuk tidak melakukan
+ pembayaran dikarenakan transaksi ini gagal kami terima. Segera lakukan pembelian
+ kembali dengan produk yang anda inginkan di website Indoteknik.com.
+ >
+ )}
+
+
+ {['pending', 'failed'].includes(statusPayment) && (
+ <>
+ Jika anda mengalami kesulitan, dapat menghubungi Customer Service kami untuk
+ menanyakan transaksi anda lakukan melalui Whatsapp kami.
+ >
+ )}
+ {statusPayment == 'success' && (
+ <>
+ Anda dapat menghubungi Customer Service kami untuk menanyakan status pesanan yang
+ sudah berhasil anda lakukan melalui Whatsapp kami.
+ >
+ )}
+
+
+
+ Detail Transaksi
+
+
+
+
+
+ {statusPayment == 'success' &&
+ 'Struk ini dapat anda simpan sebagai bukti tambahan dalam transaksi yang telah dilakukan.'}
+ {statusPayment == 'pending' &&
+ 'Kami akan menginformasikan melalui email setelah anda berhasil melakukan pembayaran.'}
+ {statusPayment == 'failed' &&
+ 'Dimohon untuk tidak melakukan pembayaran. Karena transaksi anda tidak berhasil dibuat.'}
+
+
+
+ No Transaksi (SO)
+ {transaction.name}
+
+
+ Tanggal Transaksi
+ {payment.transactionTime}
+
+
+ Status Pembayaran
+
+ {statusPayment == 'success' && (
+ Berhasil
+ )}
+ {statusPayment == 'pending' && (
+ Pending
+ )}
+ {statusPayment == 'failed' && (
+ Tidak Berhasil
+ )}
+
+
+
+ Metode Pembayaran
+
+ {toTitleCase(payment.paymentType.replaceAll('_', ' '))}
+
+
+
+ Batas Akhir Pembayaran
+ {payment.expiryTime}
+
+
+ Nominal Transfer
+
+ {currencyFormat(payment.grossAmount)}
+
+
+
+
+ Detail Produk
+
+
+
+
+ {transaction.products.map((product) => (
+
+
+
+
+
+ {product.name}
+ {product.code}
+
+
+ {currencyFormat(product.price.priceDiscount)}
+
+ {product.price.discountPercentage > 0 && (
+ <>
+
+ {currencyFormat(product.price.price)}
+ >
+ )}
+ x {product.quantity} barang
+
+
+
+ ))}
+
+
+
+
+ Subtotal
+ {currencyFormat(transaction.subtotal)}
+
+
+ Total Diskon
+
+ {currencyFormat(transaction.discountTotal)}
+
+
+
+ PPN 11% (Incl.)
+
+ {currencyFormat(transaction.subtotal * 0.11)}
+
+
+
+
+
+
+ Grand Total
+
+ {currencyFormat(transaction.amountTotal)}
+
+
+
+
+
+ Best regards,
+
+
+ PT. Indoteknik Dotcom Gemilang
+
+ Jl. Bandengan Utara 85A No. 8-9 Penjaringan.
+
+ Kec. Penjaringan, Jakarta Utara - DKI Jakarta
+
+
+
+
+ )
+}
+
+const style = {
+ main: {
+ backgroundColor: '#ffffff',
+ margin: '0 auto',
+ fontFamily:
+ "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif"
+ },
+ container: {
+ border: '1px solid #eaeaea',
+ borderRadius: '5px',
+ margin: '40px auto',
+ padding: '20px',
+ width: '465px'
+ },
+ h1: {
+ color: '#000',
+ fontSize: '24px',
+ fontWeight: 'normal',
+ textAlign: 'center',
+ margin: '30px 0',
+ padding: '0'
+ },
+ text: {
+ color: '#000',
+ fontSize: '14px',
+ lineHeight: '24px'
+ },
+ alert: {
+ backgroundColor: '#FDF1C7',
+ border: '1px solid #F8C20A',
+ padding: '8px',
+ borderRadius: '6px',
+ fontSize: '14px',
+ margin: '16px 0'
+ },
+ hr: {
+ border: 'none',
+ borderTop: '1px solid #eaeaea',
+ margin: '8px 0',
+ width: '100%'
+ },
+ descriptionRow: {
+ width: '100%',
+ margin: '10px 0',
+ fontSize: '14px'
+ },
+ descriptionLCol: {
+ width: '50%',
+ color: '#6B7280'
+ },
+ descriptionRCol: {
+ width: '50%',
+ textAlign: 'right'
+ },
+ productRow: {
+ width: '100%',
+ margin: '10px 0',
+ fontSize: '14px'
+ },
+ productLCol: {
+ width: '25%',
+ border: '1px solid #eaeaea',
+ borderRadius: '4px',
+ padding: '1px'
+ },
+ productRCol: {
+ width: '75%',
+ padding: '0 8px',
+ verticalAlign: 'top'
+ },
+ productName: {
+ lineHeight: '121%',
+ margin: '0 0 8px'
+ },
+ productCode: {
+ color: '#6B7280',
+ lineHeight: '100%',
+ margin: '0 0 8px',
+ fontSize: '14px'
+ },
+ productPriceA: {
+ lineHeight: '100%',
+ fontSize: '14px'
+ },
+ productPriceB: {
+ color: '#6B7280',
+ lineHeight: '100%',
+ fontSize: '14px',
+ textDecoration: 'line-through'
+ },
+ badge: {
+ padding: '3px 6px',
+ borderRadius: '6px',
+ fontSize: '14px',
+ width: 'fit-content',
+ marginLeft: 'auto'
+ },
+ badgeRed: {
+ color: '#E20613',
+ backgroundColor: '#FCE2E4',
+ border: '1px solid #E20613'
+ },
+ badgeGreen: {
+ color: '#16A34A',
+ backgroundColor: '#E7F4E9',
+ border: '1px solid #16A34A'
+ }
+}
+
+export default FinishCheckoutEmail
diff --git a/src/pages/api/shop/finish-checkout.js b/src/pages/api/shop/finish-checkout.js
new file mode 100644
index 00000000..66874549
--- /dev/null
+++ b/src/pages/api/shop/finish-checkout.js
@@ -0,0 +1,91 @@
+import odooApi from '@/core/api/odooApi'
+import mailer from '@/core/utils/mailer'
+import FinishCheckoutEmail from '@/lib/checkout/email/FinishCheckoutEmail'
+import { render } from '@react-email/render'
+import axios from 'axios'
+import camelcaseObjectDeep from 'camelcase-object-deep'
+
+export default async function handler(req, res) {
+ const { orderName = null } = req.query
+
+ if (!orderName) {
+ return res.status(422).json({ error: 'parameter missing' })
+ }
+
+ let { auth } = req.cookies
+
+ if (!auth) {
+ return res.status(401).json({ error: 'Unauthorized' })
+ }
+ auth = JSON.parse(auth)
+
+ const midtransAuthKey = btoa(process.env.MIDTRANS_SERVER_KEY + ':')
+ const midtransHeaders = {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ Authorization: `Basic ${midtransAuthKey}`
+ }
+ let midtransStatus = await axios.get(`${process.env.MIDTRANS_HOST}/v2/${orderName}/status`, {
+ headers: midtransHeaders
+ })
+ midtransStatus = camelcaseObjectDeep(midtransStatus.data)
+ if (!midtransStatus?.orderId) {
+ return res.status(400).json({ error: 'Payment Not Found' })
+ }
+
+ const query = `name=${orderName.replaceAll('-', '/')}&limit=1`
+ const searchTransaction = await odooApi(
+ 'GET',
+ `/api/v1/partner/${auth.partnerId}/sale_order?${query}`,
+ {},
+ { Token: auth.token }
+ )
+ if (searchTransaction.saleOrderTotal == 0) {
+ return res.status(400).json({ error: 'Transaction Not Found' })
+ }
+
+ let transaction = await odooApi(
+ 'GET',
+ `/api/v1/partner/${auth.partnerId}/sale_order/${searchTransaction.saleOrders[0].id}`,
+ {},
+ { Token: auth.token }
+ )
+ if (!transaction?.id) {
+ return res.status(400).json({ error: 'Transaction Detail Not Found' })
+ }
+
+ transaction.subtotal = 0
+ transaction.discountTotal = 0
+ for (const product of transaction.products) {
+ transaction.subtotal += product.price.price * product.quantity
+ transaction.discountTotal -=
+ (product.price.price - product.price.priceDiscount) * product.quantity
+ }
+
+ let statusPayment = ''
+ const transactionStatus = midtransStatus.transactionStatus
+ if (['capture', 'settlement'].includes(transactionStatus)) {
+ statusPayment = 'success'
+ } else if (transactionStatus == 'pending') {
+ statusPayment = 'pending'
+ } else {
+ statusPayment = 'failed'
+ }
+
+ const emailMessage = render(
+
+ )
+
+ mailer.sendMail({
+ from: 'sales@indoteknik.com',
+ to: transaction.address.customer.email,
+ subject: 'Pembelian di Indoteknik.com',
+ html: emailMessage
+ })
+
+ return res.status(200).json({ description: 'success' })
+}
diff --git a/src/pages/api/shop/midtrans-payment.js b/src/pages/api/shop/midtrans-payment.js
index a9bf16ac..be676d38 100644
--- a/src/pages/api/shop/midtrans-payment.js
+++ b/src/pages/api/shop/midtrans-payment.js
@@ -1,17 +1,18 @@
import odooApi from '@/core/api/odooApi'
+import camelcaseObjectDeep from 'camelcase-object-deep'
import midtransClient from 'midtrans-client'
export default async function handler(req, res) {
const { transactionId = null } = req.query
if (!transactionId) {
- res.status(422).json({ error: 'parameter missing' })
+ return res.status(422).json({ error: 'parameter missing' })
}
let { auth } = req.cookies
if (!auth) {
- res.status(401).json({ error: 'Unauthorized' })
+ return res.status(401).json({ error: 'Unauthorized' })
}
auth = JSON.parse(auth)
@@ -22,7 +23,7 @@ export default async function handler(req, res) {
{ Token: auth.token }
)
if (!transaction?.id) {
- res.status(400).json({ error: 'No Data' })
+ return res.status(400).json({ error: 'No Data' })
}
const snap = new midtransClient.Snap({
@@ -32,20 +33,36 @@ export default async function handler(req, res) {
const parameter = {
transaction_details: {
- order_id: transaction.name,
+ order_id: transaction.name?.replaceAll('/', '-'),
gross_amount: transaction.amountTotal
},
credit_card: {
secure: true
},
+ item_details: transaction.products.map((product) => ({
+ id: product.code,
+ price: Math.round(product.price.priceDiscount),
+ quantity: product.quantity,
+ name: product.name?.substring(0, 50)
+ })),
customer_details: {
- first_name: transaction.address.invoice.name,
- email: transaction.address.invoice.email,
- phone: transaction.address.invoice.phone
+ first_name: transaction.address.customer.name,
+ email: transaction.address.customer.email || '',
+ phone: transaction.address.customer.phone || '',
+ billing_address: {
+ first_name: transaction.address.invoice.name,
+ email: transaction.address.invoice.email || '',
+ phone: transaction.address.invoice.phone || ''
+ },
+ shipping_address: {
+ first_name: transaction.address.shipping.name,
+ email: transaction.address.shipping.email || '',
+ phone: transaction.address.shipping.phone || ''
+ }
}
}
const midtransTransaction = await snap.createTransaction(parameter)
- res.status(200).json(midtransTransaction)
+ return res.status(200).json(camelcaseObjectDeep(midtransTransaction))
}
diff --git a/src/pages/shop/checkout/finish.jsx b/src/pages/shop/checkout/finish.jsx
index cc64199f..eb7631a0 100644
--- a/src/pages/shop/checkout/finish.jsx
+++ b/src/pages/shop/checkout/finish.jsx
@@ -9,7 +9,7 @@ export default function Finish() {
return (
-
+
)
--
cgit v1.2.3