Skip to content

Commit

Permalink
Merge pull request #50 from dimagi/sr/payments
Browse files Browse the repository at this point in the history
Capture Payment Phone numbers
  • Loading branch information
sravfeyn authored Dec 12, 2024
2 parents c7526dd + 9dcd237 commit 161f4c7
Show file tree
Hide file tree
Showing 14 changed files with 381 additions and 30 deletions.
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rest_framework.test import APIClient

from users.factories import UserFactory, FCMDeviceFactory

from messaging.factories import ServerFactory

@pytest.fixture
def user(db):
Expand Down
1 change: 1 addition & 0 deletions connectid/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'users.apps.UsersConfig',
'messaging',
'oauth2_provider',
'payments',
'rest_framework',
'axes',
'fcm_django',
Expand Down
24 changes: 13 additions & 11 deletions messaging/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
from django.urls import reverse
from firebase_admin import messaging
from rest_framework import status
from rest_framework.test import APITestCase

from messaging.factories import ChannelFactory, MessageFactory, ServerFactory
from messaging.models import Channel, Message, MessageStatus
from messaging.serializers import MessageData
from users.factories import FCMDeviceFactory
from payments.models import PaymentProfile
from users.factories import FCMDeviceFactory, UserFactory


APPLICATION_JSON = "application/json"

Expand All @@ -21,7 +24,6 @@
def server(oauth_app):
return ServerFactory(oauth_application=oauth_app)


def test_send_message(authed_client, fcm_device):
url = reverse('messaging:send_message')

Expand All @@ -30,7 +32,7 @@ def test_send_message(authed_client, fcm_device):
"username": fcm_device.user.username,
"body": "test message",
"data": {"test": "data"},
}, content_type="application/json")
}, content_type=APPLICATION_JSON)
assert response.status_code == 200, response.content
assert response.json() == {
'all_success': True,
Expand Down Expand Up @@ -66,7 +68,7 @@ def test_send_message_bulk(authed_client, fcm_device):
"data": {"test": "data2"},
}
]
}, content_type="application/json")
}, content_type=APPLICATION_JSON)

assert response.status_code == 200, response.content
assert mock_send_message.call_count == 2
Expand Down Expand Up @@ -246,7 +248,7 @@ def test_multiple_messages(self, auth_device, channel, server):
response = auth_device.post(
self.url,
data=json.dumps(data),
content_type="application/json",
content_type=APPLICATION_JSON,
)
json_data = response.json()

Expand Down Expand Up @@ -332,7 +334,7 @@ def test_consent(self, auth_device, channel, server, consent=False, ):
}
json_data = json.dumps(data)
response = auth_device.post(
self.url, json_data, content_type="application/json"
self.url, json_data, content_type=APPLICATION_JSON
)

assert response.status_code == status.HTTP_200_OK
Expand All @@ -359,7 +361,7 @@ def test_invalid_channel_id(self, auth_device):
url = reverse("messaging:update_consent")
data = {"channel": str(uuid4()), "consent": False}
data = json.dumps(data)
response = auth_device.post(url, data, content_type="application/json")
response = auth_device.post(url, data, content_type=APPLICATION_JSON)
assert response.status_code == status.HTTP_404_NOT_FOUND


Expand All @@ -373,7 +375,7 @@ def test_update_received(self, auth_device, channel):

data = {"messages": message_ids}
data = json.dumps(data)
response = auth_device.post(self.url, data, content_type="application/json")
response = auth_device.post(self.url, data, content_type=APPLICATION_JSON)

assert response.status_code == status.HTTP_200_OK

Expand All @@ -384,7 +386,7 @@ def test_update_received(self, auth_device, channel):
def test_empty_message_list(self, auth_device):
data = {"messages": []}
data = json.dumps(data)
response = auth_device.post(self.url, data, content_type="application/json")
response = auth_device.post(self.url, data, content_type=APPLICATION_JSON)

assert response.status_code == status.HTTP_400_BAD_REQUEST
assert Message.objects.filter(received__isnull=False).count() == 0
Expand All @@ -393,7 +395,7 @@ def test_invalid_message_ids(self, auth_device):
invalid_message_ids = [str(uuid4()), str(uuid4())]
data = {"messages": invalid_message_ids}
data = json.dumps(data)
response = auth_device.post(self.url, data, content_type="application/json")
response = auth_device.post(self.url, data, content_type=APPLICATION_JSON)

assert response.status_code == status.HTTP_404_NOT_FOUND
assert Message.objects.filter(received__isnull=False).count() == 0
Expand All @@ -409,7 +411,7 @@ def test_grouped_channel_messages(self, mock_send_messages, auth_device):

data = {"messages": message_ids}
data = json.dumps(data)
response = auth_device.post(self.url, data, content_type="application/json")
response = auth_device.post(self.url, data, content_type=APPLICATION_JSON)

assert response.status_code == status.HTTP_200_OK

Expand Down
Empty file added payments/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions payments/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class PaymentsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'payments'
32 changes: 32 additions & 0 deletions payments/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 4.1.7 on 2024-11-09 10:16

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='PaymentProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region=None)),
('owner_name', models.TextField(max_length=150, blank=True)),
('telecom_provider', models.CharField(blank=True, max_length=50, null=True)),
('is_verified', models.BooleanField(default=False)),
('status', models.CharField(choices=[('pending', 'Pending'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='pending', max_length=10)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment_profile', to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file added payments/migrations/__init__.py
Empty file.
34 changes: 34 additions & 0 deletions payments/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.db import models

from phonenumber_field.modelfields import PhoneNumberField
from users.models import ConnectUser


class PaymentProfile(models.Model):
PENDING = 'pending'
APPROVED = 'approved'
REJECTED = 'rejected'

STATUS_CHOICES = [
(PENDING, 'Pending'),
(APPROVED, 'Approved'),
(REJECTED, 'Rejected'),
]

user = models.OneToOneField(
ConnectUser,
on_delete=models.CASCADE,
related_name='payment_profile'
)
phone_number = PhoneNumberField()
owner_name = models.TextField(max_length=150, blank=True)
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(
max_length=10,
choices=STATUS_CHOICES,
default=PENDING,
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
95 changes: 95 additions & 0 deletions payments/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import base64
import pytest

from django.urls import reverse
from rest_framework import status

from messaging.tests import APPLICATION_JSON
from payments.models import PaymentProfile
from users.factories import UserFactory


@pytest.mark.parametrize(
"data, expected_status, expected_user1_status, expected_user2_status, result",
[
# Scenario 1: Update both statuses successfully
(
[
{"username": "user1", "status": "approved"},
{"username": "user2", "status": "rejected"},
],
status.HTTP_200_OK,
"approved",
"rejected",
{"approved": 1, "rejected": 1, "pending": 0}
),
# Scenario 2: No change in status
(
[
{"username": "user2", "status": "approved"},
],
status.HTTP_200_OK,
"pending", # Should remain unchanged
"approved", # Should remain unchanged
{"approved": 0, "rejected": 0, "pending": 0}
),
# Scenario 3: Invalid user (user doesn't exist)
(
[
{"username": "nonexistent_user", "status": "rejected"},
],
status.HTTP_404_NOT_FOUND,
"pending", # No change
"approved", # No change
{}
),
# Scenario 4: Multiple users, one invalid
(
[
{"username": "user1", "status": "approved"},
{"username": "nonexistent_user", "status": "rejected"},
],
status.HTTP_404_NOT_FOUND,
"pending", # No change
"approved", # No change
{}
),
]
)
def test_validate_phone_numbers(authed_client, data, expected_status, expected_user1_status, expected_user2_status, result):
user1 = UserFactory(username="user1")
user2 = UserFactory(username="user2")
PaymentProfile.objects.create(user=user1, phone_number="12345", status="pending")
PaymentProfile.objects.create(user=user2, phone_number="67890", status="approved")

url = reverse("validate_payment_phone_numbers")

response = authed_client.post(url, {"updates": data}, content_type=APPLICATION_JSON)

assert response.status_code == expected_status

profile1 = PaymentProfile.objects.get(user=user1)
profile2 = PaymentProfile.objects.get(user=user2)

assert profile1.status == expected_user1_status
assert profile2.status == expected_user2_status
if response.status_code == 200:
assert response.json()["result"] == result


def test_fetch_phone_numbers(authed_client):
user1 = UserFactory(username="user1")
user2 = UserFactory(username="user2")
PaymentProfile.objects.create(user=user1, phone_number="12345", status="pending")
PaymentProfile.objects.create(user=user2, phone_number="67890", status="approved")

url = reverse("fetch_payment_phone_numbers")

response = authed_client.get(url, {"usernames": ["user1", "user2"]})
assert len(response.json()['found_payment_numbers']) == 2

response = authed_client.get(url, {"usernames": ["user1", "user2"], "status": "pending"})
assert len(response.json()['found_payment_numbers']) == 1

response = authed_client.get(url, {"usernames": ["user1"], "status": "approved"})
assert len(response.json()['found_payment_numbers']) == 0
Loading

0 comments on commit 161f4c7

Please sign in to comment.