2025-07-21 11:48:53 +00:00
|
|
|
package alchemy
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2025-07-21 14:31:44 +00:00
|
|
|
"fmt"
|
2025-07-21 11:48:53 +00:00
|
|
|
"gitea.pena/PenaSide/treasurer/internal/errors"
|
|
|
|
"gitea.pena/PenaSide/treasurer/internal/models"
|
2025-07-21 14:31:44 +00:00
|
|
|
"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 {
|
2025-07-21 14:31:44 +00:00
|
|
|
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 {
|
2025-07-21 14:31:44 +00:00
|
|
|
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()))
|
|
|
|
}
|
2025-07-21 14:31:44 +00:00
|
|
|
}
|
|
|
|
return ctx.SendStatus(fiber.StatusOK)
|
2025-07-21 11:48:53 +00:00
|
|
|
}
|