package services import ( "bytes" "crypto/hmac" "crypto/sha512" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "sort" "time" ) var httpClient = &http.Client{Timeout: 30 * time.Second} type NowPaymentsClient struct { APIKey string IPNSecret string BaseURL string } func NewNowPaymentsClient(apiKey, ipnSecret string) *NowPaymentsClient { return &NowPaymentsClient{ APIKey: apiKey, IPNSecret: ipnSecret, BaseURL: "https://api.nowpayments.io/v1", } } type CreatePaymentRequest struct { PriceAmount float64 `json:"price_amount"` PriceCurrency string `json:"price_currency"` PayCurrency string `json:"pay_currency"` OrderID string `json:"order_id"` IPNCallbackURL string `json:"ipn_callback_url"` } type CreatePaymentResponse struct { PaymentID json.Number `json:"payment_id"` PayAddress string `json:"pay_address"` PayAmount json.Number `json:"pay_amount"` PayCurrency string `json:"pay_currency"` PriceAmount json.Number `json:"price_amount"` PriceCurrency string `json:"price_currency"` Status string `json:"payment_status"` OrderID string `json:"order_id"` } type IPNPayload struct { PaymentID json.Number `json:"payment_id"` PaymentStatus string `json:"payment_status"` PayAddress string `json:"pay_address"` PriceAmount json.Number `json:"price_amount"` PriceCurrency string `json:"price_currency"` PayAmount json.Number `json:"pay_amount"` PayCurrency string `json:"pay_currency"` OrderID string `json:"order_id"` } func (c *NowPaymentsClient) CreatePayment(req *CreatePaymentRequest) (*CreatePaymentResponse, error) { body, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } httpReq, err := http.NewRequest("POST", c.BaseURL+"/payment", bytes.NewBuffer(body)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("x-api-key", c.APIKey) httpReq.Header.Set("Content-Type", "application/json") resp, err := httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("nowpayments error (%d): %s", resp.StatusCode, string(respBody)) } var result CreatePaymentResponse if err := json.Unmarshal(respBody, &result); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &result, nil } type GetPaymentStatusResponse struct { PaymentID json.Number `json:"payment_id"` PaymentStatus string `json:"payment_status"` PayAddress string `json:"pay_address"` PriceAmount json.Number `json:"price_amount"` PriceCurrency string `json:"price_currency"` PayAmount json.Number `json:"pay_amount"` ActuallyPaid json.Number `json:"actually_paid"` PayCurrency string `json:"pay_currency"` OrderID string `json:"order_id"` } func (c *NowPaymentsClient) GetPaymentStatus(paymentID string) (*GetPaymentStatusResponse, error) { httpReq, err := http.NewRequest("GET", c.BaseURL+"/payment/"+paymentID, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("x-api-key", c.APIKey) resp, err := httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("nowpayments error (%d): %s", resp.StatusCode, string(respBody)) } var result GetPaymentStatusResponse if err := json.Unmarshal(respBody, &result); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &result, nil } func (c *NowPaymentsClient) VerifyIPN(body []byte, signature string) bool { if c.IPNSecret == "" || signature == "" { return false } var payload map[string]interface{} if err := json.Unmarshal(body, &payload); err != nil { return false } sorted := sortedJSON(payload) mac := hmac.New(sha512.New, []byte(c.IPNSecret)) mac.Write(sorted) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) } func sortedJSON(data map[string]interface{}) []byte { keys := make([]string, 0, len(data)) for k := range data { keys = append(keys, k) } sort.Strings(keys) ordered := make([]byte, 0, 256) ordered = append(ordered, '{') for i, k := range keys { if i > 0 { ordered = append(ordered, ',') } key, _ := json.Marshal(k) val, _ := json.Marshal(data[k]) ordered = append(ordered, key...) ordered = append(ordered, ':') ordered = append(ordered, val...) } ordered = append(ordered, '}') return ordered }