Skip to content

Commit

Permalink
set up a shared shortcode
Browse files Browse the repository at this point in the history
Signed-off-by: Kathurima Kimathi <kathurimakimathi415@gmail.com>
  • Loading branch information
KathurimaKimathi committed Aug 23, 2021
1 parent 1ff99b7 commit b8a397a
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 82 deletions.
16 changes: 9 additions & 7 deletions pkg/onboarding/application/dto/input.go
Expand Up @@ -281,13 +281,15 @@ type SupplierPayload struct {
// EmailNotificationPayload is the email payload used to send email
// supplier and admins for KYC requests
type EmailNotificationPayload struct {
SupplierName string `json:"supplier_name"`
PartnerType string `json:"partner_type"`
AccountType string `json:"account_type"`
SubjectTitle string `json:"subject_title"`
EmailBody string `json:"email_body"`
EmailAddress string `json:"email_address"`
PrimaryPhone string `json:"primary_phone"`
SupplierName string `json:"supplier_name"`
PartnerType string `json:"partner_type"`
AccountType string `json:"account_type"`
SubjectTitle string `json:"subject_title"`
EmailBody string `json:"email_body"`
EmailAddress string `json:"email_address"`
PrimaryPhone string `json:"primary_phone"`
BeWellUser CRMDomain.GeneralOptionType `json:"bewell_user"`
Time string `json:"sending_time"`
}

// UserProfilePayload is used to update a user's profile.
Expand Down
5 changes: 5 additions & 0 deletions pkg/onboarding/application/utils/kyc_email_templates.go
Expand Up @@ -1445,6 +1445,11 @@ const AdminKYCSubmittedEmail = `
<p style="margin: 0;">Phone Number: <span
style="color: #000000;">{{.PrimaryPhone}}</span>
</p>
<p style="margin: 0;">Be.Well User: <span
style="color: #000000;">{{.BeWellUser}}</span></p>
<p style="margin: 0;">Time: <span
style="color: #000000;">{{.Time}}</span>
</p>
<p style="margin-top: 24px">
Regards,<br />
Expand Down
12 changes: 6 additions & 6 deletions pkg/onboarding/application/utils/validators.go
Expand Up @@ -73,27 +73,27 @@ func ValidateSignUpInput(input *dto.SignUpInput) (*dto.SignUpInput, error) {

// ValidateAficasTalkingSMSData returns AIT validated SMS data
func ValidateAficasTalkingSMSData(input *dto.AfricasTalkingMessage) (*dto.AfricasTalkingMessage, error) {
if input.LinkID == " " {
if input.LinkID == "" {
return nil, fmt.Errorf("message `linkID` cannot be empty")
}

if input.Text == " " {
if input.Text == "" {
return nil, fmt.Errorf("`text` message cannot be empty")
}

if input.To == " " {
if input.To == "" {
return nil, fmt.Errorf("`to` cannot be empty")
}

if input.ID == " " {
if input.ID == "" {
return nil, fmt.Errorf("message `ID` cannot be empty")
}

if input.Date == " " {
if input.Date == "" {
return nil, fmt.Errorf("`date` of sending cannot be empty")
}

if input.From == " " {
if input.From == "" {
return nil, fmt.Errorf("`phone` number cannot be empty")
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/onboarding/application/utils/validators_test.go
Expand Up @@ -220,7 +220,7 @@ func TestValidateSMSData(t *testing.T) {
}

invalidData := &dto.AfricasTalkingMessage{
LinkID: " ",
LinkID: "",
Text: text,
To: to,
ID: id,
Expand Down
3 changes: 3 additions & 0 deletions pkg/onboarding/infrastructure/services/engagement/service.go
Expand Up @@ -267,9 +267,12 @@ func (en *ServiceEngagementImpl) NotifyAdmins(ctx context.Context, input dto.Ema
SupplierName: input.SupplierName,
PartnerType: input.PartnerType,
AccountType: input.AccountType,
SubjectTitle: input.SubjectTitle,
EmailBody: input.EmailBody,
EmailAddress: input.EmailAddress,
PrimaryPhone: input.PrimaryPhone,
BeWellUser: input.BeWellUser,
Time: input.Time,
})

body := map[string]interface{}{
Expand Down
2 changes: 1 addition & 1 deletion pkg/onboarding/presentation/config.go
Expand Up @@ -150,7 +150,7 @@ func Router(ctx context.Context) (*mux.Router, error) {
su := usecases.NewSignUpUseCases(repo, profile, userpin, supplier, baseExt, engage, pubSub, edi)
nhif := usecases.NewNHIFUseCases(repo, profile, baseExt, engage)
aitUssd := ussd.NewUssdUsecases(repo, baseExt, profile, userpin, su, pinExt, pubSub, crmExt)
sms := usecases.NewSMSUsecase(repo, baseExt)
sms := usecases.NewSMSUsecase(repo, baseExt, engage, pubSub, crmExt)
role := usecases.NewRoleUseCases(repo, baseExt)
admin := usecases.NewAdminUseCases(repo, engage, baseExt, userpin)
agent := usecases.NewAgentUseCases(repo, engage, baseExt, userpin, role)
Expand Down
16 changes: 15 additions & 1 deletion pkg/onboarding/presentation/rest/handlers_test.go
Expand Up @@ -88,7 +88,7 @@ func InitializeFakeOnboardingInteractor() (*interactor.Interactor, error) {
userpin := usecases.NewUserPinUseCase(r, profile, ext, pinExt, engagementSvc)
su := usecases.NewSignUpUseCases(r, profile, userpin, supplier, ext, engagementSvc, ps, ediSvc)
nhif := usecases.NewNHIFUseCases(r, profile, ext, engagementSvc)
sms := usecases.NewSMSUsecase(r, ext)
sms := usecases.NewSMSUsecase(r, ext, engagementSvc, ps, crmExt)
role := usecases.NewRoleUseCases(r, ext)
admin := usecases.NewAdminUseCases(r, engagementSvc, ext, userpin)
agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin, role)
Expand Down Expand Up @@ -3554,6 +3554,20 @@ func TestHandlersInterfacesImpl_IncomingATSMS(t *testing.T) {
response := httptest.NewRecorder()

if tt.name == "VALID_CASE:Valid_incoming_sms" {
fakeRepo.GetUserProfileByPhoneNumberFn = func(ctx context.Context, phoneNumber string, suspended bool) (*profileutils.UserProfile, error) {
return &profileutils.UserProfile{
ID: uuid.NewString(),
}, nil
}

fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error {
return nil
}

fakeEngagementSvs.NotifyAdminsFn = func(ctx context.Context, input dto.EmailNotificationPayload) error {
return nil
}

fakeRepo.PersistIncomingSMSDataFn = func(ctx context.Context, input *dto.AfricasTalkingMessage) error {
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/onboarding/usecases/login_test.go
Expand Up @@ -229,7 +229,7 @@ func InitializeTestService(ctx context.Context) (*interactor.Interactor, error)
userpin := usecases.NewUserPinUseCase(repo, profile, ext, pinExt, engage)
su := usecases.NewSignUpUseCases(repo, profile, userpin, supplier, ext, engage, ps, edi)
nhif := usecases.NewNHIFUseCases(repo, profile, ext, engage)
sms := usecases.NewSMSUsecase(repo, ext)
sms := usecases.NewSMSUsecase(repo, ext, engage, ps, crmExt)

aitUssd := ussd.NewUssdUsecases(repo, ext, profile, userpin, su, pinExt, ps, crmExt)

Expand Down Expand Up @@ -489,7 +489,7 @@ func InitializeFakeOnboardingInteractor() (*interactor.Interactor, error) {
nhif := usecases.NewNHIFUseCases(r, profile, ext, engagementSvc)
aitUssd := ussd.NewUssdUsecases(r, ext, profile, userpin, su, pinExt, ps, crmExt)
adminSrv := adminSrv.NewService(ext)
sms := usecases.NewSMSUsecase(r, ext)
sms := usecases.NewSMSUsecase(r, ext, engagementSvc, ps, crmExt)
role := usecases.NewRoleUseCases(r, ext)
admin := usecases.NewAdminUseCases(r, engagementSvc, ext, userpin)
agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin, role)
Expand Down
152 changes: 152 additions & 0 deletions pkg/onboarding/usecases/shortcode.go
@@ -0,0 +1,152 @@
package usecases

import (
"context"
"fmt"

"github.com/savannahghi/onboarding/pkg/onboarding/application/dto"
"github.com/savannahghi/onboarding/pkg/onboarding/application/extension"
"github.com/savannahghi/onboarding/pkg/onboarding/application/utils"
"github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/crm"
"github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/engagement"
pubsubmessaging "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/pubsub"
"github.com/savannahghi/onboarding/pkg/onboarding/repository"
log "github.com/sirupsen/logrus"
"gitlab.slade360emr.com/go/commontools/crm/pkg/domain"
)

const (
// The right copy of reply will be availed later
shortcodeReplyMessage = "We have received your request and one of our representatives will reach out to you. Thanks you.\n\nDid you know you can now view your medical cover and benefits on Be.Well. To get started, Download Now from https://bwl.mobi/1bvf"
)

// SMSUsecase represent the logic involved in processing SMSs from shortcode
type SMSUsecase interface {
CreateSMSData(ctx context.Context, input *dto.AfricasTalkingMessage) error
}

//SMSImpl represents usecase implemention object
type SMSImpl struct {
onboardingRepository repository.OnboardingRepository
baseExt extension.BaseExtension
engagement engagement.ServiceEngagement
pubSub pubsubmessaging.ServicePubSub
hubspotCRM crm.ServiceCrm
}

// NewSMSUsecase returns a new SMS usecase
func NewSMSUsecase(
r repository.OnboardingRepository,
ext extension.BaseExtension,
engage engagement.ServiceEngagement,
ps pubsubmessaging.ServicePubSub,
crm crm.ServiceCrm,

) SMSUsecase {
return &SMSImpl{
onboardingRepository: r,
baseExt: ext,
engagement: engage,
pubSub: ps,
hubspotCRM: crm,
}
}

// CreateSMSData saves incoming shortcode messages, replies to the sender and creates hubspot contact
func (s *SMSImpl) CreateSMSData(ctx context.Context, input *dto.AfricasTalkingMessage) error {
ctx, span := tracer.Start(ctx, "CreateSMSData")
defer span.End()

validatedInput, err := utils.ValidateAficasTalkingSMSData(input)
if err != nil {
utils.RecordSpanError(span, err)
return err
}

// Checking if the shortcode SMS sender has a profile
profile, err := s.onboardingRepository.GetUserProfileByPhoneNumber(ctx, validatedInput.From, false)
if err != nil {
utils.RecordSpanError(span, err)
//should not panic when the user profile is not found
log.Errorf("an error occurred: %v", err)
}

if profile != nil {
to := validatedInput.From
message := shortcodeReplyMessage

supportEmailPayload := &dto.EmailNotificationPayload{
SubjectTitle: validatedInput.Text,
EmailBody: validatedInput.Text,
PrimaryPhone: validatedInput.From,
BeWellUser: domain.GeneralOptionTypeYes,
Time: validatedInput.Date,
}

return s.replyAndNotifyAdmin(ctx, to, message, validatedInput, supportEmailPayload)
}

to := validatedInput.From
message := shortcodeReplyMessage

supportEmailPayload := &dto.EmailNotificationPayload{
SubjectTitle: validatedInput.Text,
EmailBody: validatedInput.Text,
PrimaryPhone: validatedInput.From,
BeWellUser: domain.GeneralOptionTypeNo,
Time: validatedInput.Date,
}

err = s.replyAndNotifyAdmin(ctx, to, message, validatedInput, supportEmailPayload)
if err != nil {
return err
}

//Create CRM contact stub for the shortcode sms sender
contact := &domain.CRMContact{
Properties: domain.ContactProperties{
Phone: validatedInput.From,
Gender: string(domain.GeneralOptionTypeNotGiven),
FirstChannelOfContact: domain.ChannelOfContactShortcode,
BeWellEnrolled: domain.GeneralOptionTypeNo,
OptOut: domain.GeneralOptionTypeNo,
},
}

err = s.pubSub.NotifyCreateContact(ctx, *contact)
if err != nil {
utils.RecordSpanError(span, err)
log.Printf("failed to publish to crm.contact.create topic: %v", err)
}

return nil
}

func (s *SMSImpl) replyAndNotifyAdmin(
ctx context.Context,
to string,
message string,
validatedInput *dto.AfricasTalkingMessage,
supportEmailPayload *dto.EmailNotificationPayload) error {
ctx, span := tracer.Start(ctx, "replyAndNotifyAdmin")
defer span.End()

err := s.engagement.SendSMS(ctx, []string{to}, message)
if err != nil {
return fmt.Errorf("an error occurred while sending SMS: %v", err)
}

err = s.engagement.NotifyAdmins(ctx, *supportEmailPayload)
if err != nil {
return fmt.Errorf("an error occurred while notifying admins: %v", err)
}

err = s.onboardingRepository.PersistIncomingSMSData(ctx, validatedInput)
if err != nil {
utils.RecordSpanError(span, err)

return err
}

return nil
}
Expand Up @@ -5,7 +5,9 @@ import (
"testing"

"github.com/google/uuid"
"github.com/savannahghi/interserviceclient"
"github.com/savannahghi/onboarding/pkg/onboarding/application/dto"
"gitlab.slade360emr.com/go/commontools/crm/pkg/domain"
)

func TestSMSImpl_CreateSMSData_integration(t *testing.T) {
Expand All @@ -21,10 +23,10 @@ func TestSMSImpl_CreateSMSData_integration(t *testing.T) {
text := "Test Covers"
to := "3601"
id := "60119"
from := "+254705385894"
from := interserviceclient.TestUserPhoneNumber
date := "2021-05-17T13:20:04.490Z"

validData := &dto.AfricasTalkingMessage{
validPayload := &dto.AfricasTalkingMessage{
LinkID: validLinkId,
Text: text,
To: to,
Expand All @@ -33,15 +35,34 @@ func TestSMSImpl_CreateSMSData_integration(t *testing.T) {
From: from,
}

invalidData := &dto.AfricasTalkingMessage{
LinkID: " ",
invalidPayload := &dto.AfricasTalkingMessage{
LinkID: "",
Text: text,
To: to,
ID: id,
Date: " ",
Date: "",
From: from,
}

_ = s.Engagement.SendSMS(ctx, []string{from}, text)

supportNotification := &dto.EmailNotificationPayload{
EmailBody: text,
}
_ = s.Engagement.NotifyAdmins(ctx, *supportNotification)

contact := &domain.CRMContact{
Properties: domain.ContactProperties{
Phone: validPayload.From,
Gender: string(domain.GeneralOptionTypeNotGiven),
FirstChannelOfContact: domain.ChannelOfContactShortcode,
BeWellEnrolled: domain.GeneralOptionTypeNo,
OptOut: domain.GeneralOptionTypeNo,
},
}

_ = s.PubSub.NotifyCreateContact(ctx, *contact)

type args struct {
ctx context.Context
input dto.AfricasTalkingMessage
Expand All @@ -55,7 +76,7 @@ func TestSMSImpl_CreateSMSData_integration(t *testing.T) {
name: "Happy :) successfully persist sms data",
args: args{
ctx: ctx,
input: *validData,
input: *validPayload,
},
wantErr: false,
},
Expand All @@ -71,7 +92,7 @@ func TestSMSImpl_CreateSMSData_integration(t *testing.T) {
name: "Sad :( fail to persist sms data with invalid sms data",
args: args{
ctx: ctx,
input: *invalidData,
input: *invalidPayload,
},
wantErr: true,
},
Expand Down

0 comments on commit b8a397a

Please sign in to comment.