chore: add crypto payment

This commit is contained in:
2026-03-18 18:41:07 +01:00
parent a82097a228
commit adf68201fd
12 changed files with 788 additions and 2 deletions
+182
View File
@@ -0,0 +1,182 @@
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
}