treasurer/internal/payment_provider/alchemy/provider.go

124 lines
3.9 KiB
Go
Raw Normal View History

2025-07-21 11:48:53 +00:00
package alchemy
import (
"context"
"fmt"
2025-07-21 11:48:53 +00:00
"gitea.pena/PenaSide/treasurer/internal/errors"
"gitea.pena/PenaSide/treasurer/internal/models"
"gitea.pena/PenaSide/treasurer/internal/models/alchemy"
2025-07-21 15:03:53 +00:00
"gitea.pena/PenaSide/treasurer/internal/repository"
2025-07-21 11:48:53 +00:00
"github.com/gofiber/fiber/v2"
2025-07-22 11:28:27 +00:00
"github.com/google/uuid"
2025-07-21 11:48:53 +00:00
"go.uber.org/zap"
2025-07-22 11:16:36 +00:00
"strconv"
"strings"
"time"
2025-07-21 11:48:53 +00:00
)
const ProviderName = "alchemy"
type Config struct {
WalletAddress string `json:"walletAddress"`
2025-07-21 11:48:53 +00:00
}
type Provider struct {
2025-07-21 15:03:53 +00:00
repository *repository.PaymentRepository
logger *zap.Logger
config *Config
2025-07-21 11:48:53 +00:00
}
type Deps struct {
2025-07-21 15:25:32 +00:00
Repository *repository.PaymentRepository
2025-07-21 15:03:53 +00:00
Logger *zap.Logger
Config *Config
2025-07-21 11:48:53 +00:00
}
func New(deps Deps) *Provider {
return &Provider{
logger: deps.Logger,
config: deps.Config,
}
}
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) {
2025-07-22 11:16:36 +00:00
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{
2025-07-22 11:28:27 +00:00
PaymentID: uuid.NewString(),
2025-07-22 11:16:36 +00:00
UserID: req["user_id"],
ClientIP: req["client_ip"],
Currency: req["currency"],
2025-07-22 11:28:27 +00:00
Type: models.PaymentTypeAlchemy,
2025-07-22 11:16:36 +00:00
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
2025-07-21 11:48:53 +00:00
}
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
}
2025-07-22 11:28:27 +00:00
// todo нужно подумать как сделать так если сумма оплаты оказалась чуть больше ожидаемой...
2025-07-22 11:16:36 +00:00
payment, err := p.repository.FindByWalletsAndAmount(ctx.Context(), act.ToAddress, act.FromAddress, act.Value)
2025-07-21 15:03:53 +00:00
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()))
}
2025-07-22 11:28:27 +00:00
_, err = p.repository.SetPaymentStatus(ctx.Context(), payment.PaymentID, models.PaymentStatusSuccessfully)
2025-07-21 15:03:53 +00:00
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed to set payment complete: %s", err.Error()))
}
}
return ctx.SendStatus(fiber.StatusOK)
2025-07-21 11:48:53 +00:00
}