167 lines
4.9 KiB
Go
167 lines
4.9 KiB
Go
package payments
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid/v5"
|
|
"github.com/saveinmed/backend-go/internal/domain"
|
|
)
|
|
|
|
// AsaasGateway implements payment processing via Asaas (Brazilian gateway).
|
|
// Supports Pix, Boleto, and Credit Card with marketplace split.
|
|
type AsaasGateway struct {
|
|
APIKey string
|
|
WalletID string
|
|
Environment string // "sandbox" or "production"
|
|
MarketplaceCommission float64
|
|
}
|
|
|
|
func NewAsaasGateway(apiKey, walletID, environment string, commission float64) *AsaasGateway {
|
|
return &AsaasGateway{
|
|
APIKey: apiKey,
|
|
WalletID: walletID,
|
|
Environment: environment,
|
|
MarketplaceCommission: commission,
|
|
}
|
|
}
|
|
|
|
func (g *AsaasGateway) BaseURL() string {
|
|
if g.Environment == "production" {
|
|
return "https://api.asaas.com/v3"
|
|
}
|
|
return "https://sandbox.asaas.com/api/v3"
|
|
}
|
|
|
|
func (g *AsaasGateway) CreatePreference(ctx context.Context, order *domain.Order) (*domain.PaymentPreference, error) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
fee := int64(float64(order.TotalCents) * (g.MarketplaceCommission / 100))
|
|
|
|
// In production, this would:
|
|
// 1. Create customer if not exists
|
|
// 2. Create charge with split configuration
|
|
// 3. Return payment URL or Pix QR code
|
|
|
|
pref := &domain.PaymentPreference{
|
|
OrderID: order.ID,
|
|
Gateway: "asaas",
|
|
CommissionPct: g.MarketplaceCommission,
|
|
MarketplaceFee: fee,
|
|
SellerReceivable: order.TotalCents - fee,
|
|
PaymentURL: fmt.Sprintf("%s/checkout/%s", g.BaseURL(), order.ID.String()),
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
return pref, nil
|
|
}
|
|
|
|
// CreatePixPayment generates a Pix payment with QR code.
|
|
func (g *AsaasGateway) CreatePixPayment(ctx context.Context, order *domain.Order) (*domain.PixPaymentResult, error) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
fee := int64(float64(order.TotalCents) * (g.MarketplaceCommission / 100))
|
|
expiresAt := time.Now().Add(30 * time.Minute)
|
|
|
|
return &domain.PixPaymentResult{
|
|
PaymentID: uuid.Must(uuid.NewV7()).String(),
|
|
OrderID: order.ID,
|
|
Gateway: "asaas",
|
|
PixKey: "chave@saveinmed.com",
|
|
QRCode: fmt.Sprintf("00020126580014BR.GOV.BCB.PIX0136%s", order.ID.String()),
|
|
QRCodeBase64: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...", // Simulated
|
|
CopyPasta: fmt.Sprintf("00020126580014BR.GOV.BCB.PIX0136%s52040000", order.ID.String()),
|
|
AmountCents: order.TotalCents,
|
|
MarketplaceFee: fee,
|
|
SellerReceivable: order.TotalCents - fee,
|
|
ExpiresAt: expiresAt,
|
|
Status: "pending",
|
|
}, nil
|
|
}
|
|
|
|
// CreateBoletoPayment generates a Boleto payment.
|
|
func (g *AsaasGateway) CreateBoletoPayment(ctx context.Context, order *domain.Order, customer *domain.Customer) (*domain.BoletoPaymentResult, error) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
fee := int64(float64(order.TotalCents) * (g.MarketplaceCommission / 100))
|
|
dueDate := time.Now().AddDate(0, 0, 3) // 3 days
|
|
|
|
return &domain.BoletoPaymentResult{
|
|
PaymentID: uuid.Must(uuid.NewV7()).String(),
|
|
OrderID: order.ID,
|
|
Gateway: "asaas",
|
|
BoletoURL: fmt.Sprintf("%s/boleto/%s", g.BaseURL(), order.ID.String()),
|
|
BarCode: fmt.Sprintf("23793.38128 60000.000003 00000.000400 1 %d", order.TotalCents),
|
|
DigitableLine: fmt.Sprintf("23793381286000000000300000000401%d", order.TotalCents),
|
|
AmountCents: order.TotalCents,
|
|
MarketplaceFee: fee,
|
|
SellerReceivable: order.TotalCents - fee,
|
|
DueDate: dueDate,
|
|
Status: "pending",
|
|
}, nil
|
|
}
|
|
|
|
// ConfirmPayment checks payment status.
|
|
func (g *AsaasGateway) ConfirmPayment(ctx context.Context, paymentID string) (*domain.PaymentResult, error) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
return &domain.PaymentResult{
|
|
PaymentID: paymentID,
|
|
Status: "confirmed",
|
|
Gateway: "asaas",
|
|
Message: "Pagamento confirmado via Asaas",
|
|
ConfirmedAt: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// RefundPayment processes a refund.
|
|
func (g *AsaasGateway) RefundPayment(ctx context.Context, paymentID string, amountCents int64) (*domain.RefundResult, error) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
return &domain.RefundResult{
|
|
RefundID: uuid.Must(uuid.NewV7()).String(),
|
|
PaymentID: paymentID,
|
|
AmountCents: amountCents,
|
|
Status: "refunded",
|
|
RefundedAt: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// CreateSubaccount creates a seller subaccount for split payments.
|
|
func (g *AsaasGateway) CreateSubaccount(ctx context.Context, seller *domain.Company) (*domain.SellerPaymentAccount, error) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
}
|
|
|
|
return &domain.SellerPaymentAccount{
|
|
SellerID: seller.ID,
|
|
Gateway: "asaas",
|
|
AccountID: fmt.Sprintf("sub_%s", seller.ID.String()[:8]),
|
|
AccountType: "subaccount",
|
|
Status: "active",
|
|
CreatedAt: time.Now(),
|
|
}, nil
|
|
}
|