Files
shop-go/internal/heleket/client.go
T

139 lines
3.4 KiB
Go

package heleket
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
const apiBase = "https://api.heleket.com"
type Client struct {
merchantID string
apiKey string
http *http.Client
}
type InvoiceRequest struct {
Amount string `json:"amount"`
Currency string `json:"currency"`
OrderID string `json:"order_id"`
URLReturn string `json:"url_return,omitempty"`
URLSuccess string `json:"url_success,omitempty"`
URLCallback string `json:"url_callback,omitempty"`
PayerEmail string `json:"payer_email,omitempty"`
Lifetime int `json:"lifetime,omitempty"`
AdditionalData string `json:"additional_data,omitempty"`
}
type Invoice struct {
UUID string `json:"uuid"`
OrderID string `json:"order_id"`
Amount string `json:"amount"`
PaymentStatus string `json:"payment_status"`
URL string `json:"url"`
ExpiredAt int64 `json:"expired_at"`
IsFinal bool `json:"is_final"`
}
type apiResponse struct {
State int `json:"state"`
Result json.RawMessage `json:"result"`
Errors json.RawMessage `json:"errors"`
}
func NewClient(merchantID, apiKey string) *Client {
return &Client{
merchantID: merchantID,
apiKey: apiKey,
http: &http.Client{Timeout: 30 * time.Second},
}
}
func (c *Client) Enabled() bool {
return c.merchantID != "" && c.apiKey != ""
}
func OrderExternalID(orderID int) string {
return fmt.Sprintf("shop-order-%d", orderID)
}
func ParseOrderExternalID(orderID string) (int, bool) {
const prefix = "shop-order-"
if len(orderID) <= len(prefix) || orderID[:len(prefix)] != prefix {
return 0, false
}
id, err := strconv.Atoi(orderID[len(prefix):])
return id, err == nil && id > 0
}
func FormatAmount(amount float64) string {
return strconv.FormatFloat(amount, 'f', 2, 64)
}
func (c *Client) CreateInvoice(ctx context.Context, req InvoiceRequest) (Invoice, error) {
body, err := encodeBody(req)
if err != nil {
return Invoice{}, err
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, apiBase+"/v1/payment", bytes.NewReader(body))
if err != nil {
return Invoice{}, err
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("merchant", c.merchantID)
httpReq.Header.Set("sign", SignBody(body, c.apiKey))
resp, err := c.http.Do(httpReq)
if err != nil {
return Invoice{}, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return Invoice{}, err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return Invoice{}, fmt.Errorf("heleket http %d: %s", resp.StatusCode, string(respBody))
}
var wrapped apiResponse
if err := json.Unmarshal(respBody, &wrapped); err != nil {
return Invoice{}, err
}
if wrapped.State != 0 {
return Invoice{}, fmt.Errorf("heleket api state %d: %s", wrapped.State, string(wrapped.Errors))
}
var invoice Invoice
if err := json.Unmarshal(wrapped.Result, &invoice); err != nil {
return Invoice{}, err
}
return invoice, nil
}
// MapPaymentStatus maps Heleket payment_status to shop order status.
func MapPaymentStatus(paymentStatus string) string {
switch paymentStatus {
case "paid", "paid_over":
return "paid"
case "cancel", "fail", "system_fail":
return "cancelled"
case "refund_paid":
return "refunded"
default:
return "unpaid"
}
}
func IsPaid(paymentStatus string) bool {
return paymentStatus == "paid" || paymentStatus == "paid_over"
}