175 lines
4.8 KiB
Go
175 lines
4.8 KiB
Go
|
/*
|
||
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||
|
*/
|
||
|
|
||
|
package redsys
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/cipher"
|
||
|
"crypto/des"
|
||
|
"crypto/hmac"
|
||
|
"crypto/sha256"
|
||
|
"encoding/base64"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
type Language int
|
||
|
|
||
|
const (
|
||
|
LanguageDefault Language = 0
|
||
|
LanguageSpanish Language = 1
|
||
|
LanguageEnglish Language = 2
|
||
|
LanguageCatalan Language = 3
|
||
|
LanguageFrench Language = 4
|
||
|
)
|
||
|
|
||
|
type TransactionType int
|
||
|
|
||
|
const (
|
||
|
TransactionTypeCharge TransactionType = 0
|
||
|
TransactionTypeRefund TransactionType = 3
|
||
|
TransactionTypePreauth TransactionType = 1
|
||
|
TransactionTypePreauthConfirm TransactionType = 2
|
||
|
TransactionTypePreauthVoid TransactionType = 9
|
||
|
TransactionTypeSplitAuth TransactionType = 7
|
||
|
TransactionTypeSplitConfirm TransactionType = 8
|
||
|
)
|
||
|
|
||
|
type Currency int
|
||
|
|
||
|
// Source: https://sis-d.redsys.es/pagosonline/codigos-autorizacion.html#monedas
|
||
|
// The codes seem to be the same as ISO 4217 https://en.wikipedia.org/wiki/ISO_4217
|
||
|
const (
|
||
|
CurrencyEUR Currency = 978
|
||
|
)
|
||
|
|
||
|
type Request struct {
|
||
|
TransactionType TransactionType
|
||
|
Amount int64
|
||
|
Currency Currency
|
||
|
Order string
|
||
|
Product string
|
||
|
CardHolder string
|
||
|
SuccessURL string
|
||
|
FailureURL string
|
||
|
ConsumerLanguage Language
|
||
|
}
|
||
|
|
||
|
type merchantParameters struct {
|
||
|
MerchantCode string `json:"Ds_Merchant_MerchantCode"`
|
||
|
Terminal string `json:"Ds_Merchant_Terminal"`
|
||
|
TransactionType TransactionType `json:"Ds_Merchant_TransactionType,string"`
|
||
|
Amount int64 `json:"Ds_Merchant_Amount,string"`
|
||
|
Currency Currency `json:"Ds_Merchant_Currency,string"`
|
||
|
Order string `json:"Ds_Merchant_Order"`
|
||
|
MerchantURL string `json:"Ds_Merchant_MerchantURL,omitempty"`
|
||
|
Product string `json:"Ds_Merchant_ProductDescription,omitempty"`
|
||
|
CardHolder string `json:"Ds_Merchant_Titular,omitempty"`
|
||
|
SuccessURL string `json:"Ds_Merchant_UrlOK,omitempty"`
|
||
|
FailureURL string `json:"Ds_Merchant_UrlKO,omitempty"`
|
||
|
MerchantName string `json:"Ds_Merchant_MerchantName,omitempty"`
|
||
|
ConsumerLanguage Language `json:"Ds_Merchant_ConsumerLanguage,string,omitempty"`
|
||
|
}
|
||
|
|
||
|
type Client struct {
|
||
|
merchantCode string
|
||
|
terminalCode string
|
||
|
crypto cipher.Block
|
||
|
}
|
||
|
|
||
|
func New(merchantCode string, terminalCode string, key string) (*Client, error) {
|
||
|
b, err := base64.StdEncoding.DecodeString(key)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("key is invalid base64: %v", err)
|
||
|
}
|
||
|
|
||
|
crypto, err := des.NewTripleDESCipher(b)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return &Client{
|
||
|
merchantCode: merchantCode,
|
||
|
terminalCode: terminalCode,
|
||
|
crypto: crypto,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (p *Client) SignRequest(req *Request) (*SignedRequest, error) {
|
||
|
signed := &SignedRequest{
|
||
|
SignatureVersion: "HMAC_SHA256_V1",
|
||
|
}
|
||
|
params := &merchantParameters{
|
||
|
MerchantCode: p.merchantCode,
|
||
|
Terminal: p.terminalCode,
|
||
|
TransactionType: req.TransactionType,
|
||
|
Amount: req.Amount,
|
||
|
Currency: req.Currency,
|
||
|
Order: req.Order,
|
||
|
MerchantURL: "http://localhost/ca/",
|
||
|
Product: req.Product,
|
||
|
CardHolder: req.CardHolder,
|
||
|
SuccessURL: req.SuccessURL,
|
||
|
FailureURL: req.FailureURL,
|
||
|
MerchantName: "Merchant Here",
|
||
|
ConsumerLanguage: req.ConsumerLanguage,
|
||
|
}
|
||
|
var err error
|
||
|
signed.MerchantParameters, err = encodeMerchantParameters(params)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
signed.Signature = p.sign(params.Order, signed.MerchantParameters)
|
||
|
return signed, nil
|
||
|
}
|
||
|
|
||
|
func encodeMerchantParameters(params *merchantParameters) (string, error) {
|
||
|
marshalled, err := json.Marshal(params)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return base64.URLEncoding.EncodeToString(marshalled), err
|
||
|
}
|
||
|
|
||
|
func (p *Client) sign(id string, data string) string {
|
||
|
key := encrypt3DES(id, p.crypto)
|
||
|
return mac256(data, key)
|
||
|
}
|
||
|
|
||
|
var iv = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||
|
|
||
|
func encrypt3DES(str string, crypto cipher.Block) string {
|
||
|
cbc := cipher.NewCBCEncrypter(crypto, iv)
|
||
|
|
||
|
decrypted := []byte(str)
|
||
|
decryptedPadded, _ := zeroPad(decrypted, crypto.BlockSize())
|
||
|
cbc.CryptBlocks(decryptedPadded, decryptedPadded)
|
||
|
|
||
|
return base64.StdEncoding.EncodeToString(decryptedPadded)
|
||
|
}
|
||
|
|
||
|
func zeroPad(data []byte, blockLen int) ([]byte, error) {
|
||
|
padLen := (blockLen - (len(data) % blockLen)) % blockLen
|
||
|
pad := bytes.Repeat([]byte{0x00}, padLen)
|
||
|
|
||
|
return append(data, pad...), nil
|
||
|
}
|
||
|
|
||
|
func mac256(data string, key string) string {
|
||
|
decodedKey, _ := base64.StdEncoding.DecodeString(key)
|
||
|
res := hmac.New(sha256.New, decodedKey)
|
||
|
res.Write([]byte(strings.TrimSpace(data)))
|
||
|
result := res.Sum(nil)
|
||
|
return base64.StdEncoding.EncodeToString(result)
|
||
|
}
|
||
|
|
||
|
type SignedRequest struct {
|
||
|
MerchantParameters string
|
||
|
Signature string
|
||
|
SignatureVersion string
|
||
|
}
|