Skip to content

Add Business Information Fields to Registration new #464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 53 additions & 11 deletions controllers/accounts/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ func (ctrl *AuthController) Register(ctx *gin.Context) {
return
}

// Validate monthly volume for new business users
var monthlyVolumeFloat float64
if u.ContainsString(payload.Scopes, "provider") || u.ContainsString(payload.Scopes, "sender") {
var err error
monthlyVolumeFloat, err = u.ValidateMonthlyVolume(payload.MonthlyVolume)
if err != nil {
_ = tx.Rollback()
u.APIResponse(ctx, http.StatusBadRequest, "error", err.Error(), nil)
return
}
}

// Save the user
scope := strings.Join(payload.Scopes, " ")
userCreate := tx.User.
Expand Down Expand Up @@ -120,7 +132,7 @@ func (ctrl *AuthController) Register(ctx *gin.Context) {
if verificationToken != nil {
if _, err := ctrl.emailService.SendVerificationEmail(ctx, verificationToken.Token, user.Email, user.FirstName); err != nil {
logger.WithFields(logger.Fields{
"Error": fmt.Sprintf("%v", err),
"Error": fmt.Sprintf("%v", err),
"UserID": user.ID,
}).Errorf("Failed to send verification email")
}
Expand Down Expand Up @@ -174,17 +186,20 @@ func (ctrl *AuthController) Register(ctx *gin.Context) {
return
}

provider, err := tx.ProviderProfile.
// Create provider profile
providerCreate := tx.ProviderProfile.
Create().
AddCurrencies(fiatCurrencies...).
SetVisibilityMode(providerprofile.VisibilityModePrivate).
SetUser(user).
SetProvisionMode(providerprofile.ProvisionModeAuto).
Save(ctx)
SetMonthlyVolume(monthlyVolumeFloat)

provider, err := providerCreate.Save(ctx)
if err != nil {
_ = tx.Rollback()
logger.WithFields(logger.Fields{
"Error": fmt.Sprintf("%v", err),
"Error": fmt.Sprintf("%v", err),
"UserID": user.ID,
}).Errorf("Failed to create provider profile")
u.APIResponse(ctx, http.StatusInternalServerError, "error",
Expand All @@ -197,8 +212,8 @@ func (ctrl *AuthController) Register(ctx *gin.Context) {
if err != nil {
_ = tx.Rollback()
logger.WithFields(logger.Fields{
"Error": fmt.Sprintf("%v", err),
"UserID": user.ID,
"Error": fmt.Sprintf("%v", err),
"UserID": user.ID,
"ProviderID": provider.ID,
}).Errorf("Failed to create API key for provider")
u.APIResponse(ctx, http.StatusInternalServerError, "error",
Expand All @@ -209,14 +224,41 @@ func (ctrl *AuthController) Register(ctx *gin.Context) {

// Create a sender profile
if u.ContainsString(scopes, "sender") {
sender, err := tx.SenderProfile.
// Validate business_website if provided
if payload.BusinessWebsite != "" && !u.IsValidURL(payload.BusinessWebsite) {
_ = tx.Rollback()
u.APIResponse(ctx, http.StatusBadRequest, "error",
"Invalid business website URL", nil)
return
}

// Validate nature_of_business length if provided
if len(payload.NatureOfBusiness) > 255 {
_ = tx.Rollback()
u.APIResponse(ctx, http.StatusBadRequest, "error",
"Nature of business exceeds character limit", nil)
return
}

// Create sender profile
senderCreate := tx.SenderProfile.
Create().
SetUser(user).
Save(ctx)
SetMonthlyVolume(monthlyVolumeFloat)

// Set optional fields
if payload.BusinessWebsite != "" {
senderCreate = senderCreate.SetBusinessWebsite(payload.BusinessWebsite)
}
if payload.NatureOfBusiness != "" {
senderCreate = senderCreate.SetNatureOfBusiness(payload.NatureOfBusiness)
}

sender, err := senderCreate.Save(ctx)
if err != nil {
_ = tx.Rollback()
logger.WithFields(logger.Fields{
"Error": fmt.Sprintf("%v", err),
"Error": fmt.Sprintf("%v", err),
"UserID": user.ID,
}).Errorf("Failed to create sender profile")
u.APIResponse(ctx, http.StatusInternalServerError, "error",
Expand All @@ -229,8 +271,8 @@ func (ctrl *AuthController) Register(ctx *gin.Context) {
if err != nil {
_ = tx.Rollback()
logger.WithFields(logger.Fields{
"Error": fmt.Sprintf("%v", err),
"UserID": user.ID,
"Error": fmt.Sprintf("%v", err),
"UserID": user.ID,
"SenderID": sender.ID,
}).Errorf("Failed to create API key for sender")
u.APIResponse(ctx, http.StatusInternalServerError, "error",
Expand Down
183 changes: 151 additions & 32 deletions controllers/accounts/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
svc "github.com/paycrest/aggregator/services"
db "github.com/paycrest/aggregator/storage"
"github.com/paycrest/aggregator/types"
"github.com/paycrest/aggregator/utils"
"github.com/shopspring/decimal"
"github.com/spf13/viper"
"golang.org/x/crypto/bcrypt"

Expand Down Expand Up @@ -98,12 +100,15 @@ func TestAuth(t *testing.T) {
t.Run("with valid payload and both sender and provider scopes", func(t *testing.T) {
// Test register with valid payload
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Currencies: []string{"NGN"},
Scopes: []string{"sender", "provider"},
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Currencies: []string{"NGN"},
Scopes: []string{"sender", "provider"},
MonthlyVolume: utils.DecimalPtr(decimal.NewFromInt(1000)),
BusinessWebsite: "https://example.com",
NatureOfBusiness: "Example business",
}

header := map[string]string{
Expand Down Expand Up @@ -166,11 +171,14 @@ func TestAuth(t *testing.T) {
t.Run("with only sender scope payload", func(t *testing.T) {
// Test register with valid payload
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password1",
Scopes: []string{"sender"},
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password1",
Scopes: []string{"sender"},
MonthlyVolume: utils.DecimalPtr(decimal.NewFromInt(1000)),
BusinessWebsite: "https://example.com",
NatureOfBusiness: "Example business",
}

res, err := test.PerformRequest(t, "POST", "/register", payload, nil, router)
Expand Down Expand Up @@ -224,12 +232,13 @@ func TestAuth(t *testing.T) {
t.Run("with only provider scope payload", func(t *testing.T) {
// Test register with valid payload
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password2",
Currencies: []string{"NGN"},
Scopes: []string{"provider"},
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password2",
Currencies: []string{"NGN"},
Scopes: []string{"provider"},
MonthlyVolume: utils.DecimalPtr(decimal.NewFromInt(1000)),
}

header := map[string]string{
Expand Down Expand Up @@ -305,30 +314,139 @@ func TestAuth(t *testing.T) {
// assert.Equal(t, http.StatusInternalServerError, res.Code)
// })
})
t.Run("with NatureOfBusiness exceeding 255 characters", func(t *testing.T) {
longDescription := strings.Repeat("a", 256)
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Scopes: []string{"sender"},
MonthlyVolume: utils.DecimalPtr(decimal.NewFromInt(1000)),
BusinessWebsite: "https://example.com",
NatureOfBusiness: longDescription,
}

res, err := test.PerformRequest(t, "POST", "/register", payload, nil, router)
assert.NoError(t, err)

// Assert the response body
assert.Equal(t, http.StatusBadRequest, res.Code)

var response types.Response
err = json.Unmarshal(res.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "Nature of business exceeds character limit", response.Message)
assert.Nil(t, response.Data)
})
t.Run("with invalid BusinessWebsite", func(t *testing.T) {
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Scopes: []string{"sender"},
MonthlyVolume: utils.DecimalPtr(decimal.NewFromInt(1000)),
BusinessWebsite: "invalid-url",
NatureOfBusiness: "Example business",
}

res, err := test.PerformRequest(t, "POST", "/register", payload, nil, router)
assert.NoError(t, err)

// Assert the response body
assert.Equal(t, http.StatusBadRequest, res.Code)

var response types.Response
err = json.Unmarshal(res.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "Invalid business website URL", response.Message)
assert.Nil(t, response.Data)
})
t.Run("with missing MonthlyVolume", func(t *testing.T) {
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Scopes: []string{"sender"},
}

res, err := test.PerformRequest(t, "POST", "/register", payload, nil, router)
assert.NoError(t, err)

// Assert the response body
assert.Equal(t, http.StatusBadRequest, res.Code)

var response types.Response
err = json.Unmarshal(res.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "monthly volume must be provided and greater than zero", response.Message)
assert.Nil(t, response.Data)
})
t.Run("with MonthlyVolume as 0", func(t *testing.T) {
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Scopes: []string{"sender"},
MonthlyVolume: utils.DecimalPtr(decimal.NewFromInt(0)),
}

res, err := test.PerformRequest(t, "POST", "/register", payload, nil, router)
assert.NoError(t, err)

assert.Equal(t, http.StatusBadRequest, res.Code)

var response types.Response
err = json.Unmarshal(res.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "monthly volume must be a positive number", response.Message)
assert.Nil(t, response.Data)
})
t.Run("from the provider app", func(t *testing.T) {
// Test register with valid payload
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Currencies: []string{"NGN", "KES"},
Scopes: []string{"provider"},
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Currencies: []string{"NGN", "KES"},
Scopes: []string{"provider"},
MonthlyVolume: utils.DecimalPtr(decimal.NewFromInt(1000)),
}

headers := map[string]string{
"Client-Type": "mobile",
}

res, err := test.PerformRequest(t, "POST", "/register", payload, headers, router)
// First, ensure the currency exists in the database
ctx := context.Background()
_, err := db.Client.FiatCurrency.Create().
SetCode("NG").
SetName("Nigerian Naira").
SetShortName("NGN").
SetSymbol("#").
SetMarketRate(decimal.NewFromInt(1000)).
SetIsEnabled(true).
Save(ctx)
assert.NoError(t, err)

// Assert the response body
assert.Equal(t, http.StatusCreated, res.Code)
res, err := test.PerformRequest(t, "POST", "/register", payload, headers, router)
assert.NoError(t, err)

var response types.Response
err = json.Unmarshal(res.Body.Bytes(), &response)
assert.NoError(t, err)

assert.Equal(t, http.StatusCreated, res.Code)

if res.Code != http.StatusCreated {
t.Logf("Error Message: %s", response.Message)
return
}

data, ok := response.Data.(map[string]interface{})
assert.True(t, ok, "response.Data is not of type map[string]interface{}")
assert.NotNil(t, data, "response.Data is nil")
Expand All @@ -346,7 +464,7 @@ func TestAuth(t *testing.T) {
q.WithAPIKey()
}).
WithSenderProfile().
Only(context.Background())
Only(ctx)
assert.NoError(t, err)

assert.NotNil(t, user)
Expand All @@ -357,11 +475,12 @@ func TestAuth(t *testing.T) {
t.Run("with existing user", func(t *testing.T) {
// Test register with existing user
payload := types.RegisterPayload{
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Scopes: []string{"sender"},
FirstName: "Ike",
LastName: "Ayo",
Email: "[email protected]",
Password: "password",
Scopes: []string{"sender"},
MonthlyVolume: nil,
}

res, err := test.PerformRequest(t, "POST", "/register", payload, nil, router)
Expand Down
10 changes: 9 additions & 1 deletion ent/migrate/migrations/20240905170202_sender_provider_id.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
-- Modify "provider_profiles" table
ALTER TABLE "provider_profiles" DROP COLUMN "is_partner";
ALTER TABLE "provider_profiles"
DROP COLUMN "is_partner",
ADD COLUMN "monthly_volume" decimal DEFAULT NULL;

-- Modify "sender_profiles" table
ALTER TABLE "sender_profiles" ADD COLUMN "provider_id" character varying NULL;
ALTER TABLE "sender_profiles"
ADD COLUMN "provider_id" character varying NULL,
ADD COLUMN "monthly_volume" decimal DEFAULT NULL,
ADD COLUMN "business_website" varchar DEFAULT NULL,
ADD COLUMN "nature_of_business" varchar(255) DEFAULT NULL;
Loading
Loading