Added viva wallet support + completion on stripe

This commit is contained in:
Borna Rajković committed 2023-07-27 10:11:40 +02:00
1 parent cefc5314f2
commit 9440fa9778
9 files changed
+637 -11

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
package viva
import (
"github.com/google/uuid"
"payment-poc/state"
"time"
)
const VivaUrl = "https://demo-api.vivapayments.com"
type VivaDb struct {
Id uuid.UUID `db:"id"`
OrderId string `db:"order_id"`
TransactionId uuid.UUID `db:"transaction_id"`
TotalAmount int64 `db:"total_amount"`
Lang string `db:"lang"`
EventId string `db:"event_id"`
ECI string `db:"eci"`
DateTime time.Time `db:"date_time"`
// transaction response
State state.PaymentState `db:"payment_state"`
}
+235
View File
@@ -0,0 +1,235 @@
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
}
+44
View File
@@ -0,0 +1,44 @@
package viva
type OrderId string
func (o OrderId) MarshalJSON() ([]byte, error) {
return []byte(o), nil
}
func (o *OrderId) UnmarshalJSON(value []byte) error {
*o = OrderId(value)
return nil
}
type VivaOrderRequest struct {
Amount int64 `json:"amount"`
Description string `json:"customerTrns"`
MerchantDescription string `json:"merchantTrns"`
PreAuth bool `json:"preauth"`
AllowRecurring bool `json:"allowRecurring"`
Source string `json:"sourceCode"`
}
type VivaOrderResponse struct {
OrderId OrderId `json:"orderCode"`
}
type VivaOAuthResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
type VivaTransactionCompleteRequest struct {
Amount int64 `json:"amount"`
CustomerDescription string `json:"customerTrns"`
}
type VivaTransactionResponse struct {
Amount float64 `json:"Amount"`
StatusId string `json:"StatusId"`
ErrorCode int64 `json:"ErrorCode"`
ErrorText string `json:"ErrorText"`
EventId int64 `json:"EventId"`
Success bool `json:"Success"`
}