Update template
This commit is contained in:
parent
5111fedfa8
commit
4be9c7d73c
@ -55,6 +55,13 @@ type Customer struct {
|
||||
OTP string
|
||||
}
|
||||
|
||||
type CustomerPoints struct {
|
||||
ID uint64
|
||||
CustomerID uint64
|
||||
TotalPoints int
|
||||
AvailablePoints int
|
||||
}
|
||||
|
||||
type AuthenticateUser struct {
|
||||
ID int64
|
||||
Token string
|
||||
|
||||
@ -18,6 +18,10 @@ type CustomerRepo interface {
|
||||
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
||||
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
||||
AddPoints(ctx mycontext.Context, id int64, points int, reference string) error
|
||||
GetPointsByCustomerID(
|
||||
ctx mycontext.Context,
|
||||
customerID int64,
|
||||
) (*entity.CustomerPoints, error)
|
||||
FindSequence(ctx mycontext.Context, partnerID int64) (int64, error)
|
||||
GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error)
|
||||
VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error)
|
||||
@ -172,6 +176,31 @@ func (r *customerRepository) AddPoints(ctx mycontext.Context, customerID int64,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *customerRepository) GetPointsByCustomerID(
|
||||
ctx mycontext.Context,
|
||||
customerID int64,
|
||||
) (*entity.CustomerPoints, error) {
|
||||
var cp models.CustomerPointsDB
|
||||
|
||||
if err := r.db.Where("customer_id = ?", customerID).First(&cp).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to find customer point by customer_id")
|
||||
}
|
||||
|
||||
return r.toDomainCustomerPoint(cp), nil
|
||||
}
|
||||
|
||||
func (r *customerRepository) toDomainCustomerPoint(dbModel models.CustomerPointsDB) *entity.CustomerPoints {
|
||||
return &entity.CustomerPoints{
|
||||
ID: dbModel.ID,
|
||||
CustomerID: dbModel.CustomerID,
|
||||
TotalPoints: dbModel.TotalPoints,
|
||||
AvailablePoints: dbModel.AvailablePoints,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *customerRepository) toCustomerDBModel(customer *entity.Customer) models.CustomerDB {
|
||||
return models.CustomerDB{
|
||||
ID: customer.ID,
|
||||
|
||||
@ -21,6 +21,10 @@ type Repository interface {
|
||||
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
||||
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
||||
AddPoints(ctx mycontext.Context, id int64, points int, reference string) error
|
||||
GetPointsByCustomerID(
|
||||
ctx mycontext.Context,
|
||||
customerID int64,
|
||||
) (*entity.CustomerPoints, error)
|
||||
FindSequence(ctx mycontext.Context, partnerID int64) (int64, error)
|
||||
GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error)
|
||||
VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error)
|
||||
@ -29,6 +33,7 @@ type Repository interface {
|
||||
type Service interface {
|
||||
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
|
||||
AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error
|
||||
GetCustomerPoints(ctx mycontext.Context, customerID int64) (*entity.CustomerPoints, error)
|
||||
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||
CustomerCheck(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (*entity.CustomerCheckResponse, error)
|
||||
GetAllCustomers(ctx mycontext.Context, req *entity.MemberSearch) (*entity.MemberList, int, error)
|
||||
@ -188,6 +193,15 @@ func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *customerSvc) GetCustomerPoints(ctx mycontext.Context, customerID int64) (*entity.CustomerPoints, error) {
|
||||
cp, err := s.repo.GetPointsByCustomerID(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to add points to customer")
|
||||
}
|
||||
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
func (s *customerSvc) GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) {
|
||||
customer, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
|
||||
@ -52,7 +52,7 @@ func (s *orderSvc) processPostOrderActions(
|
||||
}
|
||||
|
||||
if order.CustomerID != nil && *order.CustomerID > 0 {
|
||||
err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/1000), fmt.Sprintf("TRX #%s", trx.ID))
|
||||
err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/50000), fmt.Sprintf("TRX #%s", trx.ID))
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err))
|
||||
}
|
||||
@ -84,10 +84,21 @@ func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, po
|
||||
}
|
||||
|
||||
func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error {
|
||||
newPoint := int(order.Total / 50000)
|
||||
|
||||
if newPoint <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if order.CustomerID == nil || *order.CustomerID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
customerPoint, err := s.customer.GetCustomerPoints(ctx, *order.CustomerID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
customer, err := s.customer.GetCustomer(ctx, *order.CustomerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error getting customer details", zap.Error(err))
|
||||
@ -133,14 +144,15 @@ func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.O
|
||||
}
|
||||
|
||||
transactionDate := transaction.CreatedAt.Format("02 January 2006 15:04")
|
||||
viewTransactionLink := fmt.Sprintf("https://enaklo.co.id/transaction/%s", transaction.ID)
|
||||
viewTransactionLink := "https://web.enaklo.co.id"
|
||||
|
||||
emailData := map[string]interface{}{
|
||||
"UserName": customer.Name,
|
||||
"PointsName": "ELP",
|
||||
"PointsBalance": int(order.Total / 1000),
|
||||
"NewPoints": int(order.Total / 1000),
|
||||
"RedeemLink": "enaklo.co.id",
|
||||
"PointsName": "Point",
|
||||
"PointsBalance": newPoint,
|
||||
"NewPoints": newPoint,
|
||||
"TotalPoints": customerPoint.TotalPoints,
|
||||
"RedeemLink": "web.enaklo.co.id",
|
||||
"BranchName": branchName,
|
||||
"TransactionNumber": order.ID,
|
||||
"TransactionDate": transactionDate,
|
||||
@ -149,6 +161,8 @@ func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.O
|
||||
"TotalPayment": fmt.Sprintf("Rp %s", formatCurrency(order.Total)),
|
||||
"ViewTransactionLink": viewTransactionLink,
|
||||
"ExpiryDate": order.CreatedAt.Format("02 January 2006"),
|
||||
"UndianDate": "17 Mei 2025",
|
||||
"WebURL": "https://web.enaklo.co.id/undian",
|
||||
}
|
||||
|
||||
return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
|
||||
|
||||
@ -44,6 +44,7 @@ type CustomerService interface {
|
||||
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
|
||||
AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error
|
||||
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||
GetCustomerPoints(ctx mycontext.Context, customerID int64) (*entity.CustomerPoints, error)
|
||||
}
|
||||
|
||||
type TransactionService interface {
|
||||
|
||||
@ -175,10 +175,9 @@
|
||||
|
||||
<div class="info-box">
|
||||
<strong>Cara Menyelesaikan Pendaftaran:</strong><br>
|
||||
1. Tunjukkan email ini kepada staf kasir Enaklo<br>
|
||||
2. Staf akan memverifikasi identitas Anda<br>
|
||||
3. Staf akan memasukkan kode OTP ini ke sistem POS<br>
|
||||
4. Pendaftaran member Anda akan segera aktif!
|
||||
1. Masukkan kode OTP yang dikirim ke email Anda pada layar ini<br>
|
||||
2. Verifikasi identitas Anda dan selesaikan pendaftaran<br>
|
||||
3. Pendaftaran member Anda akan segera aktif!
|
||||
</div>
|
||||
|
||||
<div class="text">
|
||||
|
||||
@ -10,85 +10,68 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
background-color: #f5f5f5;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 40px 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.content {
|
||||
background-color: #ffffff;
|
||||
margin: 0px auto;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #d90000;
|
||||
padding: 25px 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 120px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.greeting {
|
||||
color: white;
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.body-content {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.points-card {
|
||||
background-color: #fff8f0;
|
||||
.card {
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.new-points-card {
|
||||
background-color: #fff8f0;
|
||||
border-left: 5px solid #f46f02;
|
||||
}
|
||||
|
||||
.points-heading {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin: 0 0 5px 0;
|
||||
.total-points-card {
|
||||
background-color: #e8f5e9;
|
||||
border-left: 5px solid #4caf50;
|
||||
}
|
||||
|
||||
.points-value {
|
||||
font-weight: bold;
|
||||
font-size: 28px;
|
||||
color: #f46f02;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.expiry {
|
||||
.card-heading {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
color: #666666;
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
.card-value {
|
||||
font-size: 26px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.total-points-card {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.message {
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #333333;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@ -100,48 +83,72 @@
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.info {
|
||||
font-size: 14px;
|
||||
color: #555555;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
color: #808080;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #eeeeee;
|
||||
}
|
||||
a {
|
||||
color: #f46f02;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="padding: 40px 20px; background-color: #f5f5f5;">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<img src="https://res.cloudinary.com/dl0wpumax/image/upload/c_thumb,w_200,g_face/v1741363977/61747686_5_vtz0n4.png" alt="Enaklo Logo" class="logo">
|
||||
<img src="https://res.cloudinary.com/dl0wpumax/image/upload/c_thumb,w_200,g_face/v1741363977/61747686_5_vtz0n4.png"
|
||||
alt="Enaklo Logo" class="logo">
|
||||
<h1 class="greeting">Hai {{ .UserName }}!</h1>
|
||||
</div>
|
||||
|
||||
<!-- BODY -->
|
||||
<div class="body-content">
|
||||
<div class="points-card">
|
||||
<p class="points-heading">Kamu dapat poin</p>
|
||||
<p class="points-value">{{ .NewPoints }} {{ .PointsName }}</p>
|
||||
<p class="expiry">Berlaku sampai {{ .ExpiryDate }}</p>
|
||||
|
||||
<!-- New Points Earned -->
|
||||
<div class="card new-points-card">
|
||||
<p class="card-heading">Poin baru yang kamu dapat</p>
|
||||
<p class="card-value">{{ .NewPoints }} {{ .PointsName }}</p>
|
||||
</div>
|
||||
|
||||
<div class="message">
|
||||
Kamu bisa pakai poinnya untuk memotong jumlah transaksimu atau ditukarkan dengan Deals. Yuk, pakai sekarang!
|
||||
<div class="card total-points-card">
|
||||
<p class="card-heading">Total Poin Undian</p>
|
||||
<p class="card-value">{{ .TotalPoints }} {{ .PointsName }}</p>
|
||||
</div>
|
||||
|
||||
<a href="{{ .RedeemLink }}" class="cta-button">Pakai Sekarang</a>
|
||||
<p class="subtitle">
|
||||
Poin kamu akan digunakan untuk undian pada tanggal <strong>{{ .UndianDate }}</strong>.
|
||||
</p>
|
||||
|
||||
<p class="info">
|
||||
Nantikan undian pada tanggal <strong>{{ .UndianDate }}</strong>
|
||||
dan kunjungi <a href="{{ .WebURL }}">{{ .WebURL }}</a> untuk informasi lebih lanjut.
|
||||
</p>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
Email ini dikirim secara otomatis. Mohon jangan membalas email ini.<br>
|
||||
Butuh bantuan? Hubungi tim support kami di <a href="mailto:support@enaklo.com" style="color: #f46f02;">support@enaklo.com</a>.
|
||||
Email ini dikirim otomatis. Mohon jangan membalas email ini.<br>
|
||||
Butuh bantuan? Hubungi tim support kami di
|
||||
<a href="mailto:support@enaklo.com">support@enaklo.com</a>.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user