mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 19:12:54 +00:00
- 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.
91 lines
3.0 KiB
Go
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
|
|
}
|