camper/pkg/redsys/client.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
}