From 667d2ce2f4582387ebdd582d89402b02c4b8deca Mon Sep 17 00:00:00 2001 From: AmirMahdi Qiasvand Date: Sun, 14 Sep 2025 14:28:23 +0330 Subject: [PATCH] feat: add zohal pkg and http util --- config/config.go | 15 +++++++---- pkg/util/http.go | 57 +++++++++++++++++++++++++++++++++++++++++ pkg/zohal/types.go | 50 ++++++++++++++++++++++++++++++++++++ pkg/zohal/zohal.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 pkg/util/http.go create mode 100644 pkg/zohal/types.go create mode 100644 pkg/zohal/zohal.go diff --git a/config/config.go b/config/config.go index fd9410a..98da692 100644 --- a/config/config.go +++ b/config/config.go @@ -1,12 +1,12 @@ package config type Config struct { - Server Server `mapstructure:"server"` - JWT JWT `mapstructure:"jwt"` - DB DB `mapstructure:"db"` - Redis Redis `mapstructure:"redis"` + Server Server `mapstructure:"server"` + JWT JWT `mapstructure:"jwt"` + DB DB `mapstructure:"db"` + Redis Redis `mapstructure:"redis"` + KYCProvider KYC `mapstructure:"kyc"` } - type Server struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` @@ -31,3 +31,8 @@ type JWT struct { RefreshTokenExpMinutes uint `mapstructure:"refresh_token_exp_minutes"` TokenSecret string `mapstructure:"token_secret"` } + +type KYC struct { + APIKey string `mapstructure:"api_key"` + URL string `mapstructure:"url"` +} diff --git a/pkg/util/http.go b/pkg/util/http.go new file mode 100644 index 0000000..f83f0e0 --- /dev/null +++ b/pkg/util/http.go @@ -0,0 +1,57 @@ +package util + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "sync" +) + +type HttpClient struct { + client *http.Client + once sync.Once +} + +func NewHttpClient() *HttpClient { + return &HttpClient{ + http.DefaultClient, + sync.Once{}, + } +} + +func (h *HttpClient) getClient() *http.Client { + h.once.Do(func() { + h.client = &http.Client{} + }) + return h.client +} + +func (h *HttpClient) HttpRequest(ctx context.Context, method, url string, body interface{}, headers map[string]string) (*http.Response, error) { + payload, err := json.Marshal(body) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + + for headerTitle, headerValue := range headers { + req.Header.Set(headerTitle, headerValue) + } + + resp, err := h.getClient().Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + return &http.Response{ + Status: "200 ok", + StatusCode: 200, + Body: resp.Body, + }, nil +} diff --git a/pkg/zohal/types.go b/pkg/zohal/types.go new file mode 100644 index 0000000..97f38ba --- /dev/null +++ b/pkg/zohal/types.go @@ -0,0 +1,50 @@ +package zohal + +type respMatched struct { + Matched bool `json:"matched"` +} +type personIdentity struct { + Matched bool `json:"matched"` + LastName string `json:"last_name"` + FirstName string `json:"first_name"` + FatherName string `json:"father_name"` + NationalCode string `json:"national_code"` + IsDead bool `json:"is_dead"` + Alive bool `json:"alive"` +} +type respBody[T any] struct { + Data T `json:"data"` + Message string `json:"message"` +} +type zohalResp[T any] struct { + StatusCode int `json:"status_code"` + Body respBody[T] `json:"response_body"` +} + +type ( + // https://service.zohal.io/api/v0/services/inquiry/shahkar + ZohalShahkarReq struct { + Phone string `json:"mobile"` + NationalID string `json:"national_code"` + } + ZohalShahkarResp struct { + zohalResp[respMatched] + } + // https://service.zohal.io/api/v0/services/inquiry/national_identity_inquiry + ZohalIdentityReq struct { + NationalID string `json:"national_code"` + BirthDate string `json:"birth_date"` + } + ZohalIdentityResp struct { + zohalResp[personIdentity] + } + // https://service.zohal.io/api/v0/services/inquiry/check_iban_with_national_code + ZohalIbanReq struct { + NationalID string `json:"national_code"` + IBAN string `json:"IBAN"` + BirthDate string `json:"birth_date"` + } + ZohalIbanResp struct { + zohalResp[respMatched] + } +) diff --git a/pkg/zohal/zohal.go b/pkg/zohal/zohal.go new file mode 100644 index 0000000..b90f37c --- /dev/null +++ b/pkg/zohal/zohal.go @@ -0,0 +1,63 @@ +package zohal + +import ( + "backend/config" + "backend/pkg/util" + "backend/pkg/validate/common" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" +) + +type Zohal struct { + cfg config.KYC +} + +func (z *Zohal) Shahkar(ctx context.Context, phone, nationalID string) (ZohalShahkarResp, error) { + var req ZohalShahkarReq + var resp ZohalShahkarResp + ok, err := common.IsValidPhone(phone) + if !ok { + return resp, err + } + + ok, err = common.IsValidNationalID(nationalID) + if !ok { + return resp, err + } + + header := make(map[string]string) + header["Content-Type"] = "application/json" + header["Accept"] = "application/json" + header["Authorization"] = fmt.Sprintf("Bearer %s", z.cfg.APIKey) + + req.Phone = phone + req.NationalID = nationalID + + u, err := url.Parse(z.cfg.URL) + if err != nil { + return resp, err + } + u = u.JoinPath("inquiry", "shahkar") + + client := util.NewHttpClient() + clientResp, err := client.HttpRequest(ctx, http.MethodPost, u.String(), req, header) + if err != nil { + return resp, err + } + + bodyBytes, err := io.ReadAll(clientResp.Body) + if err != nil { + return resp, err + } + + err = json.Unmarshal(bodyBytes, &resp) + if err != nil { + return resp, err + } + + return resp, err +}