289 lines
7.8 KiB
Go
289 lines
7.8 KiB
Go
package aplicare
|
|
|
|
import (
|
|
"api-service/internal/config"
|
|
"bytes"
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// =============================================
|
|
// BPJS CLIENT
|
|
// Semua komunikasi ke BPJS Aplicares API ada di sini
|
|
// Header yang dikirim: X-cons-id, X-timestamp, X-signature SAJA
|
|
// Tidak pakai user_key — itu khusus VClaim, bukan Aplicares
|
|
// =============================================
|
|
|
|
type BpjsClient struct {
|
|
baseURL string
|
|
consID string
|
|
consSecret string
|
|
kodePPK string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func NewBpjsClient(cfg config.BpjsConfig) *BpjsClient {
|
|
kodePPK := os.Getenv("APLICARES_KODE_PPK")
|
|
if kodePPK == "" {
|
|
kodePPK = "1323R001"
|
|
}
|
|
|
|
// Pakai APLICARES_BPJS_* kalau ada, fallback ke BPJS_* dari config
|
|
baseURL := os.Getenv("APLICARES_BPJS_BASEURL")
|
|
if baseURL == "" {
|
|
baseURL = cfg.BaseURL
|
|
}
|
|
|
|
consID := os.Getenv("APLICARES_BPJS_CONSID")
|
|
if consID == "" {
|
|
consID = cfg.ConsID
|
|
}
|
|
|
|
secretKey := os.Getenv("APLICARES_BPJS_SECRETKEY")
|
|
if secretKey == "" {
|
|
secretKey = cfg.SecretKey
|
|
}
|
|
|
|
timeout := cfg.Timeout
|
|
if timeout == 0 {
|
|
timeout = 30 * time.Second
|
|
}
|
|
|
|
return &BpjsClient{
|
|
baseURL: strings.TrimRight(strings.TrimSpace(baseURL), "/") + "/",
|
|
consID: strings.TrimSpace(consID),
|
|
consSecret: strings.TrimSpace(secretKey),
|
|
kodePPK: kodePPK,
|
|
httpClient: &http.Client{
|
|
Timeout: timeout,
|
|
},
|
|
}
|
|
}
|
|
|
|
// createHeaders — HANYA 3 header untuk Aplicares
|
|
func (c *BpjsClient) createHeaders() map[string]string {
|
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
|
message := c.consID + "&" + timestamp
|
|
|
|
mac := hmac.New(sha256.New, []byte(c.consSecret))
|
|
mac.Write([]byte(message))
|
|
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
|
|
|
return map[string]string{
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
"X-cons-id": c.consID,
|
|
"X-timestamp": timestamp,
|
|
"X-signature": signature,
|
|
}
|
|
}
|
|
|
|
// =============================================
|
|
// HTTP HELPERS
|
|
// =============================================
|
|
|
|
type bpjsResponse struct {
|
|
Metadata struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
} `json:"metadata"`
|
|
Response struct {
|
|
List json.RawMessage `json:"list"`
|
|
} `json:"response"`
|
|
}
|
|
|
|
func (c *BpjsClient) get(ctx context.Context, endpoint string) (json.RawMessage, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+endpoint, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for k, v := range c.createHeaders() {
|
|
req.Header.Set(k, v)
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GET %s gagal: %w", endpoint, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode >= 400 {
|
|
return nil, fmt.Errorf("HTTP error: %d - %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var r bpjsResponse
|
|
if err := json.Unmarshal(body, &r); err != nil {
|
|
return nil, fmt.Errorf("parse response gagal: %w", err)
|
|
}
|
|
if r.Metadata.Code != 1 {
|
|
return nil, fmt.Errorf("BPJS error code %d: %s", r.Metadata.Code, r.Metadata.Message)
|
|
}
|
|
return r.Response.List, nil
|
|
}
|
|
|
|
func (c *BpjsClient) post(ctx context.Context, endpoint string, payload interface{}) (int, string, error) {
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return 0, "", err
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+endpoint, bytes.NewReader(body))
|
|
if err != nil {
|
|
return 0, "", err
|
|
}
|
|
for k, v := range c.createHeaders() {
|
|
req.Header.Set(k, v)
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("POST %s gagal: %w", endpoint, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode >= 400 {
|
|
return 0, "", fmt.Errorf("HTTP error: %d - %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
var r bpjsResponse
|
|
if err := json.Unmarshal(respBody, &r); err != nil {
|
|
return 0, "", fmt.Errorf("parse response gagal: %w", err)
|
|
}
|
|
return r.Metadata.Code, r.Metadata.Message, nil
|
|
}
|
|
|
|
// =============================================
|
|
// BPJS OPERATIONS
|
|
// =============================================
|
|
|
|
// BacaKamar membaca daftar kamar yang ada di BPJS
|
|
func (c *BpjsClient) BacaKamar(ctx context.Context, start, limit int) ([]map[string]interface{}, error) {
|
|
endpoint := fmt.Sprintf("aplicaresws/rest/bed/read/%s/%d/%d", c.kodePPK, start, limit)
|
|
raw, err := c.get(ctx, endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var list []map[string]interface{}
|
|
if err := json.Unmarshal(raw, &list); err != nil {
|
|
return nil, err
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
func (c *BpjsClient) PostKamar(ctx context.Context, bed BedData) error {
|
|
payload := map[string]interface{}{
|
|
"kodekelas": bed.KodeKelas,
|
|
"koderuang": bed.KodeRuang,
|
|
"namaruang": bed.NamaRuang,
|
|
"kapasitas": bed.Kapasitas,
|
|
"tersedia": bed.Tersedia,
|
|
"tersediapria": bed.TersediaPria,
|
|
"tersediawanita": bed.TersediaWanita,
|
|
"tersediapriawanita": bed.TersediaPriaWanita,
|
|
}
|
|
|
|
// Coba update dulu
|
|
code, updateMsg, err := c.post(ctx, fmt.Sprintf("aplicaresws/rest/bed/update/%s", c.kodePPK), payload)
|
|
if err == nil && code == 1 {
|
|
return nil
|
|
}
|
|
|
|
// Log kenapa update gagal
|
|
if err != nil {
|
|
fmt.Printf(" [DEBUG] update %s gagal: err=%v\n", bed.KodeRuang, err)
|
|
} else {
|
|
fmt.Printf(" [DEBUG] update %s gagal: code=%d msg=%s\n", bed.KodeRuang, code, updateMsg)
|
|
}
|
|
|
|
// Fallback ke create
|
|
code, msg, err := c.post(ctx, fmt.Sprintf("aplicaresws/rest/bed/create/%s", c.kodePPK), payload)
|
|
if err != nil {
|
|
return fmt.Errorf("create kamar %s gagal: %w", bed.KodeRuang, err)
|
|
}
|
|
if code != 1 {
|
|
// Kalau "sudah ada" → coba update sekali lagi
|
|
if strings.Contains(strings.ToLower(msg), "sudah ada") {
|
|
fmt.Printf(" [DEBUG] create %s sudah ada, coba update lagi\n", bed.KodeRuang)
|
|
code, msg, err = c.post(ctx, fmt.Sprintf("aplicaresws/rest/bed/update/%s", c.kodePPK), payload)
|
|
if err != nil {
|
|
return fmt.Errorf("update ulang %s gagal: %w", bed.KodeRuang, err)
|
|
}
|
|
if code != 1 {
|
|
return fmt.Errorf("update ulang %s: %s", bed.KodeRuang, msg)
|
|
}
|
|
return nil
|
|
}
|
|
return fmt.Errorf("create kamar %s: %s", bed.KodeRuang, msg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HapusKamar hapus kamar dari BPJS
|
|
func (c *BpjsClient) HapusKamar(ctx context.Context, kodekelas, koderuang string) error {
|
|
payload := map[string]string{
|
|
"kodekelas": kodekelas,
|
|
"koderuang": koderuang,
|
|
}
|
|
code, msg, err := c.post(ctx, fmt.Sprintf("aplicaresws/rest/bed/delete/%s", c.kodePPK), payload)
|
|
if err != nil {
|
|
return fmt.Errorf("hapus kamar %s gagal: %w", koderuang, err)
|
|
}
|
|
if code != 1 {
|
|
return fmt.Errorf("hapus kamar %s: %s", koderuang, msg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Flush hapus kamar di BPJS yang tidak ada lagi di SIMRS
|
|
func (c *BpjsClient) Flush(ctx context.Context, currentBeds []BedData) (int, []string) {
|
|
activeRooms := make(map[string]bool, len(currentBeds))
|
|
for _, b := range currentBeds {
|
|
activeRooms[b.KodeRuang] = true
|
|
}
|
|
|
|
bpjsKamars, err := c.BacaKamar(ctx, 1, 200)
|
|
if err != nil {
|
|
return 0, []string{fmt.Sprintf("BacaKamar flush gagal (tidak fatal): %v", err)}
|
|
}
|
|
|
|
flushed := 0
|
|
var errs []string
|
|
for _, kamar := range bpjsKamars {
|
|
kodeRuang, _ := kamar["koderuang"].(string)
|
|
kodeKelas, _ := kamar["kodekelas"].(string)
|
|
if !activeRooms[kodeRuang] {
|
|
if err := c.HapusKamar(ctx, kodeKelas, kodeRuang); err != nil {
|
|
errs = append(errs, fmt.Sprintf("hapus %s gagal: %v", kodeRuang, err))
|
|
continue
|
|
}
|
|
flushed++
|
|
}
|
|
}
|
|
return flushed, errs
|
|
}
|
|
|
|
// GetRefKelas membaca referensi kelas dari BPJS
|
|
func (c *BpjsClient) GetRefKelas(ctx context.Context) ([]map[string]interface{}, error) {
|
|
raw, err := c.get(ctx, "aplicaresws/rest/ref/kelas")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var list []map[string]interface{}
|
|
if err := json.Unmarshal(raw, &list); err != nil {
|
|
return nil, err
|
|
}
|
|
return list, nil
|
|
}
|