Compare commits
5 Commits
b4b0396b30
...
7b2523e2b1
Author | SHA1 | Date |
---|---|---|
Borna Rajković | 7b2523e2b1 | |
Borna Rajković | fe6f3b6672 | |
Borna Rajković | b6203d8a03 | |
Borna Rajković | 1307c7bc6e | |
Borna Rajković | dcc9754d43 |
|
@ -0,0 +1,54 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"payment-poc/state"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PaymentEntry struct {
|
||||
Id uuid.UUID `db:"id"`
|
||||
|
||||
Created time.Time `db:"created"`
|
||||
Modified *time.Time `db:"modified"`
|
||||
|
||||
Gateway state.PaymentGateway `db:"gateway"`
|
||||
State state.PaymentState `db:"state"`
|
||||
|
||||
Lang *string `db:"lang"`
|
||||
|
||||
Error *string `db:"error"`
|
||||
|
||||
// paid amount
|
||||
Amount *int64 `db:"amount"`
|
||||
// preauthorized amount
|
||||
TotalAmount int64 `db:"total_amount"`
|
||||
|
||||
// used for wspay and viva
|
||||
ECI *string `db:"eci"`
|
||||
|
||||
// stripe field
|
||||
PaymentIntentId *string `db:"payment_intent_id"`
|
||||
|
||||
// wspay field
|
||||
ShoppingCardID *string `db:"shopping_card_id"`
|
||||
STAN *string `db:"stan"`
|
||||
Success *int `db:"success"`
|
||||
ApprovalCode *string `db:"approval_code"`
|
||||
|
||||
// viva field
|
||||
OrderId *OrderId `db:"order_id"`
|
||||
TransactionId *uuid.UUID `db:"transaction_id"`
|
||||
EventId *string `db:"event_id"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PaymentEntryProvider struct {
|
||||
DB *sqlx.DB
|
||||
}
|
||||
|
||||
func (p *PaymentEntryProvider) CreateEntry(entry PaymentEntry) (PaymentEntry, error) {
|
||||
if entry.Id == uuid.Nil {
|
||||
entry.Id = uuid.Must(uuid.NewRandom())
|
||||
}
|
||||
entry.Created = time.Now()
|
||||
|
||||
_, err := p.DB.Exec(`INSERT INTO "payment_entry" ("id", "created", "gateway", "state", "lang", "error", "amount", "total_amount", "eci", "payment_intent_id", "shopping_card_id", "stan", "success", "approval_code", "order_id", "transaction_id", "event_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)`,
|
||||
&entry.Id, &entry.Created, &entry.Gateway, &entry.State, &entry.Lang, &entry.Error, &entry.Amount, &entry.TotalAmount, &entry.ECI, &entry.PaymentIntentId, &entry.ShoppingCardID, &entry.STAN, &entry.Success, &entry.ApprovalCode, &entry.OrderId, &entry.TransactionId, &entry.EventId,
|
||||
)
|
||||
if err != nil {
|
||||
return PaymentEntry{}, err
|
||||
}
|
||||
return p.FetchById(entry.Id)
|
||||
}
|
||||
|
||||
func (p *PaymentEntryProvider) UpdateEntry(entry PaymentEntry) (PaymentEntry, error) {
|
||||
currentTime := time.Now()
|
||||
entry.Modified = ¤tTime
|
||||
|
||||
_, err := p.DB.Exec(`UPDATE "payment_entry" SET "modified" = $2, "state" = $3, "lang" = $4, "error" = $5, "amount" = $6, "eci" = $7, "payment_intent_id" = $8, "shopping_card_id" = $9, "stan" = $10, "success" = $11, "approval_code" = $12, "order_id" = $13, "transaction_id" = $14, "event_id" = $15 WHERE "id" = $1`,
|
||||
&entry.Id, &entry.Modified, &entry.State, &entry.Lang, &entry.Error, &entry.Amount, &entry.ECI, &entry.PaymentIntentId, &entry.ShoppingCardID, &entry.STAN, &entry.Success, &entry.ApprovalCode, &entry.OrderId, &entry.TransactionId, &entry.EventId,
|
||||
)
|
||||
if err != nil {
|
||||
return PaymentEntry{}, err
|
||||
}
|
||||
return p.FetchById(entry.Id)
|
||||
}
|
||||
|
||||
func (p *PaymentEntryProvider) FetchById(id uuid.UUID) (PaymentEntry, error) {
|
||||
entry := PaymentEntry{}
|
||||
err := p.DB.Get(&entry, `SELECT * FROM "payment_entry" WHERE "id" = $1`, id)
|
||||
return entry, err
|
||||
}
|
||||
|
||||
func (p *PaymentEntryProvider) FetchAll() ([]PaymentEntry, error) {
|
||||
var entries []PaymentEntry
|
||||
err := p.DB.Select(&entries, `SELECT * FROM "payment_entry"`)
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (p *PaymentEntryProvider) FetchByOrderId(orderId OrderId) (PaymentEntry, error) {
|
||||
entry := PaymentEntry{}
|
||||
err := p.DB.Get(&entry, `SELECT * FROM "payment_entry" WHERE "order_id" = $1`, orderId)
|
||||
return entry, err
|
||||
}
|
|
@ -1,65 +1,30 @@
|
|||
|
||||
CREATE TABLE IF NOT EXISTS "wspay"
|
||||
CREATE TABLE IF NOT EXISTS "payment_entry"
|
||||
(
|
||||
"id" uuid NOT NULL,
|
||||
"shop_id" varchar(128) NOT NULL,
|
||||
"shopping_card_id" varchar(128) NOT NULL,
|
||||
"created" timestamp NOT NULL,
|
||||
"modified" timestamp DEFAULT NULL,
|
||||
|
||||
"gateway" varchar(255) NOT NULL,
|
||||
"state" varchar(255) NOT NULL,
|
||||
|
||||
"lang" varchar(16) DEFAULT NULL,
|
||||
"error" varchar(255) DEFAULT NULL,
|
||||
|
||||
"amount" int DEFAULT NULL,
|
||||
"total_amount" int NOT NULL,
|
||||
|
||||
"lang" varchar(128) DEFAULT '',
|
||||
"eci" varchar(255) DEFAULT NULL,
|
||||
|
||||
"customer_first_name" varchar(128) DEFAULT '',
|
||||
"customer_last_name" varchar(128) DEFAULT '',
|
||||
"customer_address" varchar(128) DEFAULT '',
|
||||
"customer_city" varchar(128) DEFAULT '',
|
||||
"customer_zip" varchar(128) DEFAULT '',
|
||||
"customer_country" varchar(128) DEFAULT '',
|
||||
"customer_phone" varchar(128) DEFAULT '',
|
||||
"payment_intent_id" varchar(255) DEFAULT NULL,
|
||||
|
||||
"payment_plan" varchar(128) DEFAULT '',
|
||||
"credit_card_name" varchar(128) DEFAULT '',
|
||||
"credit_card_number" varchar(128) DEFAULT '',
|
||||
"payment_method" varchar(128) DEFAULT '',
|
||||
"currency_code" int DEFAULT 0,
|
||||
"shopping_card_id" varchar(255) DEFAULT NULL,
|
||||
"stan" varchar(255) DEFAULT NULL,
|
||||
"success" int DEFAULT NULL,
|
||||
"approval_code" varchar(255) DEFAULT NULL,
|
||||
|
||||
"date_time" timestamp DEFAULT current_timestamp,
|
||||
|
||||
"eci" varchar(256) DEFAULT '',
|
||||
"stan" varchar(256) DEFAULT '',
|
||||
|
||||
"success" int DEFAULT 0,
|
||||
"approval_code" varchar(256) DEFAULT '',
|
||||
"error_message" varchar(256) DEFAULT '',
|
||||
"error_codes" varchar(256) DEFAULT '',
|
||||
|
||||
"payment_state" varchar(256) DEFAULT '',
|
||||
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT unique_id UNIQUE ("shopping_card_id")
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "stripe"
|
||||
(
|
||||
"id" uuid NOT NULL,
|
||||
"total_amount" int NOT NULL,
|
||||
"lang" varchar(128) DEFAULT '',
|
||||
"payment_intent_id" varchar(256) DEFAULT '',
|
||||
"payment_state" varchar(256) DEFAULT '',
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "viva"
|
||||
(
|
||||
"id" uuid NOT NULL,
|
||||
"order_id" varchar(24) DEFAULT '',
|
||||
"order_id" varchar(255) DEFAULT NULL,
|
||||
"transaction_id" uuid DEFAULT NULL,
|
||||
"total_amount" int NOT NULL,
|
||||
"event_id" varchar(128) DEFAULT '',
|
||||
"eci" varchar(128) DEFAULT '',
|
||||
"payment_state" varchar(256) DEFAULT '',
|
||||
"event_id" varchar(255) DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package prod
|
|
@ -0,0 +1,44 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"payment-poc/database"
|
||||
"payment-poc/state"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
BackendUrl string
|
||||
}
|
||||
|
||||
func (s Service) UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error) {
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s Service) CreatePaymentUrl(entry database.PaymentEntry) (updateEntry database.PaymentEntry, url string, err error) {
|
||||
return entry, "/mock/gateway/" + entry.Id.String(), nil
|
||||
}
|
||||
|
||||
func (s Service) CompleteTransaction(entry database.PaymentEntry, amount int64) (database.PaymentEntry, error) {
|
||||
entry.Amount = &amount
|
||||
entry.State = state.StateCompleted
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s Service) CancelTransaction(entry database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
entry.State = state.StateVoided
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s Service) HandleResponse(c *gin.Context, provider *database.PaymentEntryProvider, paymentState state.PaymentState) (string, error) {
|
||||
id := uuid.MustParse(c.Query("id"))
|
||||
entry, err := provider.FetchById(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
entry.State = paymentState
|
||||
_, err = provider.UpdateEntry(entry)
|
||||
log.Printf("[%s:%s] received authorization response", entry.Id.String(), entry.State)
|
||||
return "/entries/" + id.String(), err
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package stripe
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stripe/stripe-go/v72"
|
||||
"github.com/stripe/stripe-go/v72/checkout/session"
|
||||
"github.com/stripe/stripe-go/v72/paymentintent"
|
||||
"log"
|
||||
"payment-poc/database"
|
||||
"payment-poc/state"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
ApiKey string
|
||||
BackendUrl string
|
||||
}
|
||||
|
||||
func (s *Service) UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error) {
|
||||
pi, err := paymentintent.Get(*entry.PaymentIntentId, nil)
|
||||
if err != nil {
|
||||
return entry, err
|
||||
}
|
||||
newState := determineState(pi.Status)
|
||||
|
||||
if entry.State != newState && newState != "" {
|
||||
log.Printf("[%s] updated state for %s -> %s", entry.Id.String(), entry.State, newState)
|
||||
if pi.AmountReceived > 0 {
|
||||
entry.Amount = &pi.AmountReceived
|
||||
}
|
||||
entry.State = newState
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func determineState(status stripe.PaymentIntentStatus) state.PaymentState {
|
||||
switch status {
|
||||
case stripe.PaymentIntentStatusCanceled:
|
||||
return state.StateCanceled
|
||||
case stripe.PaymentIntentStatusProcessing:
|
||||
return state.StatePending
|
||||
case stripe.PaymentIntentStatusRequiresAction:
|
||||
return state.StatePending
|
||||
case stripe.PaymentIntentStatusRequiresCapture:
|
||||
return state.StateAccepted
|
||||
case stripe.PaymentIntentStatusRequiresConfirmation:
|
||||
return state.StatePending
|
||||
case stripe.PaymentIntentStatusRequiresPaymentMethod:
|
||||
return state.StateVoided
|
||||
case stripe.PaymentIntentStatusSucceeded:
|
||||
return state.StateCompleted
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Service) CreatePaymentUrl(entry database.PaymentEntry) (database.PaymentEntry, string, error) {
|
||||
entry, url, err := s.InitializePayment(entry)
|
||||
if err != nil {
|
||||
return entry, "", err
|
||||
}
|
||||
return entry, url, nil
|
||||
}
|
||||
|
||||
func (s *Service) InitializePayment(entry database.PaymentEntry) (database.PaymentEntry, string, error) {
|
||||
|
||||
currency := string(stripe.CurrencyEUR)
|
||||
productName := "Example product"
|
||||
productDescription := "Simple example product"
|
||||
|
||||
params := &stripe.CheckoutSessionParams{
|
||||
LineItems: []*stripe.CheckoutSessionLineItemParams{
|
||||
{
|
||||
PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
|
||||
Currency: ¤cy,
|
||||
ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
|
||||
Name: &productName,
|
||||
Description: &productDescription,
|
||||
},
|
||||
UnitAmount: &entry.TotalAmount,
|
||||
},
|
||||
Quantity: stripe.Int64(1),
|
||||
},
|
||||
},
|
||||
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
||||
PaymentIntentData: &stripe.CheckoutSessionPaymentIntentDataParams{
|
||||
CaptureMethod: stripe.String("manual"),
|
||||
},
|
||||
SuccessURL: stripe.String(s.BackendUrl + "/stripe/success?token=" + entry.Id.String()),
|
||||
CancelURL: stripe.String(s.BackendUrl + "/stripe/cancel?token=" + entry.Id.String()),
|
||||
}
|
||||
result, err := session.New(params)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, "", err
|
||||
}
|
||||
entry.State = state.StateInitialized
|
||||
entry.PaymentIntentId = &result.PaymentIntent.ID
|
||||
|
||||
return entry, result.URL, nil
|
||||
}
|
||||
|
||||
func (s *Service) CompleteTransaction(entry database.PaymentEntry, amount int64) (database.PaymentEntry, error) {
|
||||
params := &stripe.PaymentIntentCaptureParams{
|
||||
AmountToCapture: stripe.Int64(amount),
|
||||
}
|
||||
pi, err := paymentintent.Capture(*entry.PaymentIntentId, params)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
log.Printf("received state on completion: %v", pi.Status)
|
||||
newState := determineState(pi.Status)
|
||||
entry.State = newState
|
||||
if newState == state.StateCompleted || newState == state.StatePending {
|
||||
entry.Amount = &pi.AmountReceived
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Service) CancelTransaction(entry database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
params := &stripe.PaymentIntentCancelParams{}
|
||||
pi, err := paymentintent.Cancel(*entry.PaymentIntentId, params)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
log.Printf("received state on completion: %v", pi.Status)
|
||||
if pi.Status == stripe.PaymentIntentStatusCanceled {
|
||||
entry.State = state.StateCanceled
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Service) HandleResponse(c *gin.Context, provider *database.PaymentEntryProvider, paymentState state.PaymentState) (string, error) {
|
||||
id := uuid.MustParse(c.Query("token"))
|
||||
entry, err := provider.FetchById(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
entry.State = paymentState
|
||||
if _, err := provider.UpdateEntry(entry); err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Printf("[%s:%s] received authorization response", entry.Id.String(), entry.State)
|
||||
return "/entries/" + entry.Id.String(), nil
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
package viva
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"payment-poc/database"
|
||||
"payment-poc/state"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
ClientId string
|
||||
ClientSecret string
|
||||
SourceCode string
|
||||
|
||||
MerchantId string
|
||||
ApiKey string
|
||||
|
||||
token string
|
||||
expiration time.Time
|
||||
}
|
||||
|
||||
func (s *Service) UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error) {
|
||||
token, err := s.oAuthToken()
|
||||
httpResponse, err := createRequest(
|
||||
"GET",
|
||||
"https://demo-api.vivapayments.com/checkout/v2/transactions/"+entry.TransactionId.String(),
|
||||
map[string]string{"authorization": "Bearer " + token, "content-type": "application/json"},
|
||||
[]byte{},
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response TransactionStatusResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
newState := determineStatus(response.StatusId)
|
||||
|
||||
if entry.State != newState && newState != "" {
|
||||
log.Printf("[%s:%s] updated state %s -> %s", entry.Id.String(), entry.State, entry.State, newState)
|
||||
entry.State = newState
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func determineStatus(id TransactionStatus) state.PaymentState {
|
||||
switch id {
|
||||
case PaymentPreauthorized:
|
||||
return state.StateAccepted
|
||||
case PaymentPending:
|
||||
return state.StatePending
|
||||
case PaymentSuccessful:
|
||||
return state.StateCompleted
|
||||
case PaymentUnsuccessful:
|
||||
return state.StateError
|
||||
case PaymentRefunded:
|
||||
return state.StateVoided
|
||||
case PaymentVoided:
|
||||
return state.StateVoided
|
||||
}
|
||||
log.Printf("Unknonw transactionStatus: %s", string(id))
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Service) CreatePaymentUrl(entry database.PaymentEntry) (database.PaymentEntry, string, error) {
|
||||
entry, err := s.InitializePayment(entry)
|
||||
if err != nil {
|
||||
return entry, "", err
|
||||
}
|
||||
return entry, "https://demo.vivapayments.com/web/checkout?ref=" + string(*entry.OrderId), nil
|
||||
}
|
||||
|
||||
func (s *Service) InitializePayment(entry database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
token, err := s.oAuthToken()
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
request := OrderRequest{
|
||||
Amount: entry.TotalAmount,
|
||||
Description: "Example payment",
|
||||
MerchantDescription: "Example payment",
|
||||
PreAuth: true,
|
||||
AllowRecurring: false,
|
||||
Source: s.SourceCode,
|
||||
}
|
||||
|
||||
httpResponse, err := createRequest(
|
||||
"POST",
|
||||
"https://demo-api.vivapayments.com/checkout/v2/orders",
|
||||
map[string]string{"authorization": "Bearer " + token, "content-type": "application/json"},
|
||||
toJson(request),
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response OrderResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
entry.State = state.StateInitialized
|
||||
entry.OrderId = &response.OrderId
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Service) CompleteTransaction(entry database.PaymentEntry, amount int64) (database.PaymentEntry, error) {
|
||||
completionRequest := TransactionCompleteRequest{
|
||||
Amount: amount,
|
||||
CustomerDescription: "Example transaction",
|
||||
}
|
||||
httpResponse, err := createRequest(
|
||||
"POST",
|
||||
"https://demo.vivapayments.com/api/transactions/"+entry.TransactionId.String(),
|
||||
map[string]string{"authorization": "Bearer " + s.basicAuth(),
|
||||
"content-type": "application/json",
|
||||
},
|
||||
toJson(completionRequest),
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response TransactionResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
if response.StatusId == "F" {
|
||||
paidAmount := int64(response.Amount * 100)
|
||||
entry.Amount = &paidAmount
|
||||
entry.State = state.StateCompleted
|
||||
} else {
|
||||
return database.PaymentEntry{}, errors.New("received invalid status = " + response.StatusId)
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Service) CancelTransaction(entry database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
httpResponse, err := createRequest(
|
||||
"DELETE",
|
||||
"https://demo.vivapayments.com/api/transactions/"+entry.TransactionId.String(),
|
||||
map[string]string{"authorization": "Bearer " + s.basicAuth(),
|
||||
"content-type": "application/json",
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response TransactionResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
if response.StatusId == "F" {
|
||||
paidAmount := int64(0)
|
||||
entry.Amount = &paidAmount
|
||||
entry.State = state.StateVoided
|
||||
} else {
|
||||
return database.PaymentEntry{}, errors.New("received invalid status = " + response.StatusId)
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Service) oAuthToken() (string, error) {
|
||||
if s.token != "" && s.expiration.After(time.Now()) {
|
||||
return s.token, nil
|
||||
}
|
||||
return s.fetchOAuthToken()
|
||||
}
|
||||
|
||||
func readResponse[T any](httpResponse *http.Response, response T) error {
|
||||
if httpResponse.StatusCode == http.StatusOK {
|
||||
content, err := io.ReadAll(httpResponse.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(content, response)
|
||||
} else {
|
||||
return errors.New("received wrong status, expected 200 received " + strconv.FormatInt(int64(httpResponse.StatusCode), 10))
|
||||
}
|
||||
}
|
||||
|
||||
func createRequest(method string, url string, headers map[string]string, content []byte) (*http.Response, error) {
|
||||
request, err := http.NewRequest(method, url, bytes.NewReader(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range headers {
|
||||
request.Header.Add(key, value)
|
||||
}
|
||||
return http.DefaultClient.Do(request)
|
||||
}
|
||||
|
||||
func toJson[T any](request T) []byte {
|
||||
response, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func (s *Service) fetchOAuthToken() (string, error) {
|
||||
form := url.Values{
|
||||
"grant_type": []string{"client_credentials"},
|
||||
}
|
||||
httpResponse, err := createRequest(
|
||||
"POST",
|
||||
"https://demo-accounts.vivapayments.com/connect/token",
|
||||
map[string]string{"content-type": "application/x-www-form-urlencoded", "authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte(s.ClientId+":"+s.ClientSecret))},
|
||||
[]byte(form.Encode()),
|
||||
)
|
||||
var response OAuthResponse
|
||||
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.token = response.AccessToken
|
||||
s.expiration = time.Now().Add(time.Duration(response.ExpiresIn) * time.Second)
|
||||
|
||||
return s.token, nil
|
||||
}
|
||||
|
||||
func (s *Service) basicAuth() string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(s.MerchantId + ":" + s.ApiKey))
|
||||
}
|
||||
|
||||
func (s *Service) HandleResponse(c *gin.Context, provider *database.PaymentEntryProvider, state state.PaymentState) (string, error) {
|
||||
transactionId := uuid.MustParse(c.Query("t"))
|
||||
orderId := database.OrderId(c.Query("s"))
|
||||
lang := c.Query("lang")
|
||||
eventId := c.Query("eventId")
|
||||
eci := c.Query("eci")
|
||||
|
||||
log.Printf("[%s] received error response for viva payment", orderId)
|
||||
entry, err := provider.FetchByOrderId(orderId)
|
||||
if err != nil {
|
||||
log.Printf("[%s] couldn't find payment info for viva payment", orderId)
|
||||
return "", err
|
||||
}
|
||||
|
||||
entry.State = state
|
||||
entry.ECI = &eci
|
||||
entry.Lang = &lang
|
||||
entry.EventId = &eventId
|
||||
entry.TransactionId = &transactionId
|
||||
|
||||
if _, err := provider.UpdateEntry(entry); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Printf("[%s:%s] received authorization response", entry.Id.String(), entry.State)
|
||||
|
||||
return "/entries/" + entry.Id.String(), nil
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package viva
|
||||
|
||||
import "payment-poc/database"
|
||||
|
||||
type OrderRequest 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 OrderResponse struct {
|
||||
OrderId database.OrderId `json:"orderCode"`
|
||||
}
|
||||
|
||||
type OAuthResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
type TransactionCompleteRequest struct {
|
||||
Amount int64 `json:"amount"`
|
||||
CustomerDescription string `json:"customerTrns"`
|
||||
}
|
||||
|
||||
type TransactionResponse 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"`
|
||||
}
|
||||
|
||||
type TransactionStatus string
|
||||
|
||||
const (
|
||||
PaymentSuccessful TransactionStatus = "F"
|
||||
PaymentPending TransactionStatus = "A"
|
||||
PaymentPreauthorized TransactionStatus = "C"
|
||||
PaymentUnsuccessful TransactionStatus = "E"
|
||||
PaymentRefunded TransactionStatus = "R"
|
||||
PaymentVoided TransactionStatus = "X"
|
||||
)
|
||||
|
||||
type TransactionStatusResponse struct {
|
||||
Email string `json:"email"`
|
||||
Amount int `json:"amount"`
|
||||
OrderCode database.OrderId `json:"orderCode"`
|
||||
StatusId TransactionStatus `json:"statusId"`
|
||||
FullName string `json:"fullName"`
|
||||
CardNumber string `json:"cardNumber"`
|
||||
}
|
|
@ -0,0 +1,386 @@
|
|||
package wspay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"payment-poc/database"
|
||||
"payment-poc/state"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
ShopId string
|
||||
ShopSecret string
|
||||
BackendUrl string
|
||||
}
|
||||
|
||||
func (s *Service) UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error) {
|
||||
var request = StatusCheckRequest{
|
||||
Version: "2.0",
|
||||
ShopId: s.ShopId,
|
||||
ShoppingCartId: entry.Id.String(),
|
||||
Signature: CalculateStatusCheckSignature(s.ShopId, s.ShopSecret, entry.Id.String()),
|
||||
}
|
||||
|
||||
httpResponse, err := createRequest(
|
||||
"POST",
|
||||
"https://test.wspay.biz/api/services/statusCheck",
|
||||
map[string]string{"content-type": "application/json"},
|
||||
toJson(request),
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response StatusCheckResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
if CompareStatusCheckReturnSignature(response.Signature, s.ShopId, s.ShopSecret, response.ActionSuccess, response.ApprovalCode, entry.Id.String()) != nil {
|
||||
entry.Amount = &response.Amount
|
||||
newState := determineState(response)
|
||||
|
||||
if entry.State != newState && newState != "" {
|
||||
log.Printf("Updated state for %s: %s -> %s", entry.Id.String(), entry.State, newState)
|
||||
entry.State = newState
|
||||
}
|
||||
|
||||
entry.State = state.StateCompleted
|
||||
} else {
|
||||
return database.PaymentEntry{}, errors.New("invalid signature")
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func determineState(response StatusCheckResponse) state.PaymentState {
|
||||
if response.Completed == "1" {
|
||||
return state.StateCompleted
|
||||
} else if response.Voided == "1" {
|
||||
return state.StateVoided
|
||||
} else if response.Refunded == "1" {
|
||||
return state.StateVoided
|
||||
} else if response.Authorized == "1" {
|
||||
return state.StateAccepted
|
||||
} else {
|
||||
return state.StateInitialized
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CreatePaymentUrl(entry database.PaymentEntry) (database.PaymentEntry, string, error) {
|
||||
return entry, "/wspay/initialize/" + entry.Id.String(), nil
|
||||
}
|
||||
|
||||
func (s *Service) CompleteTransaction(entry database.PaymentEntry, amount int64) (database.PaymentEntry, error) {
|
||||
if entry.State == state.StateAccepted {
|
||||
var request = CompletionRequest{
|
||||
Version: "2.0",
|
||||
WsPayOrderId: entry.Id.String(),
|
||||
ShopId: s.ShopId,
|
||||
ApprovalCode: *entry.ApprovalCode,
|
||||
STAN: *entry.STAN,
|
||||
Amount: amount,
|
||||
Signature: CalculateCompletionSignature(s.ShopId, s.ShopSecret, entry.Id.String(), *entry.STAN, *entry.ApprovalCode, amount),
|
||||
}
|
||||
|
||||
httpResponse, err := createRequest(
|
||||
"POST",
|
||||
"https://test.wspay.biz/api/services/completion",
|
||||
map[string]string{"content-type": "application/json"},
|
||||
toJson(request),
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response CompletionResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
if CompareCompletionReturnSignature(response.Signature, s.ShopId, s.ShopSecret, entry.Id.String(), *entry.STAN, response.ActionSuccess, response.ApprovalCode) != nil {
|
||||
entry.Amount = &amount
|
||||
entry.State = state.StateCompleted
|
||||
} else {
|
||||
return database.PaymentEntry{}, errors.New("invalid signature")
|
||||
}
|
||||
return entry, nil
|
||||
} else {
|
||||
return database.PaymentEntry{}, errors.New("payment is in invalid state")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CancelTransaction(entry database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
if entry.State == state.StateAccepted {
|
||||
var request = CompletionRequest{
|
||||
Version: "2.0",
|
||||
WsPayOrderId: entry.Id.String(),
|
||||
ShopId: s.ShopId,
|
||||
ApprovalCode: *entry.ApprovalCode,
|
||||
STAN: *entry.STAN,
|
||||
Amount: entry.TotalAmount,
|
||||
Signature: CalculateCompletionSignature(s.ShopId, s.ShopSecret, entry.Id.String(), *entry.STAN, *entry.ApprovalCode, entry.TotalAmount),
|
||||
}
|
||||
|
||||
httpResponse, err := createRequest(
|
||||
"POST",
|
||||
"https://test.wspay.biz/api/services/void",
|
||||
map[string]string{"content-type": "application/json"},
|
||||
toJson(request),
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response CompletionResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
if CompareCompletionReturnSignature(response.Signature, s.ShopId, s.ShopSecret, entry.Id.String(), *entry.STAN, response.ActionSuccess, response.ApprovalCode) != nil {
|
||||
entry.State = state.StateCanceled
|
||||
} else {
|
||||
return database.PaymentEntry{}, errors.New("invalid signature")
|
||||
}
|
||||
return entry, nil
|
||||
} else {
|
||||
return database.PaymentEntry{}, errors.New("payment is in invalid state")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) InitializePayment(entry database.PaymentEntry) Form {
|
||||
form := Form{
|
||||
ShopID: s.ShopId,
|
||||
ShoppingCartID: entry.Id.String(),
|
||||
Version: "2.0",
|
||||
TotalAmount: entry.TotalAmount,
|
||||
ReturnURL: s.BackendUrl + "/wspay/success",
|
||||
ReturnErrorURL: s.BackendUrl + "/wspay/error",
|
||||
CancelURL: s.BackendUrl + "/wspay/cancel",
|
||||
Signature: CalculateFormSignature(s.ShopId, s.ShopSecret, entry.Id.String(), entry.TotalAmount),
|
||||
}
|
||||
return form
|
||||
}
|
||||
|
||||
func (s *Service) HandleSuccessResponse(c *gin.Context, provider *database.PaymentEntryProvider) (string, error) {
|
||||
response := FormReturn{}
|
||||
if err := c.ShouldBind(&response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
entry, err := provider.FetchById(uuid.MustParse(response.ShoppingCartID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := CompareFormReturnSignature(response.Signature, s.ShopId, s.ShopSecret, response.ShoppingCartID, response.Success, response.ApprovalCode); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
entry.Lang = &response.Lang
|
||||
entry.ECI = &response.ECI
|
||||
entry.STAN = &response.STAN
|
||||
entry.Success = &response.Success
|
||||
entry.ApprovalCode = &response.ApprovalCode
|
||||
entry.State = state.StateAccepted
|
||||
|
||||
if _, err := provider.UpdateEntry(entry); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "/entries/" + entry.Id.String(), nil
|
||||
}
|
||||
|
||||
func (s *Service) HandleErrorResponse(c *gin.Context, provider *database.PaymentEntryProvider, paymentState state.PaymentState) (string, error) {
|
||||
response := FormError{}
|
||||
if err := c.ShouldBind(&response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
entry, err := provider.FetchById(uuid.MustParse(response.ShoppingCartID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := CompareFormReturnSignature(response.Signature, s.ShopId, s.ShopSecret, response.ShoppingCartID, response.Success, response.ApprovalCode); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
entry.Lang = &response.Lang
|
||||
entry.ECI = &response.ECI
|
||||
entry.Success = &response.Success
|
||||
entry.ApprovalCode = &response.ApprovalCode
|
||||
entry.Error = &response.ErrorMessage
|
||||
entry.State = paymentState
|
||||
|
||||
if _, err := provider.UpdateEntry(entry); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "/entries/" + entry.Id.String(), nil
|
||||
}
|
||||
|
||||
func CalculateFormSignature(shopId string, secret string, cartId string, amount int64) string {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
SecretKey
|
||||
ShoppingCartID
|
||||
SecretKey
|
||||
TotalAmount
|
||||
SecretKey
|
||||
*/
|
||||
signature := shopId + secret + cartId + secret + strconv.FormatInt(amount, 10) + secret
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(signature))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
func CalculateCompletionSignature(shopId string, secret string, cartId string, stan string, approvalCode string, amount int64) string {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
WsPayOrderId
|
||||
SecretKey
|
||||
STAN
|
||||
SecretKey
|
||||
ApprovalCode
|
||||
SecretKey
|
||||
Amount
|
||||
SecretKey
|
||||
WsPayOrderId
|
||||
*/
|
||||
signature := shopId + cartId + secret + stan + secret + approvalCode + secret + strconv.FormatInt(amount, 10) + secret + cartId
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(signature))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
func CompareFormReturnSignature(signature string, shopId string, secret string, cartId string, success int, approvalCode string) error {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
SecretKey
|
||||
ShoppingCartID
|
||||
SecretKey
|
||||
Success
|
||||
SecretKey
|
||||
ApprovalCode
|
||||
SecretKey
|
||||
Merchant should validate this signature to make sure that the request is originating from WSPayForm.
|
||||
*/
|
||||
calculatedSignature := shopId + secret + cartId + secret + strconv.FormatInt(int64(success), 10) + secret + approvalCode + secret
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(calculatedSignature))
|
||||
if hex.EncodeToString(hash.Sum(nil)) == signature {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("signature mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func CompareCompletionReturnSignature(signature string, shopId string, secret string, stan string, actionSuccess string, approvalCode string, cartId string) error {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
SecretKey
|
||||
STAN
|
||||
ActionSuccess
|
||||
SecretKey
|
||||
ApprovalCode
|
||||
WsPayOrderId
|
||||
Merchant should validate this signature to make sure that the request is originating from WSPayForm.
|
||||
*/
|
||||
calculatedSignature := shopId + secret + stan + actionSuccess + secret + approvalCode + cartId
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(calculatedSignature))
|
||||
if hex.EncodeToString(hash.Sum(nil)) == signature {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("signature mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func CompareStatusCheckReturnSignature(signature string, shopId string, secret string, actionSuccess string, approvalCode string, cartId string) error {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
SecretKey
|
||||
ActionSuccess
|
||||
ApprovalCode
|
||||
SecretKey
|
||||
ShopID
|
||||
ApprovalCode
|
||||
WsPayOrderId
|
||||
*/
|
||||
calculatedSignature := shopId + secret + actionSuccess + approvalCode + secret + shopId + approvalCode + cartId
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(calculatedSignature))
|
||||
if hex.EncodeToString(hash.Sum(nil)) == signature {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("signature mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func CalculateStatusCheckSignature(shopId string, secret string, cartId string) string {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
SecretKey
|
||||
ShoppingCartID
|
||||
SecretKey
|
||||
ShopID
|
||||
ShoppingCartID
|
||||
*/
|
||||
signature := shopId + secret + cartId + secret + shopId + cartId
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(signature))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
func readResponse[T any](httpResponse *http.Response, response T) error {
|
||||
if httpResponse.StatusCode == http.StatusOK {
|
||||
content, err := io.ReadAll(httpResponse.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(content, response)
|
||||
} else {
|
||||
return errors.New("received wrong status, expected 200 received " + strconv.FormatInt(int64(httpResponse.StatusCode), 10))
|
||||
}
|
||||
}
|
||||
|
||||
func createRequest(method string, url string, headers map[string]string, content []byte) (*http.Response, error) {
|
||||
request, err := http.NewRequest(method, url, bytes.NewReader(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range headers {
|
||||
request.Header.Add(key, value)
|
||||
}
|
||||
return http.DefaultClient.Do(request)
|
||||
}
|
||||
|
||||
func toJson[T any](request T) []byte {
|
||||
response, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return response
|
||||
}
|
|
@ -2,7 +2,7 @@ package wspay
|
|||
|
||||
const AuthorisationForm = "https://formtest.wspay.biz/authorization.aspx"
|
||||
|
||||
type WsPayForm struct {
|
||||
type Form struct {
|
||||
// required args
|
||||
ShopID string
|
||||
ShoppingCartID string
|
||||
|
@ -31,7 +31,7 @@ type WsPayForm struct {
|
|||
CurrencyCode int
|
||||
}
|
||||
|
||||
type WsPayFormReturn struct {
|
||||
type FormReturn struct {
|
||||
CustomerFirstName string `form:"CustomerFirstname"`
|
||||
CustomerSurname string `form:"CustomerSurname"`
|
||||
CustomerAddress string `form:"CustomerAddress"`
|
||||
|
@ -61,7 +61,7 @@ type WsPayFormReturn struct {
|
|||
Signature string `form:"Signature"`
|
||||
}
|
||||
|
||||
type WsPayFormError struct {
|
||||
type FormError struct {
|
||||
CustomerFirstName string
|
||||
CustomerSurname string
|
||||
CustomerAddress string
|
||||
|
@ -88,7 +88,7 @@ type WsPayFormError struct {
|
|||
Signature string
|
||||
}
|
||||
|
||||
type WsPayFormCancel struct {
|
||||
type FormCancel struct {
|
||||
ResponseCode int
|
||||
ShoppingCartID string
|
||||
ApprovalCode string
|
||||
|
@ -96,7 +96,7 @@ type WsPayFormCancel struct {
|
|||
Signature string
|
||||
}
|
||||
|
||||
type WsPayCompletionRequest struct {
|
||||
type CompletionRequest struct {
|
||||
Version string
|
||||
WsPayOrderId string
|
||||
ShopId string
|
||||
|
@ -106,7 +106,7 @@ type WsPayCompletionRequest struct {
|
|||
Signature string
|
||||
}
|
||||
|
||||
type WsPayCompletionResponse struct {
|
||||
type CompletionResponse struct {
|
||||
WsPayOrderId string
|
||||
ShopId string
|
||||
ApprovalCode string
|
||||
|
@ -116,28 +116,28 @@ type WsPayCompletionResponse struct {
|
|||
Signature string
|
||||
}
|
||||
|
||||
type WsPayStatusCheckRequest struct {
|
||||
type StatusCheckRequest struct {
|
||||
Version string
|
||||
ShopId string
|
||||
ShoppingCartId string
|
||||
Signature string
|
||||
}
|
||||
|
||||
type WsPayStatusCheckResponse struct {
|
||||
type StatusCheckResponse struct {
|
||||
WsPayOrderId string
|
||||
Signature string
|
||||
STAN string
|
||||
ApprovalCode string
|
||||
ShopID string
|
||||
ShoppingCartID string
|
||||
Amount string
|
||||
Amount int64
|
||||
CurrencyCode string
|
||||
ActionSuccess string
|
||||
Success string // deprecated
|
||||
Authorized int
|
||||
Completed int
|
||||
Voided int
|
||||
Refunded int
|
||||
Authorized string
|
||||
Completed string
|
||||
Voided string
|
||||
Refunded string
|
||||
PaymentPlan string
|
||||
Partner string
|
||||
OnSite int
|
|
@ -3,9 +3,14 @@ package state
|
|||
type PaymentState string
|
||||
|
||||
const (
|
||||
// initial state
|
||||
StatePreinitialized PaymentState = "preinitialized"
|
||||
// initial state
|
||||
StateInitialized PaymentState = "initialized"
|
||||
|
||||
// state given to async payments (eg. GooglePay,ApplePay...)
|
||||
StatePending PaymentState = "pending"
|
||||
|
||||
// state on response
|
||||
StateAccepted PaymentState = "accepted"
|
||||
StateError PaymentState = "error"
|
||||
|
@ -16,3 +21,12 @@ const (
|
|||
StateVoided PaymentState = "voided"
|
||||
StateCanceled PaymentState = "canceled"
|
||||
)
|
||||
|
||||
type PaymentGateway string
|
||||
|
||||
const (
|
||||
GatewayWsPay PaymentGateway = "wspay"
|
||||
GatewayStripe PaymentGateway = "stripe"
|
||||
GatewayVivaWallet PaymentGateway = "viva-wallet"
|
||||
GatewayMock PaymentGateway = "mock"
|
||||
)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package stripe
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"payment-poc/state"
|
||||
)
|
||||
|
||||
type StripeDb struct {
|
||||
Id uuid.UUID `db:"id"`
|
||||
TotalAmount int64 `db:"total_amount"`
|
||||
Lang string `db:"lang"`
|
||||
|
||||
PaymentIntentId string `db:"payment_intent_id"`
|
||||
|
||||
State state.PaymentState `db:"payment_state"`
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package stripe
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"payment-poc/state"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
DB *sqlx.DB
|
||||
}
|
||||
|
||||
func (s *Service) CreateEntry(totalAmount int64) (StripeDb, error) {
|
||||
id := uuid.Must(uuid.NewRandom())
|
||||
entry := StripeDb{
|
||||
Id: id,
|
||||
TotalAmount: totalAmount,
|
||||
State: state.StateInitialized,
|
||||
}
|
||||
_, err := s.DB.Exec(`INSERT INTO "stripe" ("id", "total_amount", "payment_state") VALUES ($1, $2, $3)`,
|
||||
&entry.Id, &entry.TotalAmount, &entry.State,
|
||||
)
|
||||
if err != nil {
|
||||
return StripeDb{}, err
|
||||
}
|
||||
return s.FetchById(id)
|
||||
}
|
||||
|
||||
func (s *Service) FetchAll() ([]StripeDb, error) {
|
||||
var entries []StripeDb
|
||||
err := s.DB.Select(&entries, `SELECT * FROM "stripe"`)
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (s *Service) FetchById(id uuid.UUID) (StripeDb, error) {
|
||||
entry := StripeDb{}
|
||||
err := s.DB.Get(&entry, `SELECT * FROM "stripe" WHERE "id" = $1`, id)
|
||||
return entry, err
|
||||
}
|
||||
|
||||
func (s *Service) Update(entry StripeDb) error {
|
||||
_, err := s.DB.Exec(`UPDATE "stripe" set "payment_intent_id" = $2, "payment_state" = $3 WHERE "id" = $1`,
|
||||
&entry.Id, &entry.PaymentIntentId, &entry.State,
|
||||
)
|
||||
return err
|
||||
}
|
|
@ -10,10 +10,10 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
th {text-align: left}
|
||||
tr > td:nth-child(2) {
|
||||
tr > td:nth-child(3) {
|
||||
text-align: right;
|
||||
}
|
||||
tr > th:nth-child(2) {
|
||||
tr > th:nth-child(3) {
|
||||
text-align: right;
|
||||
}
|
||||
td, th {
|
||||
|
@ -24,71 +24,41 @@
|
|||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="container">
|
||||
<h2>Novo plačanje</h2>
|
||||
<body>
|
||||
<!-- As a link -->
|
||||
<nav class="navbar navbar-dark bg-dark">
|
||||
<section class="container">
|
||||
<a class="navbar-brand" href="/">Payment-poc</a>
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<section class="container">
|
||||
<h2>Novo plaćanje</h2>
|
||||
|
||||
<form method="get" action="/methods">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="amount">Vrijednost</label>
|
||||
<input class="form-control" id="amount" required name="amount" type="number" step="0.01" min="0">
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Izradi novo plačanje</button>
|
||||
<button class="btn btn-primary" type="submit">Izradi novo plaćanje</button>
|
||||
</form>
|
||||
|
||||
<div>
|
||||
<h2>WsPay</h2>
|
||||
<h2>Entries</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Gateway</th>
|
||||
<th>Vrijednost</th>
|
||||
<th>Stanje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .WsPay}}
|
||||
{{range .Entries}}
|
||||
<tr>
|
||||
<td><a class="link-primary" href="/wspay/info/{{.Id}}">{{.Id}}</a></td>
|
||||
<td>{{formatCurrency .TotalAmount}}</td>
|
||||
<td>{{formatState .State}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Stripe</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Vrijednost</th>
|
||||
<th>Stanje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Stripe}}
|
||||
<tr>
|
||||
<td><a class="link-primary" href="/stripe/info/{{.Id}}">{{.Id}}</a></td>
|
||||
<td>{{formatCurrency .TotalAmount}}</td>
|
||||
<td>{{formatState .State}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Viva</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Vrijednost</th>
|
||||
<th>Stanje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Viva}}
|
||||
<tr>
|
||||
<td><a class="link-primary" href="/viva/info/{{.Id}}">{{.Id}}</a></td>
|
||||
<td><a class="link-primary" href="/entries/{{.Id}}">{{.Id}}</a></td>
|
||||
<td>{{.Gateway}}</td>
|
||||
<td>{{formatCurrency .TotalAmount}}</td>
|
||||
<td>{{formatState .State}}</td>
|
||||
</tr>
|
||||
|
@ -96,5 +66,6 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,79 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Index</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
th {text-align: left}
|
||||
h2 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- As a link -->
|
||||
<nav class="navbar navbar-dark bg-dark">
|
||||
<section class="container">
|
||||
<a class="navbar-brand" href="/">Payment-poc</a>
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<section class="container">
|
||||
|
||||
<h2>Plaćanje {{.Entry.Id}}</h2>
|
||||
|
||||
{{if not (eq .Entry.State "preinitialized")}}
|
||||
<form method="post" action="/entries/{{.Entry.Id}}/refresh">
|
||||
<button class="btn btn-primary">Ažuriraj</button>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
<table class="table">
|
||||
<tr><th>Id: </th><td>{{.Entry.Id}}</td></tr>
|
||||
<tr><th>Datum izrade: </th><td>{{.Entry.Created.Format "Jan 02, 2006 15:04:05 UTC"}}</td></tr>
|
||||
<tr><th>Zadnja izmjena: </th><td>{{or (.Entry.Modified.Format "Jan 02, 2006 15:04:05 UTC") .Entry.Created.Format "Jan 02, 2006 15:04:05 UTC"}}</td></tr>
|
||||
<tr><th>Gateway: </th><td>{{.Entry.Gateway}}</td></tr>
|
||||
<tr><th>Naplaćena vrijednost: </th><td>{{formatCurrencyPtr .Entry.Amount}}</td></tr>
|
||||
<tr><th>Ukupna vrijednost: </th><td>{{formatCurrency .Entry.TotalAmount}}</td></tr>
|
||||
<tr><th>Jezik: </th><td>{{or .Entry.Lang "-"}}</td></tr>
|
||||
<tr><th>Greške: </th><td>{{or .Entry.Error "-"}}</td></tr>
|
||||
<tr><th>Stanje: </th><td>{{formatState .Entry.State}}</td></tr>
|
||||
|
||||
{{if eq .Entry.Gateway "wspay"}}
|
||||
<tr><th>WsPay</th><td></td></tr>
|
||||
<tr><th>Success: </th><td>{{or .Entry.Success "-"}}</td></tr>
|
||||
{{end}}
|
||||
|
||||
{{if eq .Entry.Gateway "stripe"}}
|
||||
<tr><th>Stripe</th><td></td></tr>
|
||||
<tr><th>Payment intent ID: </th><td>{{or .Entry.PaymentIntentId "-"}}</td></tr>
|
||||
{{end}}
|
||||
|
||||
{{if eq .Entry.Gateway "viva-wallet"}}
|
||||
<tr><th>Viva wallet</th><td></td></tr>
|
||||
<tr><th>Order ID: </th><td>{{or .Entry.OrderId "-"}}</td></tr>
|
||||
<tr><th>Transaction ID: </th><td>{{or .Entry.TransactionId "-"}}</td></tr>
|
||||
<tr><th>Event ID: </th><td>{{or .Entry.EventId "-"}}</td></tr>
|
||||
{{end}}
|
||||
|
||||
</table>
|
||||
{{if eq .Entry.State "accepted"}}
|
||||
<form class="mb-3" method="post" action="/entries/{{.Entry.Id}}/complete">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="amount">Završi plaćanje</label>
|
||||
<input class="form-control" id="amount" required name="amount" type="number" value="{{decimalCurrency .Entry.TotalAmount}}" step="0.01" min="0.01" max="{{decimalCurrency .Entry.TotalAmount}}">
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Završi plaćanje</button>
|
||||
</form>
|
||||
<form method="post" action="/entries/{{.Entry.Id}}/cancel">
|
||||
<button class="btn btn-primary" type="submit">Otkaži plaćanje</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -21,10 +21,19 @@
|
|||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="container">
|
||||
<h2>Izaberi metodu plačanja</h2>
|
||||
<a class="btn btn-success" href="/wspay?amount={{.Amount}}">WsPay</a>
|
||||
<a class="btn btn-success" href="/stripe?amount={{.Amount}}">Stripe</a>
|
||||
<a class="btn btn-success" href="/viva?amount={{.Amount}}">Viva</a>
|
||||
<body>
|
||||
<!-- As a link -->
|
||||
<nav class="navbar navbar-dark bg-dark">
|
||||
<section class="container">
|
||||
<a class="navbar-brand" href="/">Payment-poc</a>
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<section class="container">
|
||||
<h2>Izaberi metodu plaćanja</h2>
|
||||
{{ range $key, $value := .Gateways }}
|
||||
<a class="btn btn-success" href="/methods/{{$key}}?amount={{$.Amount}}">{{$value}}</a>
|
||||
{{end}}
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -5,31 +5,23 @@
|
|||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Obrada odgovora</title>
|
||||
<title>Index</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
th {text-align: left}
|
||||
h2 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="container">
|
||||
|
||||
<p>Obrada odgovora...</p>
|
||||
<div id="error"></div>
|
||||
|
||||
<script>
|
||||
window.onload = () => {
|
||||
setTimeout(() => {
|
||||
window?.top?.postMessage(JSON.stringify({"success": true}), {targetOrigin: "*"});
|
||||
if(window?.top) {
|
||||
document.querySelector("#error").innerHTML = `<p>Izgleda da je došlo do greške, jer stranica nije otvorena u iframe-u</p><a href="/">Stisnite ovdje da se vratite na naslovnicu</a>`;
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<!-- As a link -->
|
||||
<nav class="navbar navbar-dark bg-dark">
|
||||
<section class="container">
|
||||
<a class="navbar-brand" href="/">Payment-poc</a>
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<section class="container">
|
||||
<h2>Mock gateway {{.Entry.Id.String}}</h2>
|
||||
<p>{{formatCurrency .Entry.TotalAmount}}</p>
|
||||
<a href="/providers/mock/success?id={{.Entry.Id.String}}" class="btn btn-success">Potvrdi plaćanje</a>
|
||||
<a href="/providers/mock/error?id={{.Entry.Id.String}}" class="btn btn-danger">Otkaži plaćanje</a>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -1,42 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Info</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
th {text-align: left}
|
||||
h2 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="container">
|
||||
<h2>Plačanje {{.Entry.Id}}</h2>
|
||||
|
||||
<table class="table">
|
||||
<tr><th>Id: </th><td>{{.Entry.Id}}</td></tr>
|
||||
<tr><th>Ukupna vrijednost: </th><td>{{formatCurrency .Entry.TotalAmount}}</td></tr>
|
||||
<tr><th>Jezik: </th><td>{{omitempty .Entry.Lang}}</td></tr>
|
||||
<tr><th>Stanje: </th><td>{{formatState .Entry.State}}</td></tr>
|
||||
</table>
|
||||
|
||||
|
||||
{{if eq .Entry.State "accepted"}}
|
||||
<form class="mb-3" method="post" action="/stripe/complete/{{.Entry.Id}}">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="amount">Završi transakciju</label>
|
||||
<input class="form-control" id="amount" required name="amount" type="number" value="{{formatCurrency2 .Entry.TotalAmount}}" step="0.01" min="0.01" max="{{formatCurrency2 .Entry.TotalAmount}}">
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Završi transakciju</button>
|
||||
</form>
|
||||
<form method="post" action="/stripe/cancel/{{.Entry.Id}}">
|
||||
<button class="btn btn-primary" type="submit">Otkaži transakciju</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
|
@ -1,44 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Info</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
th {text-align: left}
|
||||
h2 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="container">
|
||||
<h2>Plačanje {{.Entry.Id}}</h2>
|
||||
|
||||
<table class="table">
|
||||
<tr><th>Id: </th><td>{{.Entry.Id}}</td></tr>
|
||||
<tr><th>Order id: </th><td>{{.Entry.OrderId}}</td></tr>
|
||||
<tr><th>Transaction id: </th><td>{{.Entry.TransactionId.String}}</td></tr>
|
||||
<tr><th>Ukupna vrijednost: </th><td>{{formatCurrency .Entry.TotalAmount}}</td></tr>
|
||||
<tr><th>Jezik: </th><td>{{omitempty .Entry.Lang}}</td></tr>
|
||||
<tr><th>Događaj: </th><td>{{.Entry.EventId}}</td></tr>
|
||||
<tr><th>Stanje: </th><td>{{formatState .Entry.State}}</td></tr>
|
||||
</table>
|
||||
|
||||
{{if eq .Entry.State "accepted"}}
|
||||
<form class="mb-3" method="post" action="/viva/complete/{{.Entry.Id}}">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="amount">Završi transakciju</label>
|
||||
<input class="form-control" id="amount" required name="amount" type="number" value="{{formatCurrency2 .Entry.TotalAmount}}" step="0.01" min="0.01" max="{{formatCurrency2 .Entry.TotalAmount}}">
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Završi transakciju</button>
|
||||
</form>
|
||||
<form method="post" action="/viva/cancel/{{.Entry.Id}}">
|
||||
<button class="btn btn-primary" type="submit">Otkaži transakciju</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
|
@ -5,7 +5,7 @@
|
|||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Izradi plančanje</title>
|
||||
<title>Izradi planćanje</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
|
@ -15,9 +15,9 @@
|
|||
</style>
|
||||
</head>
|
||||
<body class="container" style="margin-top: 32px">
|
||||
<h2>Započni proces plačanja</h2>
|
||||
<h2>Započni proces plaćanja</h2>
|
||||
|
||||
<form action="{{.Action}}" method="POST">
|
||||
<form id="wspay-form" action="{{.Action}}" method="POST">
|
||||
<input type="hidden" name="ShopID" value="{{.Form.ShopID}}">
|
||||
<input type="hidden" name="ShoppingCartID" value="{{.Form.ShoppingCartID}}">
|
||||
<input type="hidden" name="Version" value="{{.Form.Version}}">
|
||||
|
@ -26,36 +26,10 @@
|
|||
<input type="hidden" name="ReturnURL" value="{{.Form.ReturnURL}}">
|
||||
<input type="hidden" name="CancelURL" value="{{.Form.CancelURL}}">
|
||||
<input type="hidden" name="ReturnErrorURL" value="{{.Form.ReturnErrorURL}}">
|
||||
<input type="submit" class="btn btn-primary" value="Koristi normalni redirect">
|
||||
</form>
|
||||
|
||||
<h2>Započni normalni proces u iframe-u</h2>
|
||||
|
||||
<form target="payment-frame" name="pay" action="{{.Action}}" method="POST">
|
||||
<input type="hidden" name="Iframe" value="True">
|
||||
<input type="hidden" name="IframeResponseTarget" value="SELF">
|
||||
|
||||
<input type="hidden" name="ShopID" value="{{.Form.ShopID}}">
|
||||
<input type="hidden" name="ShoppingCartID" value="{{.Form.ShoppingCartID}}">
|
||||
<input type="hidden" name="Version" value="{{.Form.Version}}">
|
||||
<input type="hidden" name="TotalAmount" value="{{formatCurrency .Form.TotalAmount}}">
|
||||
<input type="hidden" name="Signature" value="{{.Form.Signature}}">
|
||||
<input type="hidden" name="ReturnURL" value="{{.Form.ReturnURL}}?iframe=true">
|
||||
<input type="hidden" name="CancelURL" value="{{.Form.CancelURL}}?iframe=true">
|
||||
<input type="hidden" name="ReturnErrorURL" value="{{.Form.ReturnErrorURL}}?iframe=true">
|
||||
<input type="submit" class="btn btn-primary" value="Koristi navigaciju u iframe-u">
|
||||
</form>
|
||||
<iframe id="payment-frame" name="payment-frame" style="width: 100%; min-height: 600px"></iframe>
|
||||
|
||||
<script>
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
console.log("received response")
|
||||
window.location.href = "/";
|
||||
},
|
||||
false
|
||||
);
|
||||
document.querySelector("#wspay-form").submit();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,62 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Info</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
th {text-align: left}
|
||||
h2 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="container">
|
||||
<h2>Plačanje {{.Entry.Id}}</h2>
|
||||
|
||||
<table class="table">
|
||||
<tr><th>CartId: </th><td>{{.Entry.ShoppingCartID}}</td></tr>
|
||||
<tr><th>Ukupna vrijednost: </th><td>{{formatCurrency .Entry.TotalAmount}}</td></tr>
|
||||
<tr><th>Jezik: </th><td>{{omitempty .Entry.Lang}}</td></tr>
|
||||
|
||||
<tr><th>Ime: </th><td>{{omitempty .Entry.CustomerFirstName}}</td></tr>
|
||||
<tr><th>Prezime: </th><td>{{omitempty .Entry.CustomerLastName}}</td></tr>
|
||||
<tr><th>Adresa: </th><td>{{omitempty .Entry.CustomerAddress}}</td></tr>
|
||||
<tr><th>Grad: </th><td>{{omitempty .Entry.CustomerCity}}</td></tr>
|
||||
<tr><th>ZIP: </th><td>{{omitempty .Entry.CustomerZIP}}</td></tr>
|
||||
<tr><th>Zemlja: </th><td>{{omitempty .Entry.CustomerCountry}}</td></tr>
|
||||
<tr><th>Broj telefona: </th><td>{{omitempty .Entry.CustomerPhone}}</td></tr>
|
||||
|
||||
<tr><th>Plan plačanja: </th><td>{{omitempty .Entry.PaymentPlan}}</td></tr>
|
||||
<tr><th>Ime kartice: </th><td>{{omitempty .Entry.CreditCardName}}</td></tr>
|
||||
<tr><th>Broj kartice: </th><td>{{omitempty .Entry.CreditCardNumber}}</td></tr>
|
||||
<tr><th>Metoda plačanja: </th><td>{{omitempty .Entry.PaymentMethod}}</td></tr>
|
||||
<tr><th>Oznaka valute: </th><td>{{.Entry.CurrencyCode}}</td></tr>
|
||||
|
||||
<tr><th>Datum i vrijeme: </th><td>{{.Entry.DateTime.Format "Jan 02, 2006 15:04:05 UTC"}}</td></tr>
|
||||
<tr><th>Uspjeh: </th> <td>{{.Entry.Success}}</td></tr>
|
||||
<tr><th>Kod: </th> <td>{{omitempty .Entry.ApprovalCode}}</td></tr>
|
||||
<tr><th>Poruka greške: </th> <td>{{omitempty .Entry.ErrorMessage}}</td></tr>
|
||||
<tr><th>Kodovi greške: </th> <td>{{omitempty .Entry.ErrorCodes}}</td></tr>
|
||||
|
||||
<tr><th>Stanje: </th><td>{{formatState .Entry.State}}</td></tr>
|
||||
</table>
|
||||
|
||||
{{if eq .Entry.State "accepted"}}
|
||||
<form class="mb-3" method="post" action="/wspay/complete/{{.Entry.Id}}">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="amount">Završi transakciju</label>
|
||||
<input class="form-control" id="amount" required name="amount" type="number" value="{{formatCurrency2 .Entry.TotalAmount}}" step="0.01" min="0.01" max="{{formatCurrency2 .Entry.TotalAmount}}">
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Završi transakciju</button>
|
||||
</form>
|
||||
<form method="post" action="/wspay/cancel/{{.Entry.Id}}">
|
||||
<button class="btn btn-primary" type="submit">Otkaži transakciju</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
|
@ -1,26 +0,0 @@
|
|||
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
viva/service.go
235
viva/service.go
|
@ -1,235 +0,0 @@
|
|||
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
viva/viva.go
44
viva/viva.go
|
@ -1,44 +0,0 @@
|
|||
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"`
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package wspay
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"payment-poc/state"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WsPayDb struct {
|
||||
Id uuid.UUID `db:"id"`
|
||||
ShopID string `db:"shop_id"`
|
||||
ShoppingCartID string `db:"shopping_card_id"`
|
||||
TotalAmount int64 `db:"total_amount"`
|
||||
Lang string `db:"lang"`
|
||||
|
||||
CustomerFirstName string `db:"customer_first_name"`
|
||||
CustomerLastName string `db:"customer_last_name"`
|
||||
CustomerAddress string `db:"customer_address"`
|
||||
CustomerCity string `db:"customer_city"`
|
||||
CustomerZIP string `db:"customer_zip"`
|
||||
CustomerCountry string `db:"customer_country"`
|
||||
CustomerPhone string `db:"customer_phone"`
|
||||
|
||||
PaymentPlan string `db:"payment_plan"`
|
||||
CreditCardName string `db:"credit_card_name"`
|
||||
CreditCardNumber string `db:"credit_card_number"`
|
||||
PaymentMethod string `db:"payment_method"`
|
||||
CurrencyCode int `db:"currency_code"`
|
||||
|
||||
DateTime time.Time `db:"date_time"`
|
||||
|
||||
ECI string `db:"eci"`
|
||||
STAN string `db:"stan"`
|
||||
|
||||
Success int `db:"success"`
|
||||
ApprovalCode string `db:"approval_code"`
|
||||
ErrorMessage string `db:"error_message"`
|
||||
ErrorCodes string `db:"error_codes"`
|
||||
|
||||
State state.PaymentState `db:"payment_state"`
|
||||
}
|
143
wspay/service.go
143
wspay/service.go
|
@ -1,143 +0,0 @@
|
|||
package wspay
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"payment-poc/state"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
DB *sqlx.DB
|
||||
}
|
||||
|
||||
func (s *Service) CreateEntry(shopId string, totalAmount int64) (WsPayDb, error) {
|
||||
id := uuid.Must(uuid.NewRandom())
|
||||
entry := WsPayDb{
|
||||
Id: id,
|
||||
ShopID: shopId,
|
||||
ShoppingCartID: id.String(),
|
||||
TotalAmount: totalAmount,
|
||||
State: state.StateInitialized,
|
||||
}
|
||||
_, err := s.DB.Exec(`INSERT INTO "wspay" ("id", "shop_id", "shopping_card_id", "total_amount", "payment_state") VALUES ($1, $2, $3, $4, $5)`,
|
||||
&entry.Id, &entry.ShopID, &entry.ShoppingCartID, &entry.TotalAmount, &entry.State,
|
||||
)
|
||||
if err != nil {
|
||||
return WsPayDb{}, err
|
||||
}
|
||||
return s.FetchById(id)
|
||||
}
|
||||
|
||||
func (s *Service) FetchAll() ([]WsPayDb, error) {
|
||||
var entries []WsPayDb
|
||||
err := s.DB.Select(&entries, `SELECT * FROM "wspay"`)
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (s *Service) FetchById(id uuid.UUID) (WsPayDb, error) {
|
||||
entry := WsPayDb{}
|
||||
err := s.DB.Get(&entry, `SELECT * FROM "wspay" WHERE "id" = $1`, id)
|
||||
return entry, err
|
||||
}
|
||||
|
||||
func (s *Service) FetchByShoppingCartID(id string) (WsPayDb, error) {
|
||||
entry := WsPayDb{}
|
||||
err := s.DB.Get(&entry, `SELECT * FROM "wspay" WHERE "shopping_card_id" = $1`, id)
|
||||
return entry, err
|
||||
}
|
||||
|
||||
func (s *Service) Update(entry WsPayDb) error {
|
||||
_, err := s.DB.Exec(`UPDATE "wspay" set "lang" = $2, "customer_first_name" = $3, "customer_last_name" = $4, "customer_address" = $5, "customer_city" = $6, "customer_zip" = $7, "customer_country" = $8, "customer_phone" = $9, "payment_plan" = $10, "credit_card_name" = $11, "credit_card_number" = $12, "payment_method" = $13, "currency_code" = $14, "date_time" = $15, "eci" = $16, "stan" = $17, "success" = $18, "approval_code" = $19, "error_message" = $20, "error_codes" = $21, "payment_state" = $22 WHERE "id" = $1`,
|
||||
&entry.Id, &entry.Lang, &entry.CustomerFirstName, &entry.CustomerLastName, &entry.CustomerAddress, &entry.CustomerCity, &entry.CustomerZIP, &entry.CustomerCountry, &entry.CustomerPhone, &entry.PaymentPlan, &entry.CreditCardName, &entry.CreditCardNumber, &entry.PaymentMethod, &entry.CurrencyCode, &entry.DateTime, &entry.ECI, &entry.STAN, &entry.Success, &entry.ApprovalCode, &entry.ErrorMessage, &entry.ErrorCodes, &entry.State,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func CalculateFormSignature(shopId string, secret string, cartId string, amount int64) string {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
SecretKey
|
||||
ShoppingCartID
|
||||
SecretKey
|
||||
TotalAmount
|
||||
SecretKey
|
||||
*/
|
||||
signature := shopId + secret + cartId + secret + strconv.FormatInt(amount, 10) + secret
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(signature))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
func CalculateCompletionSignature(shopId string, secret string, cartId string, stan string, approvalCode string, amount int64) string {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
WsPayOrderId
|
||||
SecretKey
|
||||
STAN
|
||||
SecretKey
|
||||
ApprovalCode
|
||||
SecretKey
|
||||
Amount
|
||||
SecretKey
|
||||
WsPayOrderId
|
||||
*/
|
||||
signature := shopId + cartId + secret + stan + secret + approvalCode + secret + strconv.FormatInt(amount, 10) + secret + cartId
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(signature))
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
func CompareFormReturnSignature(signature string, shopId string, secret string, cartId string, success int, approvalCode string) error {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
SecretKey
|
||||
ShoppingCartID
|
||||
SecretKey
|
||||
Success
|
||||
SecretKey
|
||||
ApprovalCode
|
||||
SecretKey
|
||||
Merchant should validate this signature to make sure that the request is originating from WSPayForm.
|
||||
*/
|
||||
calculatedSignature := shopId + secret + cartId + secret + strconv.FormatInt(int64(success), 10) + secret + approvalCode + secret
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(calculatedSignature))
|
||||
if hex.EncodeToString(hash.Sum(nil)) == signature {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("signature mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func CompareCompletionReturnSignature(signature string, shopId string, secret string, stan string, actionSuccess string, approvalCode string, cartId string) error {
|
||||
/**
|
||||
Represents a signature created from string formatted from following values in a following order using
|
||||
SHA512 algorithm:
|
||||
ShopID
|
||||
SecretKey
|
||||
STAN
|
||||
ActionSuccess
|
||||
SecretKey
|
||||
ApprovalCode
|
||||
WsPayOrderId
|
||||
Merchant should validate this signature to make sure that the request is originating from WSPayForm.
|
||||
*/
|
||||
calculatedSignature := shopId + secret + stan + actionSuccess + secret + approvalCode + cartId
|
||||
hash := sha512.New()
|
||||
hash.Write([]byte(calculatedSignature))
|
||||
if hex.EncodeToString(hash.Sum(nil)) == signature {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("signature mismatch")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue