diff --git a/config/config.go b/config/config.go index 7bfb560..a9221aa 100644 --- a/config/config.go +++ b/config/config.go @@ -16,6 +16,7 @@ const ( voucherTransfersPathPrefix = "/api/v1/transfers/last10" voucherDataPathPrefix = "/api/v1/token" aliasPrefix = "api/v1/alias" + SendSMSPrefix = "api/v1/external/upsell" poolDepositPrefix = "/api/v2/pool/deposit" poolSwapQoutePrefix = "/api/v2/pool/quote" poolSwapPrefix = "/api/v2/pool/swap" @@ -23,6 +24,7 @@ const ( retrievePoolDetailsPrefix = "/api/v1/pool/reverse" poolSwappableVouchersPrefix = "/api/v1/pool" AliasEnsPrefix = "/api/v1/bypass" + ExternalSMSPrefix = "/api/v1/external" ) var ( @@ -30,6 +32,7 @@ var ( dataURLBase string BearerToken string aliasEnsURLBase string + externalSMSBase string ) var ( @@ -48,7 +51,9 @@ var ( TopPoolsURL string RetrievePoolDetailsURL string PoolSwappableVouchersURL string + SendSMSURL string AliasEnsURL string + ExternalSMSURL string ) func setBase() error { @@ -57,6 +62,7 @@ func setBase() error { custodialURLBase = env.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003") dataURLBase = env.GetEnv("DATA_URL_BASE", "http://localhost:5006") aliasEnsURLBase = env.GetEnv("ALIAS_ENS_BASE", "http://localhost:5015") + externalSMSBase = env.GetEnv("EXTERNAL_SMS_BASE", "http://localhost:5035") BearerToken = env.GetEnv("BEARER_TOKEN", "") _, err = url.Parse(custodialURLBase) @@ -85,6 +91,7 @@ func LoadConfig() error { VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix) VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix) CheckAliasURL, _ = url.JoinPath(dataURLBase, aliasPrefix) + SendSMSURL, _ = url.JoinPath(dataURLBase, SendSMSPrefix) PoolDepositURL, _ = url.JoinPath(custodialURLBase, poolDepositPrefix) PoolSwapQuoteURL, _ = url.JoinPath(custodialURLBase, poolSwapQoutePrefix) PoolSwapURL, _ = url.JoinPath(custodialURLBase, poolSwapPrefix) @@ -93,5 +100,7 @@ func LoadConfig() error { PoolSwappableVouchersURL, _ = url.JoinPath(dataURLBase, poolSwappableVouchersPrefix) AliasEnsURL, _ = url.JoinPath(aliasEnsURLBase, AliasEnsPrefix) + ExternalSMSURL, _ = url.JoinPath(externalSMSBase, ExternalSMSPrefix) + return nil } diff --git a/dev/api.go b/dev/api.go index e9a2a94..d879869 100644 --- a/dev/api.go +++ b/dev/api.go @@ -806,6 +806,21 @@ func (das *DevAccountService) RequestAlias(ctx context.Context, publicKey string }, nil } +func (das *DevAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) { + logg.DebugCtxf(ctx, "sent an SMS", "inviterPhone", inviterPhone, "inviteePhone", inviteePhone) + return &models.SendSMSResponse{ + Invitee: inviteePhone, + }, nil +} + +func (das *DevAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error { + return fmt.Errorf("unimplemented") +} + +func (das *DevAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error { + return fmt.Errorf("unimplemented") +} + func (das *DevAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) { var topPools []dataserviceapi.PoolDetails for _, p := range das.pools { diff --git a/models/send_sms_response.go b/models/send_sms_response.go new file mode 100644 index 0000000..1f6a70f --- /dev/null +++ b/models/send_sms_response.go @@ -0,0 +1,5 @@ +package models + +type SendSMSResponse struct { + Invitee string `json:"invitee"` +} diff --git a/remote/account_service.go b/remote/account_service.go index cabd4d8..1ea0af7 100644 --- a/remote/account_service.go +++ b/remote/account_service.go @@ -17,6 +17,9 @@ type AccountService interface { TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) RequestAlias(ctx context.Context, hint string, publicKey string) (*models.RequestAliasResult, error) + SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) + SendAddressSMS(ctx context.Context, publicKey, originPhone string) error + SendPINResetSMS(ctx context.Context, admin, phone string) error PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) GetPoolSwappableFromVouchers(ctx context.Context, poolAddress, publicKey string) ([]dataserviceapi.TokenHoldings, error) diff --git a/remote/http/service.go b/remote/http/service.go index 43793f0..d69b5ae 100644 --- a/remote/http/service.go +++ b/remote/http/service.go @@ -520,6 +520,87 @@ func requestEnsAlias(ctx context.Context, publicKey string, hint string) (*model return &r, nil } +// SendSMS calls the API to send out an SMS. +// Parameters: +// - inviterPhone: The user initiating the SMS. +// - inviteePhone: The number being invited to Sarafu. +func (as *HTTPAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) { + var r models.SendSMSResponse + + // Create request payload + payload := map[string]string{ + "inviterPhone": inviterPhone, + "inviteePhone": inviteePhone, + } + + payloadBytes, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + // Create a new request + req, err := http.NewRequest("POST", config.SendSMSURL, bytes.NewBuffer(payloadBytes)) + if err != nil { + return nil, err + } + _, err = doRequest(ctx, req, &r) + if err != nil { + return nil, err + } + + return &r, nil +} + +func (as *HTTPAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error { + ep, err := url.JoinPath(config.ExternalSMSURL, "address") + if err != nil { + return err + } + logg.InfoCtxf(ctx, "sending an address sms", "endpoint", ep, "address", publicKey, "origin-phone", originPhone) + payload := map[string]string{ + "address": publicKey, + "originPhone": originPhone, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + return err + } + req, err := http.NewRequest("POST", ep, bytes.NewBuffer(payloadBytes)) + if err != nil { + return err + } + _, err = doRequest(ctx, req, nil) + if err != nil { + return err + } + return nil +} + +func (as *HTTPAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error { + ep, err := url.JoinPath(config.ExternalSMSURL, "pinreset") + if err != nil { + return err + } + logg.InfoCtxf(ctx, "sending pin reset sms", "endpoint", ep, "admin", admin, "phone", phone) + payload := map[string]string{ + "admin": admin, + "phone": phone, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + return err + } + req, err := http.NewRequest("POST", ep, bytes.NewBuffer(payloadBytes)) + if err != nil { + return err + } + _, err = doRequest(ctx, req, nil) + if err != nil { + return err + } + return nil +} + // TODO: remove eth-custodial api dependency func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) { var okResponse api.OKResponse diff --git a/testutil/mocks/api_mock.go b/testutil/mocks/api_mock.go index b020677..2293aa6 100644 --- a/testutil/mocks/api_mock.go +++ b/testutil/mocks/api_mock.go @@ -61,3 +61,7 @@ func (m MockApi) RequestAlias(ctx context.Context, publicKey string, hint string func (m MockApi) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) { return nil, nil } + +func (m MockApi) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) { + return nil, nil +} diff --git a/testutil/mocks/service_mock.go b/testutil/mocks/service_mock.go index 42bcb28..aaa78bb 100644 --- a/testutil/mocks/service_mock.go +++ b/testutil/mocks/service_mock.go @@ -53,11 +53,24 @@ func (m *MockAccountService) CheckAliasAddress(ctx context.Context, alias string return args.Get(0).(*models.AliasAddress), args.Error(1) } -func (m MockAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { +func (m *MockAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { args := m.Called(publicKey, hint) return args.Get(0).(*models.RequestAliasResult), args.Error(1) } +func (m *MockAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) { + args := m.Called(inviterPhone, inviteePhone) + return args.Get(0).(*models.SendSMSResponse), args.Error(1) +} + +func (m *MockAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error { + return nil +} + +func (m *MockAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error { + return nil +} + func (m MockAccountService) PoolDeposit(ctx context.Context, amount, from, poolAddress, tokenAddress string) (*models.PoolDepositResult, error) { args := m.Called(amount, from, poolAddress, tokenAddress) return args.Get(0).(*models.PoolDepositResult), args.Error(1) diff --git a/testutil/testservice/account_service.go b/testutil/testservice/account_service.go index 445fd9b..00906c0 100644 --- a/testutil/testservice/account_service.go +++ b/testutil/testservice/account_service.go @@ -61,14 +61,26 @@ func (m TestAccountService) PoolDeposit(ctx context.Context, amount, from, poolA return &models.PoolDepositResult{}, nil } -func (m TestAccountService) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) { +func (m *TestAccountService) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) { return &models.AliasAddress{}, nil } -func (m TestAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { +func (m *TestAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { return &models.RequestAliasResult{}, nil } +func (m *TestAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) { + return &models.SendSMSResponse{}, nil +} + +func (m *TestAccountService) SendAddressSMS(ctx context.Context, publicKey, originPhone string) error { + return nil +} + +func (m *TestAccountService) SendPINResetSMS(ctx context.Context, admin, phone string) error { + return nil +} + func (m TestAccountService) FetchTopPools(ctx context.Context) ([]dataserviceapi.PoolDetails, error) { return []dataserviceapi.PoolDetails{}, nil }