treasurer/internal/payment_provider/alchemy/provider.go
Pasha e3df922f0a
Some checks failed
Lint / Lint (push) Failing after 53s
fix after tests
2025-07-22 15:11:45 +03:00

125 lines
4.0 KiB
Go

package alchemy
import (
"context"
"fmt"
"gitea.pena/PenaSide/treasurer/internal/errors"
"gitea.pena/PenaSide/treasurer/internal/models"
"gitea.pena/PenaSide/treasurer/internal/models/alchemy"
"gitea.pena/PenaSide/treasurer/internal/repository"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"go.uber.org/zap"
"strconv"
"strings"
"time"
)
const ProviderName = "alchemy"
type Config struct {
WalletAddress string `json:"walletAddress"`
}
type Provider struct {
repository *repository.PaymentRepository
logger *zap.Logger
config *Config
}
type Deps struct {
Repository *repository.PaymentRepository
Logger *zap.Logger
Config *Config
}
func New(deps Deps) *Provider {
return &Provider{
logger: deps.Logger,
config: deps.Config,
repository: deps.Repository,
}
}
func (p *Provider) GetName() string {
return ProviderName
}
func (p *Provider) GetSupportedPaymentMethods() []models.PaymentType {
return []models.PaymentType{models.PaymentTypeAlchemy}
}
func (p *Provider) CreateInvoice(ctx context.Context, req map[string]string) (string, errors.Error) {
amountStr := req["cryptoAmount"]
fromWallet := req["fromWalletAddress"]
if amountStr == "" || fromWallet == "" {
p.logger.Error("amount or fromWallet address is empty", zap.String("fromWalletAddress", fromWallet), zap.String("cryptoAmount", amountStr))
return "", errors.NewWithMessage("cryptoAmount and fromWalletAddress required", errors.ErrInvalidArgs)
}
cryptoAmount, err := strconv.ParseFloat(amountStr, 64)
if err != nil {
p.logger.Error("failed to parse cryptoAmount from wallet address", zap.Error(err))
return "", errors.NewWithMessage("invalid cryptoAmount", errors.ErrInvalidArgs)
}
now := time.Now()
payment := &models.Payment{
PaymentID: uuid.NewString(),
UserID: req["user_id"],
ClientIP: req["client_ip"],
Currency: req["currency"],
Type: models.PaymentTypeAlchemy,
Status: models.PaymentStatusWaiting,
CreatedAt: now,
UpdatedAt: now,
ToWalletAddress: p.config.WalletAddress,
FromWalletAddress: fromWallet,
CryptoAmount: cryptoAmount,
}
var callbackHosts []string
if val, ok := req["callback_host_grpc"]; ok && val != "" {
callbackHosts = strings.Split(val, ",")
}
payment.CallbackHostGRPC = callbackHosts
_, err = p.repository.Insert(ctx, payment)
if err != nil {
p.logger.Error("failed to insert payment into database", zap.Error(err))
return "", errors.NewWithMessage(fmt.Sprintf("failed to insert payment into database: %v", err), errors.ErrInternalError)
}
return p.config.WalletAddress, nil
}
func (p *Provider) RegisterWebhookHandlers(router fiber.Router) {
router.Post("/webhook/alchemy", p.handleWebhook)
}
func (p *Provider) handleWebhook(ctx *fiber.Ctx) error {
var payload alchemy.AlchemyAddressActivityWebhook
if err := ctx.BodyParser(&payload); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("failed to parse Alchemy webhook: %s", err.Error()))
}
for _, act := range payload.Event.Activity {
if act.ToAddress != p.config.WalletAddress {
continue
}
// todo нужно подумать как сделать так если сумма оплаты оказалась чуть больше ожидаемой...
payment, err := p.repository.FindByWalletsAndAmount(ctx.Context(), act.ToAddress, act.FromAddress, act.Value)
if err != nil {
if err.Type() == errors.ErrNotFound {
return ctx.Status(fiber.StatusNotFound).SendString(fmt.Sprintf("payment not found: %s", err.Error()))
}
return ctx.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("internal error while searching payment: %s", err.Error()))
}
_, err = p.repository.SetPaymentStatus(ctx.Context(), payment.PaymentID, models.PaymentStatusSuccessfully)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed to set payment complete: %s", err.Error()))
}
}
return ctx.SendStatus(fiber.StatusOK)
}