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 }