236 lines
7.2 KiB
Go
236 lines
7.2 KiB
Go
|
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
|
||
|
}
|