Mhamdans17
Update WhatsApp notification format, add voucher to order reports, and add tenant settings toggle
7121ebc | 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:] | |
| } | |