github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/pilvytis/api.go (about)

     1  /*
     2   * Copyright (C) 2020 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package pilvytis
    19  
    20  import (
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"net/url"
    27  	"runtime"
    28  	"strings"
    29  
    30  	"github.com/ethereum/go-ethereum/common"
    31  	"github.com/mysteriumnetwork/node/config"
    32  	"github.com/mysteriumnetwork/node/core/location/locationstate"
    33  	"github.com/mysteriumnetwork/node/identity"
    34  	"github.com/mysteriumnetwork/node/metadata"
    35  	"github.com/mysteriumnetwork/node/requests"
    36  	"github.com/mysteriumnetwork/payments/exchange"
    37  )
    38  
    39  // API is object which exposes pilvytis API.
    40  type API struct {
    41  	req               *requests.HTTPClient
    42  	channelCalculator addressProvider
    43  	signer            identity.SignerFactory
    44  	lp                locationProvider
    45  	url               string
    46  }
    47  
    48  type locationProvider interface {
    49  	GetOrigin() locationstate.Location
    50  }
    51  
    52  const exchangeEndpoint = "api/v1/payment/exchange-rate"
    53  
    54  type addressProvider interface {
    55  	GetActiveChannelAddress(chainID int64, id common.Address) (common.Address, error)
    56  }
    57  
    58  // NewAPI returns a new API instance.
    59  func NewAPI(hc *requests.HTTPClient, url string, signer identity.SignerFactory, lp locationProvider, cc addressProvider) *API {
    60  	if strings.HasSuffix(url, "/") {
    61  		url = strings.TrimSuffix(url, "/")
    62  	}
    63  
    64  	return &API{
    65  		req:               hc,
    66  		signer:            signer,
    67  		url:               url,
    68  		lp:                lp,
    69  		channelCalculator: cc,
    70  	}
    71  }
    72  
    73  // PaymentOrderOptions represents pilvytis payment order options
    74  type PaymentOrderOptions struct {
    75  	Minimum   float64   `json:"minimum"`
    76  	Suggested []float64 `json:"suggested"`
    77  }
    78  
    79  // GetMystExchangeRate returns the exchange rate for myst to other currencies.
    80  func (a *API) GetMystExchangeRate() (map[string]float64, error) {
    81  	req, err := requests.NewGetRequest(a.url, exchangeEndpoint, nil)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	var resp map[string]float64
    87  	return resp, a.sendRequestAndParseResp(req, &resp)
    88  }
    89  
    90  // GetMystExchangeRateFor returns the exchange rate for myst to for a given currency currencies.
    91  func (a *API) GetMystExchangeRateFor(curr string) (float64, error) {
    92  	rates, err := a.GetMystExchangeRate()
    93  	if err != nil {
    94  		return 0, err
    95  	}
    96  	rate, ok := rates[strings.ToUpper(curr)]
    97  	if !ok {
    98  		return 0, errors.New("currency not supported")
    99  	}
   100  	return rate, nil
   101  }
   102  
   103  // GatewaysResponse holds data about payment gateways.
   104  type GatewaysResponse struct {
   105  	Name         string              `json:"name"`
   106  	OrderOptions PaymentOrderOptions `json:"order_options"`
   107  	Currencies   []string            `json:"currencies"`
   108  }
   109  
   110  // GetPaymentGateways returns a slice of supported gateways.
   111  func (a *API) GetPaymentGateways(optionsCurrency exchange.Currency) ([]GatewaysResponse, error) {
   112  	query := url.Values{}
   113  	query.Set("options_currency", string(optionsCurrency))
   114  	req, err := requests.NewGetRequest(a.url, "api/v2/payment/gateways", query)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	var resp []GatewaysResponse
   120  	return resp, a.sendRequestAndParseResp(req, &resp)
   121  }
   122  
   123  // GatewayOrderResponse is a response for a payment order.
   124  type GatewayOrderResponse struct {
   125  	ID     string             `json:"id"`
   126  	Status PaymentOrderStatus `json:"status"`
   127  
   128  	Identity       string `json:"identity"`
   129  	ChainID        int64  `json:"chain_id"`
   130  	ChannelAddress string `json:"channel_address"`
   131  
   132  	GatewayName string `json:"gateway_name"`
   133  
   134  	ReceiveMYST string `json:"receive_myst"`
   135  	PayAmount   string `json:"pay_amount"`
   136  	PayCurrency string `json:"pay_currency"`
   137  
   138  	Country string `json:"country"`
   139  	State   string `json:"state"`
   140  
   141  	Currency      string `json:"currency"`
   142  	ItemsSubTotal string `json:"items_sub_total"`
   143  	TaxRate       string `json:"tax_rate"`
   144  	TaxSubTotal   string `json:"tax_sub_total"`
   145  	OrderTotal    string `json:"order_total"`
   146  
   147  	PublicGatewayData json.RawMessage `json:"public_gateway_data"`
   148  }
   149  
   150  // PaymentOrderStatus defines a status for a payment order.
   151  type PaymentOrderStatus string
   152  
   153  const (
   154  	// PaymentOrderStatusInitial defines a status for any payment orders not sent to the
   155  	// payment service provider.
   156  	PaymentOrderStatusInitial PaymentOrderStatus = "initial"
   157  	// PaymentOrderStatusNew defines a status for any payment orders sent to the
   158  	// payment service provider.
   159  	PaymentOrderStatusNew PaymentOrderStatus = "new"
   160  	// PaymentOrderStatusPaid defines a status for any payment orders paid and processed by the
   161  	// payment service provider.
   162  	PaymentOrderStatusPaid PaymentOrderStatus = "paid"
   163  	// PaymentOrderStatusFailed defines a status for any payment orders sent to the
   164  	// payment service provider which have failed.
   165  	PaymentOrderStatusFailed PaymentOrderStatus = "failed"
   166  )
   167  
   168  // Incomplete tells if the order is incomplete and its status needs to be tracked for updates.
   169  func (p PaymentOrderStatus) Incomplete() bool {
   170  	switch p {
   171  	case PaymentOrderStatusPaid, PaymentOrderStatusFailed:
   172  		return false
   173  	default:
   174  		return true
   175  	}
   176  }
   177  
   178  // Paid tells if the order has been paid for.
   179  func (p PaymentOrderStatus) Paid() bool {
   180  	return p == PaymentOrderStatusPaid
   181  }
   182  
   183  // Status returns a current status.
   184  // Its part of StatusProvider interface.
   185  func (p PaymentOrderStatus) Status() string {
   186  	return string(p)
   187  }
   188  
   189  // GetPaymentGatewayOrders returns a list of payment orders from the API service made by a given identity.
   190  func (a *API) GetPaymentGatewayOrders(id identity.Identity) ([]GatewayOrderResponse, error) {
   191  	req, err := requests.NewSignedGetRequest(a.url, "api/v2/payment/orders", a.signer(id))
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	var resp []GatewayOrderResponse
   197  	return resp, a.sendRequestAndParseResp(req, &resp)
   198  }
   199  
   200  // GetPaymentGatewayOrder returns a payment order by ID from the API
   201  // service that belongs to a given identity.
   202  func (a *API) GetPaymentGatewayOrder(id identity.Identity, oid string) (*GatewayOrderResponse, error) {
   203  	req, err := requests.NewSignedGetRequest(a.url, fmt.Sprintf("api/v2/payment/orders/%s", oid), a.signer(id))
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	var resp GatewayOrderResponse
   209  	return &resp, a.sendRequestAndParseResp(req, &resp)
   210  }
   211  
   212  // GetPaymentGatewayOrderInvoice returns an invoice for a payment order by ID from the API
   213  // service that belongs to a given identity.
   214  func (a *API) GetPaymentGatewayOrderInvoice(id identity.Identity, oid string) ([]byte, error) {
   215  	req, err := requests.NewSignedGetRequest(a.url, fmt.Sprintf("api/v2/payment/orders/%s/invoice", oid), a.signer(id))
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	res, err := a.req.Do(req)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	return io.ReadAll(res.Body)
   225  }
   226  
   227  // GatewayClientCallback triggers a payment callback from the client-side.
   228  // We will query the payment provider to verify the payment.
   229  func (a *API) GatewayClientCallback(id identity.Identity, gateway string, payload any) error {
   230  	req, err := requests.NewSignedPostRequest(a.url, fmt.Sprintf("api/v2/payment/%s/client-callback", gateway), payload, a.signer(id))
   231  	if err != nil {
   232  		return err
   233  	}
   234  	var resp struct{}
   235  	return a.sendRequestAndParseResp(req, &resp)
   236  }
   237  
   238  type paymentOrderRequest struct {
   239  	ChannelAddress string `json:"channel_address"`
   240  	MystAmount     string `json:"myst_amount"`
   241  	AmountUSD      string `json:"amount_usd"`
   242  	PayCurrency    string `json:"pay_currency"`
   243  	Country        string `json:"country"`
   244  	State          string `json:"state"`
   245  	ChainID        int64  `json:"chain_id"`
   246  	ProjectId      string `json:"project_id"`
   247  
   248  	GatewayCallerData json.RawMessage `json:"gateway_caller_data"`
   249  }
   250  
   251  // GatewayOrderRequest for creating payment gateway order
   252  type GatewayOrderRequest struct {
   253  	Identity    identity.Identity
   254  	Gateway     string
   255  	MystAmount  string
   256  	AmountUSD   string
   257  	PayCurrency string
   258  	Country     string
   259  	State       string
   260  	ProjectID   string
   261  	CallerData  json.RawMessage
   262  }
   263  
   264  // createPaymentOrder creates a new payment order in the API service.
   265  func (a *API) createPaymentGatewayOrder(cgo GatewayOrderRequest) (*GatewayOrderResponse, error) {
   266  	chainID := config.Current.GetInt64(config.FlagChainID.Name)
   267  
   268  	ch, err := a.channelCalculator.GetActiveChannelAddress(chainID, cgo.Identity.ToCommonAddress())
   269  	if err != nil {
   270  		return nil, fmt.Errorf("could get channel address: %w", err)
   271  	}
   272  	//https: //sandbox-pilvytis.mysterium.network
   273  	payload := paymentOrderRequest{
   274  		ChannelAddress:    ch.Hex(),
   275  		MystAmount:        cgo.MystAmount,
   276  		AmountUSD:         cgo.AmountUSD,
   277  		PayCurrency:       cgo.PayCurrency,
   278  		Country:           cgo.Country,
   279  		State:             cgo.State,
   280  		ChainID:           chainID,
   281  		GatewayCallerData: cgo.CallerData,
   282  		ProjectId:         cgo.ProjectID,
   283  	}
   284  
   285  	path := fmt.Sprintf("api/v2/payment/%s/orders", cgo.Gateway)
   286  	req, err := requests.NewSignedPostRequest(a.url, path, payload, a.signer(cgo.Identity))
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	var resp GatewayOrderResponse
   292  	return &resp, a.sendRequestAndParseResp(req, &resp)
   293  }
   294  
   295  func (a *API) sendRequestAndParseResp(req *http.Request, resp interface{}) error {
   296  	loc := a.lp.GetOrigin()
   297  
   298  	req.Header.Set("X-Origin-Country", loc.Country)
   299  	req.Header.Set("X-Origin-OS", runtime.GOOS)
   300  	req.Header.Set("X-Origin-Node-Version", metadata.VersionAsString())
   301  
   302  	return a.req.DoRequestAndParseResponse(req, &resp)
   303  }
   304  
   305  // RegistrationPaymentResponse is a response for the status of a registration payment.
   306  type RegistrationPaymentResponse struct {
   307  	Paid bool `json:"paid"`
   308  }
   309  
   310  // GetRegistrationPaymentStatus returns whether a registration payment order
   311  // has been paid by a given identity
   312  func (a *API) GetRegistrationPaymentStatus(id identity.Identity) (*RegistrationPaymentResponse, error) {
   313  	req, err := requests.NewGetRequest(a.url, fmt.Sprintf("api/v2/payment/registration/%s", id.Address), nil)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  
   318  	var resp RegistrationPaymentResponse
   319  	return &resp, a.sendRequestAndParseResp(req, &resp)
   320  }