Skip to content
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

Capture Payment Phone numbers #50

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Capture Payment Phone numbers #50

wants to merge 4 commits into from

Conversation

sravfeyn
Copy link
Member

@sravfeyn sravfeyn commented Nov 7, 2024

This implements connect-id side endpoints for capturing users' mobile payment phone numbers. I have tested these locally using curl

Spec

@sravfeyn
Copy link
Member Author

Corresponding connect PR dimagi/commcare-connect#428

@sravfeyn
Copy link
Member Author

@calellowitz Please have a review of this and the corresponding connect PR that's linked. It will make it easy for mobile to test these.

Copy link
Collaborator

@calellowitz calellowitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few questions and a few suggestions

telecom_provider = models.CharField(max_length=50, blank=True, null=True)
# whether the number is verified using OTP
is_verified = models.BooleanField(default=False)
status = models.CharField(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this column do?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tracks whether this is pending for review or approved or rejected by connect user.

from django.conf import settings

# Create the client instance only once
twilio_client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason to have this be a singleton?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you instantiate the client, there's bunch of auth overhead API calls. Singleton would avoid that overhead each time we need it

try:
phone_info = client.lookups.v1.phone_numbers(phone_number).fetch(type="carrier")
return phone_info.carrier.get("name")
except Exception as e:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't catch bare exceptions. Is there a specific error you are looking for?

Copy link
Member Author

@sravfeyn sravfeyn Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true, I will adjust it such it logs the exception if any. For now, I will catch all exceptions.

'status': PaymentProfile.PENDING
}
)
return PhoneDevice.send_otp_httpresponse(phone_number=payment_profile.phone_number, user=payment_profile.user)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to send this every time they hit here or only on initial creation (and maybe if the phone number changes)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this single view to manage resend func, so we want to send it each time this view is called.

from users.models import ConnectUser


class PaymentProfile(models.Model):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there can only be one of these per user, what is the reason to have a separate model rather than adding these columns to the existing model (and then avoiding the extra lookups and possible exceptions).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are too many fields to add on user directly (telecom_provider, otp-verification status, payment validation status).

return JsonResponse({"success": True})


class FetchPhoneNumbers(APIView):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: these views should include the "impossible" scopes to prevent access tokens from working. I think it isn't necessary but am not sure enough to avoid adding it for safety.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "impossible" scopes?

phone_number = data["phone_number"]
status = data["status"]

filter_conditions |= Q(user__username=username, phone_number=phone_number)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since payment profiles are one to one with username why does the phone number need to be in the query? Can't it just be user__usernames__in=<list from request>, and similarly the dict can key just off the username?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I guess, we could simplify it. Though, one (debatable) advantage of this is that it would guard against the hypothetical scenario of two users using same payment phone number.


profiles = PaymentProfile.objects.filter(filter_conditions).select_related("user")
if len(profiles) != len(users_data):
return Response(status=drf_status.HTTP_404_NOT_FOUND)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This throws a 404 if any aren't found? It might be nicer to just return the success list at the end so one bad entry doesn't ruin the whole request.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, this shouldn't happen since the request is coming from the view which returns valid profiles in the first place. So, it's not worth partially succeeding and then managing partial failure that calls this view.

}
with transaction.atomic():
for profile in profiles:
key = (profile.user.username, profile.phone_number)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question about just using username

"approved": [],
"rejected": [],
}
with transaction.atomic():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this guarding against? It only wraps the update at the end, so I am not sure how it could end up rolled back in an unfinished state.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I had it when I did it sequentially instead of bulk update, I will remove it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants