360 lines
7.8 KiB
Go
360 lines
7.8 KiB
Go
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
|
||
}
|