mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-05 03:02:56 +00:00
hot fix #1
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
package eshop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"fotbal-club/internal/config"
|
||||
"fotbal-club/internal/models"
|
||||
"fotbal-club/pkg/httpclient"
|
||||
)
|
||||
|
||||
// RevolutService handles Revolut payment integration
|
||||
type RevolutService struct {
|
||||
cfg *config.Config
|
||||
client *http.Client
|
||||
apiBase string
|
||||
}
|
||||
|
||||
// Revolut API structures
|
||||
type RevolutOrderRequest struct {
|
||||
Amount int64 `json:"amount"` // Amount in minor currency units (cents)
|
||||
Currency string `json:"currency"` // 3-letter currency code (EUR, CZK, etc.)
|
||||
Description string `json:"description"` // Order description
|
||||
MerchantOrderID string `json:"merchant_order_id,omitempty"` // Your internal order ID
|
||||
Customer *RevolutCustomer `json:"customer,omitempty"`
|
||||
}
|
||||
|
||||
type RevolutCustomer struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
FirstName string `json:"first_name,omitempty"`
|
||||
LastName string `json:"last_name,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
}
|
||||
|
||||
type RevolutOrderResponse struct {
|
||||
ID string `json:"id"`
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"` // PENDING, COMPLETED, FAILED
|
||||
CheckoutURL string `json:"checkout_url,omitempty"`
|
||||
MerchantOrderID string `json:"merchant_order_id,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type RevolutWebhookPayload struct {
|
||||
Type string `json:"type"` // ORDER_COMPLETED, ORDER_CANCELLED, etc.
|
||||
OrderID string `json:"order_id"`
|
||||
Order RevolutOrderResponse `json:"order"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
func NewRevolutService(cfg *config.Config) *RevolutService {
|
||||
base := "https://sandbox-merchant.revolut.com/api"
|
||||
if strings.ToLower(strings.TrimSpace(cfg.RevolutEnvironment)) == "production" {
|
||||
base = "https://merchant.revolut.com/api"
|
||||
}
|
||||
|
||||
return &RevolutService{
|
||||
cfg: cfg,
|
||||
client: httpclient.DefaultClient(),
|
||||
apiBase: base,
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePayment initializes a Revolut payment for the given order
|
||||
func (s *RevolutService) CreatePayment(order *models.EshopOrder) (*PaymentResult, error) {
|
||||
if !s.cfg.RevolutEnabled {
|
||||
return nil, fmt.Errorf("Revolut payment provider is disabled")
|
||||
}
|
||||
|
||||
apiKey := strings.TrimSpace(s.cfg.RevolutAPIKey)
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("Revolut API key not configured")
|
||||
}
|
||||
|
||||
// Parse billing address to get customer information
|
||||
var billingAddress map[string]interface{}
|
||||
if order.BillingAddressJSON != "" {
|
||||
if err := json.Unmarshal([]byte(order.BillingAddressJSON), &billingAddress); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse billing address: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create order request
|
||||
orderReq := RevolutOrderRequest{
|
||||
Amount: order.TotalAmountCents,
|
||||
Currency: order.Currency,
|
||||
Description: fmt.Sprintf("Objednávka %s", order.OrderNumber),
|
||||
MerchantOrderID: order.OrderNumber,
|
||||
Customer: &RevolutCustomer{
|
||||
Email: order.Email,
|
||||
FirstName: getStringFromMap(billingAddress, "first_name"),
|
||||
LastName: getStringFromMap(billingAddress, "last_name"),
|
||||
Phone: getStringFromMap(billingAddress, "phone"),
|
||||
},
|
||||
}
|
||||
|
||||
// Convert to JSON
|
||||
jsonData, err := json.Marshal(orderReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal Revolut order request: %w", err)
|
||||
}
|
||||
|
||||
// Create HTTP request
|
||||
url := fmt.Sprintf("%s/1.0/orders", s.apiBase)
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Revolut order request: %w", err)
|
||||
}
|
||||
|
||||
// Set headers
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
|
||||
// Execute request
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute Revolut order request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Revolut order response: %w", err)
|
||||
}
|
||||
|
||||
// Check status code
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("Revolut API error: status %d, response: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var orderResp RevolutOrderResponse
|
||||
if err := json.Unmarshal(body, &orderResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Revolut order response: %w", err)
|
||||
}
|
||||
|
||||
// Store raw payload for debugging
|
||||
rawPayload := string(body)
|
||||
|
||||
// Return payment result
|
||||
return &PaymentResult{
|
||||
RedirectURL: orderResp.CheckoutURL,
|
||||
ProviderPaymentID: orderResp.ID,
|
||||
RawPayloadJSON: rawPayload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyWebhook verifies and parses incoming Revolut webhooks
|
||||
func (s *RevolutService) VerifyWebhook(payload []byte, signature string) (bool, error) {
|
||||
webhookSecret := strings.TrimSpace(s.cfg.RevolutWebhookSecret)
|
||||
if webhookSecret == "" {
|
||||
return false, fmt.Errorf("Revolut webhook secret not configured")
|
||||
}
|
||||
|
||||
// TODO: Implement webhook signature verification
|
||||
// Revolut uses HMAC-SHA256 for webhook signatures
|
||||
// For now, we'll accept all webhooks in sandbox mode
|
||||
if strings.ToLower(strings.TrimSpace(s.cfg.RevolutEnvironment)) == "sandbox" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// In production, verify the signature here
|
||||
// This would involve computing HMAC-SHA256 of the payload with the webhook secret
|
||||
// and comparing it with the provided signature
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ParseWebhook parses a Revolut webhook payload
|
||||
func (s *RevolutService) ParseWebhook(payload []byte) (*RevolutWebhookPayload, error) {
|
||||
var webhook RevolutWebhookPayload
|
||||
if err := json.Unmarshal(payload, &webhook); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Revolut webhook: %w", err)
|
||||
}
|
||||
return &webhook, nil
|
||||
}
|
||||
|
||||
// GetOrderStatus retrieves the current status of a Revolut order
|
||||
func (s *RevolutService) GetOrderStatus(revolutOrderID string) (*RevolutOrderResponse, error) {
|
||||
if !s.cfg.RevolutEnabled {
|
||||
return nil, fmt.Errorf("Revolut payment provider is disabled")
|
||||
}
|
||||
|
||||
apiKey := strings.TrimSpace(s.cfg.RevolutAPIKey)
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("Revolut API key not configured")
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/1.0/orders/%s", s.apiBase, revolutOrderID)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Revolut status request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute Revolut status request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Revolut status response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Revolut API error: status %d, response: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var orderResp RevolutOrderResponse
|
||||
if err := json.Unmarshal(body, &orderResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Revolut status response: %w", err)
|
||||
}
|
||||
|
||||
return &orderResp, nil
|
||||
}
|
||||
|
||||
// RefundOrder processes a refund for a Revolut order
|
||||
func (s *RevolutService) RefundOrder(revolutOrderID string, amount int64, reason string) error {
|
||||
if !s.cfg.RevolutEnabled {
|
||||
return fmt.Errorf("Revolut payment provider is disabled")
|
||||
}
|
||||
|
||||
apiKey := strings.TrimSpace(s.cfg.RevolutAPIKey)
|
||||
if apiKey == "" {
|
||||
return fmt.Errorf("Revolut API key not configured")
|
||||
}
|
||||
|
||||
// Refund request structure
|
||||
refundReq := map[string]interface{}{
|
||||
"amount": amount,
|
||||
"reason": reason,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(refundReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal Revolut refund request: %w", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/1.0/orders/%s/refund", s.apiBase, revolutOrderID)
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Revolut refund request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute Revolut refund request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("Revolut refund API error: status %d, response: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStringFromMap safely extracts a string value from a map, returning empty string if not found or not a string
|
||||
func getStringFromMap(m map[string]interface{}, key string) string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
if val, ok := m[key]; ok {
|
||||
if str, ok := val.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user