Ctrl K
ComunidadGuías…

internal/store/postgres/bot_invoice.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package postgres

import (
	"context"
	"errors"

	"github.com/jackc/pgx/v5"

	"telesrv/internal/domain"
)

// Bot XTR invoices.
//
// payments.sendPaymentForm has no purpose field in this layer, so the price is
// resolved from the stored invoice: the client reports only the message id.
// The row is the price authority; bot_invoice_payments.go owns the atomic
// invoice, ledger, message and delivery transaction.

func scanBotInvoice(row interface {
	Scan(dest ...any) error
}) (domain.BotInvoice, error) {
	var (
		invoice domain.BotInvoice
		date    int32
		paidAt  int32
	)
	err := row.Scan(
		&invoice.ID, &invoice.BotUserID, &invoice.ChatID, &invoice.MessageID,
		&invoice.Title, &invoice.Description, &invoice.Amount, &invoice.Currency,
		&invoice.Payload, &invoice.ChargeID, &invoice.PayerID, &invoice.Paid,
		&invoice.Refunded, &paidAt, &date,
	)
	if err != nil {
		return domain.BotInvoice{}, err
	}
	invoice.PaidAt = int(paidAt)
	invoice.Date = int(date)
	return invoice, nil
}

const botInvoiceColumns = `id,bot_user_id,chat_id,message_id,title,description,
	amount,currency,payload,charge_id,payer_user_id,paid,refunded,paid_at,date`

// CreateBotInvoice stores a freshly sent invoice message. The message id is
// unique per bot chat, so a resend after a delete cannot orphan a paid invoice:
// the unique index rejects it rather than silently replacing history.
func (s *BotStore) CreateBotInvoice(ctx context.Context, invoice domain.BotInvoice) (domain.BotInvoice, error) {
	if !invoice.Valid() {
		return domain.BotInvoice{}, domain.ErrBotInvoiceInvalid
	}
	row := s.db.QueryRow(ctx, `INSERT INTO bot_invoices
		(bot_user_id,chat_id,message_id,title,description,amount,currency,payload,date)
		VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)
		RETURNING `+botInvoiceColumns,
		invoice.BotUserID, invoice.ChatID, invoice.MessageID, invoice.Title,
		invoice.Description, invoice.Amount, invoice.Currency, invoice.Payload, invoice.Date)
	stored, err := scanBotInvoice(row)
	if err != nil {
		return domain.BotInvoice{}, err
	}
	return stored, nil
}

// BotInvoiceByMessage resolves the invoice a client referenced through
// inputInvoiceMessage.
func (s *BotStore) BotInvoiceByMessage(ctx context.Context, botUserID, chatID int64, messageID int) (domain.BotInvoice, bool, error) {
	if botUserID <= 0 || chatID == 0 || messageID <= 0 {
		return domain.BotInvoice{}, false, domain.ErrBotInvoiceInvalid
	}
	row := s.db.QueryRow(ctx, `SELECT `+botInvoiceColumns+`
FROM bot_invoices WHERE bot_user_id=$1 AND chat_id=$2 AND message_id=$3`,
		botUserID, chatID, messageID)
	invoice, err := scanBotInvoice(row)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return domain.BotInvoice{}, false, nil
		}
		return domain.BotInvoice{}, false, err
	}
	return invoice, true, nil
}

// BotInvoiceByCharge resolves an invoice by the charge id the client reported,
// leaving it untouched so the caller can check ownership before flagging it.
func (s *BotStore) BotInvoiceByCharge(ctx context.Context, chargeID string) (domain.BotInvoice, bool, error) {
	if chargeID == "" {
		return domain.BotInvoice{}, false, domain.ErrBotInvoiceInvalid
	}
	row := s.db.QueryRow(ctx, `SELECT `+botInvoiceColumns+` FROM bot_invoices WHERE charge_id=$1`, chargeID)
	invoice, err := scanBotInvoice(row)
	if errors.Is(err, pgx.ErrNoRows) {
		return domain.BotInvoice{}, false, domain.ErrBotInvoiceNotFound
	}
	if err != nil {
		return domain.BotInvoice{}, false, err
	}
	return invoice, true, nil
}

var _ interface {
	CreateBotInvoice(context.Context, domain.BotInvoice) (domain.BotInvoice, error)
	BotInvoiceByMessage(context.Context, int64, int64, int) (domain.BotInvoice, bool, error)

	BotInvoiceByCharge(context.Context, string) (domain.BotInvoice, bool, error)
} = (*BotStore)(nil)