package viva import ( "bytes" "encoding/base64" "encoding/json" "errors" "github.com/google/uuid" "github.com/jmoiron/sqlx" "io" "log" "net/http" "net/url" "payment-poc/state" "strconv" "strings" "time" ) type Service struct { DB *sqlx.DB Token string Expiration time.Time ClientId string ClientSecret string SourceCode string MerchantId string ApiKey string } func (s *Service) OAuthToken() (string, error) { if s.Token != "" && s.Expiration.After(time.Now()) { return s.Token, nil } return s.fetchOAuthToken() } func (s *Service) CreatePaymentOrder(entry VivaDb) (VivaDb, error) { token, err := s.OAuthToken() if err != nil { return VivaDb{}, err } orderRequest := VivaOrderRequest{ Amount: entry.TotalAmount, Description: "Example payment", MerchantDescription: "Example payment", PreAuth: true, AllowRecurring: false, Source: s.SourceCode, } content, err := json.Marshal(&orderRequest) if err != nil { return VivaDb{}, err } request, err := http.NewRequest("POST", "https://demo-api.vivapayments.com/checkout/v2/orders", bytes.NewReader(content)) request.Header.Add("authorization", "Bearer "+token) request.Header.Add("content-type", "application/json") response, err := http.DefaultClient.Do(request) if err != nil { return VivaDb{}, err } if response.StatusCode == http.StatusOK { orderResponse := VivaOrderResponse{} content, err := io.ReadAll(response.Body) if err != nil { return VivaDb{}, err } if err := json.Unmarshal(content, &orderResponse); err != nil { return VivaDb{}, err } else { entry.OrderId = string(orderResponse.OrderId) return entry, nil } } else { return VivaDb{}, errors.New("received wrong status, expected 200 received " + strconv.FormatInt(int64(response.StatusCode), 10)) } } func (s *Service) CreateEntry(totalAmount int64) (VivaDb, error) { id := uuid.Must(uuid.NewRandom()) entry := VivaDb{ Id: id, TotalAmount: totalAmount, State: state.StateInitialized, } _, err := s.DB.Exec(`INSERT INTO "viva" ("id", "total_amount", "payment_state") VALUES ($1, $2, $3)`, &entry.Id, &entry.TotalAmount, &entry.State, ) if err != nil { return VivaDb{}, err } return s.FetchById(id) } func (s *Service) FetchAll() ([]VivaDb, error) { var entries []VivaDb err := s.DB.Select(&entries, `SELECT * FROM "viva"`) return entries, err } func (s *Service) FetchById(id uuid.UUID) (VivaDb, error) { entry := VivaDb{} err := s.DB.Get(&entry, `SELECT * FROM "viva" WHERE "id" = $1`, id) return entry, err } func (s *Service) FetchByOrderId(id OrderId) (VivaDb, error) { entry := VivaDb{} err := s.DB.Get(&entry, `SELECT * FROM "viva" WHERE "order_id" = $1`, string(id)) return entry, err } func (s *Service) Update(entry VivaDb) error { _, err := s.DB.Exec(`UPDATE "viva" set "order_id" = $2, "transaction_id" = $3, "payment_state" = $4 WHERE "id" = $1`, &entry.Id, &entry.OrderId, &entry.TransactionId, &entry.State, ) return err } func (s *Service) fetchOAuthToken() (string, error) { form := url.Values{ "grant_type": []string{"client_credentials"}, } request, err := http.NewRequest("POST", "https://demo-accounts.vivapayments.com/connect/token", strings.NewReader(form.Encode())) if err != nil { return "", err } request.Header.Add("content-type", "application/x-www-form-urlencoded") request.SetBasicAuth(s.ClientId, s.ClientSecret) response, err := http.DefaultClient.Do(request) if err != nil { return "", err } if response.StatusCode == http.StatusOK { oauthObject := VivaOAuthResponse{} content, err := io.ReadAll(response.Body) if err != nil { return "", err } if err := json.Unmarshal(content, &oauthObject); err != nil { return "", err } else { s.Token = oauthObject.AccessToken s.Expiration = time.Now().Add(time.Duration(oauthObject.ExpiresIn) * time.Second) } } else { return "", errors.New("received wrong status, expected 200 received " + strconv.FormatInt(int64(response.StatusCode), 10)) } return s.Token, nil } func (s *Service) CompleteTransaction(entry VivaDb, amount int64) (VivaDb, error) { completionRequest := VivaTransactionCompleteRequest{ Amount: amount, CustomerDescription: "Example transaction", } content, err := json.Marshal(&completionRequest) if err != nil { return VivaDb{}, err } request, err := http.NewRequest("POST", "https://demo.vivapayments.com/api/transactions/"+entry.TransactionId.String(), bytes.NewReader(content)) request.Header.Add("authorization", "Bearer "+s.BasicAuth()) request.Header.Add("content-type", "application/json") response, err := http.DefaultClient.Do(request) if err != nil { return VivaDb{}, err } if response.StatusCode == http.StatusOK { transactionResponse := VivaTransactionResponse{} content, err := io.ReadAll(response.Body) if err != nil { return VivaDb{}, err } if err := json.Unmarshal(content, &transactionResponse); err != nil { return VivaDb{}, err } else { log.Printf("Received transaction response: success=%v, eventId=%d, status=%s, amount=%f, errorCode=%d, errorText=%s", transactionResponse.Success, transactionResponse.EventId, transactionResponse.StatusId, transactionResponse.Amount, transactionResponse.ErrorCode, transactionResponse.ErrorText, ) if transactionResponse.StatusId == "F" { entry.TotalAmount = int64(transactionResponse.Amount * 100) entry.State = state.StateCompleted } else { return VivaDb{}, errors.New("received invalid status = " + transactionResponse.StatusId) } } } else { return VivaDb{}, errors.New("received wrong status, expected 200 received " + strconv.FormatInt(int64(response.StatusCode), 10)) } return entry, nil } func (s *Service) BasicAuth() string { return base64.StdEncoding.EncodeToString([]byte(s.MerchantId + ":" + s.ApiKey)) } func (s *Service) CancelTransaction(entry VivaDb) (VivaDb, error) { request, err := http.NewRequest("DELETE", "https://demo.vivapayments.com/api/transactions/"+entry.TransactionId.String()+"?amount="+strconv.FormatInt(entry.TotalAmount, 10), bytes.NewReader([]byte{})) request.Header.Add("authorization", "Bearer "+s.BasicAuth()) response, err := http.DefaultClient.Do(request) if err != nil { return VivaDb{}, err } if response.StatusCode == http.StatusOK { transactionResponse := VivaTransactionResponse{} content, err := io.ReadAll(response.Body) if err != nil { return VivaDb{}, err } if err := json.Unmarshal(content, &transactionResponse); err != nil { return VivaDb{}, err } else { log.Printf("Received transaction response: success=%v, eventId=%d, status=%s, amount=%f, errorCode=%d, errorText=%s", transactionResponse.Success, transactionResponse.EventId, transactionResponse.StatusId, transactionResponse.Amount, transactionResponse.ErrorCode, transactionResponse.ErrorText, ) if transactionResponse.StatusId == "F" { entry.State = state.StateVoided } else { return VivaDb{}, errors.New("received invalid status = " + transactionResponse.StatusId) } } } else { return VivaDb{}, errors.New("received wrong status, expected 200 received " + strconv.FormatInt(int64(response.StatusCode), 10)) } return entry, nil }