package billing import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" ) const wiseBaseURL = "https://api.transferwise.com" type WiseClient struct { apiKey string profileID string client *http.Client } func NewWiseClient() *WiseClient { return &WiseClient{ apiKey: os.Getenv("WISE_API_KEY"), profileID: os.Getenv("WISE_PROFILE_ID"), client: &http.Client{}, } } func (c *WiseClient) IsConfigured() bool { return c.apiKey != "" && c.profileID != "" } type Quote struct { ID string `json:"id"` SourceAmount float64 `json:"sourceAmount"` TargetAmount float64 `json:"targetAmount"` Rate float64 `json:"rate"` Fee float64 `json:"fee"` SourceCurrency string `json:"sourceCurrency"` TargetCurrency string `json:"targetCurrency"` } type Transfer struct { ID int64 `json:"id"` Status string `json:"status"` SourceValue float64 `json:"sourceValue"` TargetValue float64 `json:"targetValue"` } func (c *WiseClient) CreateQuote(ctx context.Context, sourceCurrency, targetCurrency string, sourceAmountCents int) (*Quote, error) { payload := map[string]any{ "sourceCurrency": sourceCurrency, "targetCurrency": targetCurrency, "sourceAmount": float64(sourceAmountCents) / 100, "profile": c.profileID, } body, err := json.Marshal(payload) if err != nil { return nil, err } req, err := http.NewRequestWithContext(ctx, "POST", wiseBaseURL+"/v3/quotes", bytes.NewReader(body)) if err != nil { return nil, err } c.setHeaders(req) resp, err := c.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("quote creation failed (status %d): %s", resp.StatusCode, string(respBody)) } var quote Quote if err := json.Unmarshal(respBody, "e); err != nil { return nil, err } return "e, nil } func (c *WiseClient) CreateTransfer(ctx context.Context, quoteID string, recipientID int64, reference string) (*Transfer, error) { payload := map[string]any{ "targetAccount": recipientID, "quoteUuid": quoteID, "customerTransactionId": reference, "details": map[string]any{"reference": reference}, } body, err := json.Marshal(payload) if err != nil { return nil, err } req, err := http.NewRequestWithContext(ctx, "POST", wiseBaseURL+"/v1/transfers", bytes.NewReader(body)) if err != nil { return nil, err } c.setHeaders(req) resp, err := c.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { return nil, fmt.Errorf("transfer creation failed (status %d): %s", resp.StatusCode, string(respBody)) } var transfer Transfer if err := json.Unmarshal(respBody, &transfer); err != nil { return nil, err } return &transfer, nil } func (c *WiseClient) FundTransfer(ctx context.Context, transferID int64) error { url := fmt.Sprintf("%s/v3/profiles/%s/transfers/%d/payments", wiseBaseURL, c.profileID, transferID) payload := map[string]any{"type": "BALANCE"} body, err := json.Marshal(payload) if err != nil { return err } req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) if err != nil { return err } c.setHeaders(req) resp, err := c.client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { respBody, _ := io.ReadAll(resp.Body) return fmt.Errorf("funding failed (status %d): %s", resp.StatusCode, string(respBody)) } return nil } func (c *WiseClient) GetTransferStatus(ctx context.Context, transferID int64) (string, error) { url := fmt.Sprintf("%s/v1/transfers/%d", wiseBaseURL, transferID) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return "", err } c.setHeaders(req) resp, err := c.client.Do(req) if err != nil { return "", err } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return "", err } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("get transfer failed (status %d): %s", resp.StatusCode, string(respBody)) } var transfer Transfer if err := json.Unmarshal(respBody, &transfer); err != nil { return "", err } return transfer.Status, nil } func (c *WiseClient) setHeaders(req *http.Request) { req.Header.Set("Authorization", "Bearer "+c.apiKey) req.Header.Set("Content-Type", "application/json") }