treasurer/dal/payway.go

360 lines
7.8 KiB
Go
Raw Normal View History

2023-05-16 16:21:56 +00:00
package dal
import (
"context"
"crypto/aes"
"encoding/hex"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"strings"
"time"
)
const BlockSize = 16
const PrivateKey = "SomePrivateKey1!" //16 byte
type PayWay struct {
ID string `bson:"_id"`
Name string `bson:"name"`
ShortName string `bson:"short_name"`
MerchantID string `bson:"merchant_id"`
WalletID string `bson:"wallet_id"`
Secret1 string `bson:"secret_1"`
Secret2 string `bson:"secret_2"`
Secret3 string `bson:"secret_3"`
PayoutTypeList []PayoutType `bson:"payout_type_list"` // TODO: перенести так же как и payment
CreatedAt time.Time `bson:"created_at"`
UpdatedAt time.Time `bson:"updated_at"`
}
type PayoutType struct {
Name string `bson:"name"`
IsEnabled bool `bson:"is_enabled"`
Currency string `bson:"currency"` // Валюта вывода: RUB, EUR, USD и т.д.
ApiId string `bson:"api_id"`
Commission float64 `bson:"commission"`
Minimum float64 `bson:"minimum"`
Maximum float64 `bson:"maximum"`
}
func (mc *MongoConnection) InsertPayWay(ctx context.Context, record PayWay) (string, error) {
now := time.Now()
if record.ID == "" {
record.ID = primitive.NewObjectIDFromTimestamp(now).Hex()
}
record.CreatedAt = now
record.UpdatedAt = now
if record.Secret1 != "" {
s, err := EncryptAES(PrivateKey, record.Secret1)
if err != nil {
mc.hl.Emit(ErrorInsertPayWay{err})
return "", err
}
record.Secret1 = s
}
if record.Secret2 != "" {
s, err := EncryptAES(PrivateKey, record.Secret2)
if err != nil {
mc.hl.Emit(ErrorInsertPayWay{err})
return "", err
}
record.Secret2 = s
}
if record.Secret3 != "" {
s, err := EncryptAES(PrivateKey, record.Secret3)
if err != nil {
mc.hl.Emit(ErrorInsertPayWay{err})
return "", err
}
record.Secret3 = s
}
result, err := mc.coll["payway"].InsertOne(ctx, record)
if err != nil {
mc.hl.Emit(ErrorInsertPayWay{err})
return "", err
}
mc.hl.Emit(InfoInsertPayWay{result})
return result.InsertedID.(string), nil
}
func (mc *MongoConnection) UpdatePayWay(ctx context.Context, record PayWay) error {
update := bson.M{"updated_at": time.Now()}
if record.Name != "" {
update["name"] = record.Name
}
if record.ShortName != "" {
update["short_name"] = record.ShortName
}
if record.MerchantID != "" {
update["merchant_id"] = record.MerchantID
}
if record.WalletID != "" {
update["wallet_id"] = record.WalletID
}
if record.PayoutTypeList != nil || len(record.PayoutTypeList) != 0 {
update["payout_type_list"] = record.PayoutTypeList
}
if record.Secret1 != "" {
s, err := EncryptAES(PrivateKey, record.Secret1)
if err != nil {
mc.hl.Emit(ErrorUpdatePayWay{err})
return err
}
update["secret_1"] = s
}
if record.Secret2 != "" {
s, err := EncryptAES(PrivateKey, record.Secret2)
if err != nil {
mc.hl.Emit(ErrorUpdatePayWay{err})
return err
}
update["secret_2"] = s
}
if record.Secret3 != "" {
s, err := EncryptAES(PrivateKey, record.Secret3)
if err != nil {
mc.hl.Emit(ErrorUpdatePayWay{err})
return err
}
update["secret_3"] = s
}
result, err := mc.coll["payway"].UpdateByID(ctx, record.ID, bson.D{{"$set", update}})
if err != nil {
mc.hl.Emit(ErrorUpdatePayWay{err})
return err
}
mc.hl.Emit(InfoUpdatePayWay{result})
return nil
}
func (mc *MongoConnection) GetPayWay(ctx context.Context, id string) (*PayWay, error) {
filter := bson.M{"_id": id}
var result PayWay
err := mc.coll["payway"].FindOne(ctx, filter).Decode(&result)
if err == mongo.ErrNoDocuments {
return nil, nil
} else {
if err != nil {
mc.hl.Emit(ErrorGetPayWay{err})
return nil, err
}
}
if result.Secret1 != "" {
s, err := DecryptAES(PrivateKey, result.Secret1)
if err != nil {
mc.hl.Emit(ErrorGetPayWay{err})
return nil, err
}
result.Secret1 = s
}
if result.Secret2 != "" {
s, err := DecryptAES(PrivateKey, result.Secret2)
if err != nil {
mc.hl.Emit(ErrorGetPayWay{err})
return nil, err
}
result.Secret2 = s
}
if result.Secret3 != "" {
s, err := DecryptAES(PrivateKey, result.Secret3)
if err != nil {
mc.hl.Emit(ErrorGetPayWay{err})
return nil, err
}
result.Secret3 = s
}
return &result, err
}
func (mc *MongoConnection) GetPayWayByName(ctx context.Context, name string) (*PayWay, error) {
filter := bson.M{"name": name}
var result PayWay
err := mc.coll["payway"].FindOne(ctx, filter).Decode(&result)
if err == mongo.ErrNoDocuments {
return nil, err
} else {
if err != nil {
mc.hl.Emit(ErrorGetPayWay{err})
return nil, err
}
}
if result.Secret1 != "" {
s, err := DecryptAES(PrivateKey, result.Secret1)
if err != nil {
mc.hl.Emit(ErrorGetPayWay{err})
return nil, err
}
result.Secret1 = s
}
if result.Secret2 != "" {
s, err := DecryptAES(PrivateKey, result.Secret2)
if err != nil {
mc.hl.Emit(ErrorGetPayWay{err})
return nil, err
}
result.Secret2 = s
}
if result.Secret3 != "" {
s, err := DecryptAES(PrivateKey, result.Secret3)
if err != nil {
mc.hl.Emit(ErrorGetPayWay{err})
return nil, err
}
result.Secret3 = s
}
return &result, err
}
//func (mc *MongoConnection) GetPayWayListByPayoutType(ctx context.Context, name string) ([]PayWay, error) {
//
//}
func (mc *MongoConnection) ListenPayWay(ctx context.Context, updateAction func(data *PayWay) error) {
operationTypes := []bson.D{{{"operationType", "update"}}}
matchStage := bson.D{
{"$match", bson.D{{"$or", operationTypes}}},
}
opts := options.ChangeStream().SetFullDocument(options.UpdateLookup)
changeStream, err := mc.coll["payway"].Watch(ctx,
mongo.Pipeline{matchStage}, opts)
if err != nil {
mc.hl.Emit(ErrorListenPayWay{err})
return
}
go func() {
// Перехват паники (см. ниже)
defer func() {
if err := recover(); err != nil {
mc.hl.Emit(ErrorListenPayWay{err.(error)})
}
}()
for changeStream.Next(ctx) {
// При закрытии приложения происходит паника (change_stream.go 561). context не важен...'
current := changeStream.Current
if err != nil {
mc.hl.Emit(ErrorListenPayWay{err})
return
}
var payWay PayWay
err = bson.Unmarshal(current.Lookup("fullDocument").Value, &payWay)
if err != nil {
mc.hl.Emit(ErrorListenPayWay{err})
return
}
err = updateAction(&payWay)
if err != nil {
mc.hl.Emit(ErrorListenPayWay{err})
continue
}
mc.hl.Emit(InfoListenPayWay{fmt.Sprintf("%v (%v) updated", payWay.ID, payWay.ShortName)})
}
if err = changeStream.Err(); err != nil {
mc.hl.Emit(ErrorListenPayWay{err})
}
}()
select {
case <-ctx.Done():
err = changeStream.Close(context.TODO())
if err != nil {
mc.hl.Emit(ErrorListenPayWay{err})
}
return
}
}
func EncryptAES(key, data string) (string, error) {
c, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}
// @TODO: Выглядит как костыль, но что поделать?
// Для большей безопасности можно генерировать случайную строку с указателем конца информации. example = "...`randS"
if len(data) < BlockSize {
x := BlockSize - len(data)
data += strings.Repeat("`", x)
}
out := make([]byte, len(data))
c.Encrypt(out, []byte(data))
return hex.EncodeToString(out), nil
}
func DecryptAES(key, data string) (string, error) {
ct, _ := hex.DecodeString(data)
c, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}
pt := make([]byte, len(ct))
c.Decrypt(pt, ct)
// @TODO: Выглядит как костыль, но что поделать?
result := strings.ReplaceAll(string(pt[:]), "`", "")
return result, nil
}