Dev / services /whatsapp.go
Mhamdans17
Update WhatsApp notification format, add voucher to order reports, and add tenant settings toggle
7121ebc
Raw
History Blame Contribute Delete
8.5 kB
package services
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"time"
"service-warungpos-go/config"
)
const FonnteAPIURL = "https://api.fonnte.com/send"
func formatPhone(phone string) string {
phone = strings.TrimSpace(phone)
phone = strings.ReplaceAll(phone, " ", "")
phone = strings.ReplaceAll(phone, "-", "")
if strings.HasPrefix(phone, "0") {
phone = "62" + phone[1:]
} else if strings.HasPrefix(phone, "+") {
phone = phone[1:]
}
return phone
}
func fmtRupiah(amount int) string {
// Format ribuan dengan titik (contoh: 15000 menjadi Rp 15.000)
str := fmt.Sprintf("%d", amount)
var result []string
length := len(str)
for i := length; i > 0; i -= 3 {
start := i - 3
if start < 0 {
start = 0
}
result = append([]string{str[start:i]}, result...)
}
return "Rp " + strings.Join(result, ".")
}
func divider(char string, length int) string {
return strings.Repeat(char, length)
}
type WAItem struct {
Name string `json:"name"`
Quantity int `json:"quantity"`
Price int `json:"price"`
}
func SendPaymentNotification(
phone string,
orderNumber string,
totalAmount int,
paymentMethod string,
customerName string,
items []WAItem,
changeAmount int,
storeName string,
branchName string,
branchAddress string,
subTotal int,
discountAmount int,
servedBy string,
pointsEarned int,
) bool {
if config.GlobalConfig.FonnteToken == "" {
log.Println("[WHATSAPP] Token Fonnte tidak dikonfigurasi, lewati kirim WhatsApp")
return false
}
if phone == "" {
log.Println("[WHATSAPP] Nomor telepon customer kosong, lewati kirim WhatsApp")
return false
}
formattedPhone := formatPhone(phone)
baseStore := storeName
if baseStore == "" {
baseStore = config.GlobalConfig.StoreName
}
if baseStore == "" {
baseStore = "XOGM Go"
}
displayName := baseStore
if branchName != "" {
displayName = baseStore + " - " + branchName
}
methodLabel := "Nontunai"
if paymentMethod == "cash" {
methodLabel = "Tunai (Cash)"
}
// Buat isi pesan
var lines []string
lines = append(lines, "Berikut adalah e-receipt untuk transaksi Anda")
lines = append(lines, orderNumber)
lines = append(lines, "")
now := time.Now()
// Gunakan WIB atau UTC
lines = append(lines, fmt.Sprintf("Pembayaran anda telah kami terima pada %s", now.Format("2006-01-02 15:04:05")))
lines = append(lines, "")
lines = append(lines, fmt.Sprintf("KWITANSI %s", strings.ToUpper(baseStore)))
lines = append(lines, "")
lines = append(lines, fmt.Sprintf("Outlet : %s", displayName))
if branchAddress != "" {
lines = append(lines, fmt.Sprintf("Alamat : %s", branchAddress))
}
lines = append(lines, divider("-", 30))
lines = append(lines, fmt.Sprintf("No. Kwitansi : %s", orderNumber))
if customerName != "" {
lines = append(lines, fmt.Sprintf("Pelanggan : %s", customerName))
} else {
lines = append(lines, "Pelanggan : Umum")
}
if servedBy != "" {
lines = append(lines, fmt.Sprintf("Kasir : %s", servedBy))
}
lines = append(lines, fmt.Sprintf("Tanggal : %s", now.Format("2006-01-02 15:04:05")))
lines = append(lines, fmt.Sprintf("Pembayaran : %s", methodLabel))
lines = append(lines, divider("-", 30))
if len(items) > 0 {
lines = append(lines, "Layanan / Pesanan")
for _, item := range items {
subtotal := item.Price * item.Quantity
lines = append(lines, fmt.Sprintf("- %s x%d — %s", item.Name, item.Quantity, fmtRupiah(subtotal)))
}
lines = append(lines, "")
}
lines = append(lines, "Ringkasan Pembayaran")
if discountAmount > 0 {
lines = append(lines, "- Voucher : Ya")
} else {
lines = append(lines, "- Voucher : Tidak")
}
if subTotal > 0 {
lines = append(lines, fmt.Sprintf("- Sub Total : %s", fmtRupiah(subTotal)))
} else {
// Fallback
lines = append(lines, fmt.Sprintf("- Sub Total : %s", fmtRupiah(totalAmount+discountAmount)))
}
lines = append(lines, fmt.Sprintf("- Potongan Voucher : %s", fmtRupiah(discountAmount)))
lines = append(lines, fmt.Sprintf("- Total Bayar : %s", fmtRupiah(totalAmount)))
if paymentMethod == "cash" {
lines = append(lines, fmt.Sprintf("- Kembalian : %s", fmtRupiah(changeAmount)))
}
if pointsEarned > 0 {
lines = append(lines, fmt.Sprintf("- Poin Didapat : %d Poin", pointsEarned))
}
lines = append(lines, divider("-", 30))
lines = append(lines, "Terima kasih telah menggunakan jasa kami.")
lines = append(lines, "")
lines = append(lines, "Hormat kami,")
lines = append(lines, "")
lines = append(lines, displayName)
lines = append(lines, "Jika Anda tidak merasa melakukan transaksi ini, segera hubungi kami.")
message := strings.Join(lines, "\n")
return postFonnte(formattedPhone, message)
}
func SendOtpWa(phone string, otpCode string) bool {
if config.GlobalConfig.FonnteToken == "" {
return false
}
formattedPhone := formatPhone(phone)
storeName := config.GlobalConfig.StoreName
if storeName == "" {
storeName = "XOGM Go"
}
message := fmt.Sprintf("*%s*\n\nKode OTP Registrasi Anda adalah:\n*%s*\n\nKode ini berlaku selama 5 menit. Jangan berikan kode ini kepada siapa pun.", storeName, otpCode)
return postFonnte(formattedPhone, message)
}
func SendResetPasswordOtpWa(phone string, otpCode string) bool {
if config.GlobalConfig.FonnteToken == "" {
return false
}
formattedPhone := formatPhone(phone)
storeName := config.GlobalConfig.StoreName
if storeName == "" {
storeName = "XOGM Go"
}
message := fmt.Sprintf("*%s*\n\nKode OTP Reset Password Anda adalah:\n*%s*\n\nKode ini berlaku selama 10 menit. Jangan berikan kode ini kepada siapa pun.", storeName, otpCode)
return postFonnte(formattedPhone, message)
}
func SendApprovalWa(phone string, storeName string, email string) bool {
if config.GlobalConfig.FonnteToken == "" || phone == "" {
return false
}
formattedPhone := formatPhone(phone)
message := fmt.Sprintf("✅ *SELAMAT! AKUN ANDA TELAH DISETUJUI*\n\nPerusahaan/Toko *%s* sudah diaktifkan!\nAnda dapat mencoba login ke Dashboard Kasir sekarang menggunakan email:\n_%s_\n\n*PENTING*: Agar dapat mulai menerima transaksi, pastikan Anda melakukan pengisian saldo (Top Up) Wallet/Dompet terlebih dahulu melalui menu Top Up di aplikasi.\n\nSukses Selalu,\nTim Pengelola XOGM Go.", storeName, email)
return postFonnte(formattedPhone, message)
}
func SendNewRegistrationAdminWa(adminPhone string, storeName string, ownerName string, email string, phone string) bool {
if config.GlobalConfig.FonnteToken == "" || adminPhone == "" {
return false
}
formattedPhone := formatPhone(adminPhone)
message := fmt.Sprintf("🚨 *PENDAFTARAN BARU XOGM GO* 🚨\n\nAda pendaftar baru yang menunggu persetujuan (approval) Anda:\n\n*Nama Toko / PT*: %s\n*Nama Owner*: %s\n*Email*: %s\n*No HP*: %s\n\nSilakan cek Dashboard Super Admin untuk menyetujui akun ini.", storeName, ownerName, email, phone)
return postFonnte(formattedPhone, message)
}
func postFonnte(target string, message string) bool {
// Gunakan http client bawaan Go dengan timeout 15 detik
client := &http.Client{Timeout: 15 * time.Second}
// Payload dikirim sebagai form url encoded
data := url.Values{}
data.Set("target", target)
data.Set("message", message)
data.Set("countryCode", "62")
req, err := http.NewRequest("POST", FonnteAPIURL, bytes.NewBufferString(data.Encode()))
if err != nil {
log.Printf("[WHATSAPP] Gagal membuat request Fonnte: %v", err)
return false
}
req.Header.Set("Authorization", config.GlobalConfig.FonnteToken)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
log.Printf("[WHATSAPP] Error kirim Fonnte: %v", err)
return false
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("[WHATSAPP] Gagal membaca response Fonnte: %v", err)
return false
}
var responseData map[string]interface{}
if err := json.Unmarshal(bodyBytes, &responseData); err != nil {
log.Printf("[WHATSAPP] Gagal parse JSON Fonnte: %v", err)
return false
}
status, ok := responseData["status"].(bool)
if ok && status {
log.Printf("[WHATSAPP] Notification terkirim ke target %s", maskPhone(target))
return true
}
reason := responseData["reason"]
log.Printf("[WHATSAPP] Fonnte gagal mengirim: %v", reason)
return false
}
func maskPhone(phone string) string {
if len(phone) <= 6 {
return phone
}
return phone[:4] + "****" + phone[len(phone)-2:]
}