/* * SPDX-FileCopyrightText: 2023 jordi fita mas * 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 }