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
|
|||
|
}
|