treasurer/dal/payway.go
2023-05-16 19:21:56 +03:00

360 lines
7.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}