Files
telecom-platform/apps/api-server/internal/payment/gateway_stripe_methods.go
T
nutcas3 cb1d7171e0 feat: Add PaymentController with modular handlers for payment operations, refunds, and webhook processing
- Add PaymentController struct with paymentService/db dependencies and NewPaymentController constructor
- Add SetupRoutes with /payments endpoints (POST /top-up, POST/DELETE/GET /payment-methods, GET /transactions/:id, POST /transactions/:id/refund) and /webhooks/stripe endpoint
- Add ProcessTopUp handler with amount/paymentMethodId validation, subscriberID authentication, and PaymentService.
2026-04-24 08:53:34 +03:00

91 lines
3.0 KiB
Go

package payment
import (
"context"
"crypto/hmac"
"fmt"
"strings"
"time"
)
// CreatePaymentMethod creates a payment method in Stripe and attaches it to the customer.
func (s *StripeGateway) CreatePaymentMethod(ctx context.Context, req *CreatePaymentMethodRequest) (*PaymentMethod, error) {
pmData := map[string]any{
"type": s.mapPaymentMethodType(req.Type),
"card": map[string]string{"token": req.Token},
"metadata": req.Metadata,
}
pmResp, err := s.makeStripeRequest(ctx, "POST", "/v1/payment_methods", pmData)
if err != nil {
return nil, fmt.Errorf("failed to create payment method: %w", err)
}
pm, ok := pmResp.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid payment method response")
}
attachData := map[string]any{"customer": req.CustomerID}
if _, err := s.makeStripeRequest(ctx, "POST", "/v1/payment_methods/"+pm["id"].(string)+"/attach", attachData); err != nil {
return nil, fmt.Errorf("failed to attach payment method: %w", err)
}
if req.IsDefault {
customerData := map[string]any{
"invoice_settings": map[string]string{"default_payment_method": pm["id"].(string)},
}
if _, err := s.makeStripeRequest(ctx, "POST", "/v1/customers/"+req.CustomerID, customerData); err != nil {
return nil, fmt.Errorf("failed to set default payment method: %w", err)
}
}
card, _ := pm["card"].(map[string]any)
createdAt, _ := time.Parse(time.RFC3339, pm["created"].(string))
return &PaymentMethod{
ID: pm["id"].(string),
Type: req.Type,
CustomerID: req.CustomerID,
Last4: card["last4"].(string),
Brand: card["brand"].(string),
ExpiryMonth: int(card["exp_month"].(float64)),
ExpiryYear: int(card["exp_year"].(float64)),
IsDefault: req.IsDefault,
CreatedAt: createdAt,
Metadata: req.Metadata,
}, nil
}
// DeletePaymentMethod deletes (detaches) a payment method from Stripe.
func (s *StripeGateway) DeletePaymentMethod(ctx context.Context, paymentMethodID string) error {
if _, err := s.makeStripeRequest(ctx, "DELETE", "/v1/payment_methods/"+paymentMethodID, nil); err != nil {
return fmt.Errorf("failed to delete payment method: %w", err)
}
return nil
}
// ValidateWebhook validates a Stripe webhook signature using the configured secret.
func (s *StripeGateway) ValidateWebhook(ctx context.Context, signature string, body []byte) (bool, error) {
if s.webhookSecret == "" {
return false, fmt.Errorf("webhook secret not configured")
}
parts := strings.Split(signature, ",")
var timestamp, v1Signature string
for _, part := range parts {
part = strings.TrimSpace(part)
if after, ok := strings.CutPrefix(part, "t="); ok {
timestamp = after
} else if after, ok := strings.CutPrefix(part, "v1="); ok {
v1Signature = after
}
}
if timestamp == "" || v1Signature == "" {
return false, fmt.Errorf("invalid signature format")
}
expectedSignature := s.computeHMACSHA256(timestamp, string(body), s.webhookSecret)
return hmac.Equal([]byte(v1Signature), []byte(expectedSignature)), nil
}