Repository: oppia/oppia

Issue:
Introduce new Donate page
Introdce a new Donate page based on these [mocks](https://www.figma.com/file/Q4UOZRGxG1E5Vt67lHJKjV/Oppia-Foundation?node-id=0%3A1).


Discussion:

Resolving patch:
diff --git a/core/controllers/profile.py b/core/controllers/profile.py
--- a/core/controllers/profile.py
+++ b/core/controllers/profile.py
@@ -199,21 +199,22 @@ def post(self, unused_secret: str) -> None:
         self.render_json({})
 
 
-class AndroidListSubscriptionHandlerNormalizedPayloadDict(TypedDict):
-    """Dict representation of AndroidListSubscriptionHandler's
+class MailingListSubscriptionHandlerNormalizedPayloadDict(TypedDict):
+    """Dict representation of MailingListSubscriptionHandler's
     normalized_request dictionary.
     """
 
     email: str
     name: str
+    tag: str
 
 
-class AndroidListSubscriptionHandler(
+class MailingListSubscriptionHandler(
     base.BaseHandler[
-        AndroidListSubscriptionHandlerNormalizedPayloadDict, Dict[str, str]
+        MailingListSubscriptionHandlerNormalizedPayloadDict, Dict[str, str]
     ]
 ):
-    """Adds user to Android mailing list."""
+    """Adds user to the mailing list."""
 
     URL_PATH_ARGS_SCHEMAS: Dict[str, str] = {}
     HANDLER_ARGS_SCHEMAS = {
@@ -234,6 +235,14 @@ class AndroidListSubscriptionHandler(
                         'id': 'is_nonempty'
                     }]
                 }
+            },
+            'tag': {
+                'schema': {
+                    'type': 'basestring',
+                    'validators': [{
+                        'id': 'is_nonempty'
+                    }]
+                }
             }
         }
     }
@@ -244,7 +253,8 @@ def put(self) -> None:
         assert self.normalized_payload is not None
         email = self.normalized_payload['email']
         name = self.normalized_payload['name']
-        status = user_services.add_user_to_android_list(email, name)
+        tag = self.normalized_payload['tag']
+        status = user_services.add_user_to_mailing_list(email, name, tag)
         self.render_json({'status': status})
 
 
diff --git a/core/domain/user_services.py b/core/domain/user_services.py
--- a/core/domain/user_services.py
+++ b/core/domain/user_services.py
@@ -1728,13 +1728,14 @@ def record_user_created_an_exploration(user_id: str) -> None:
         save_user_settings(user_settings)
 
 
-def add_user_to_android_list(email: str, name: str) -> bool:
-    """Adds user to the bulk email provider with the 'Android' tag and required
+def add_user_to_mailing_list(email: str, name: str, tag: str) -> bool:
+    """Adds user to the bulk email provider with the relevant tag and required
     merge fields.
 
     Args:
         email: str. Email of the user.
         name: str. Name of the user.
+        tag: str. Tag for the mailing list.
 
     Returns:
         bool. Whether the operation was successful or not.
@@ -1743,7 +1744,7 @@ def add_user_to_android_list(email: str, name: str) -> bool:
         'NAME': name
     }
     return bulk_email_services.add_or_update_user_status(
-        email, merge_fields, 'Android', can_receive_email_updates=True)
+        email, merge_fields, tag, can_receive_email_updates=True)
 
 
 def update_email_preferences(
diff --git a/main.py b/main.py
--- a/main.py
+++ b/main.py
@@ -568,8 +568,8 @@ def get_redirect_route(
     get_redirect_route(
         r'/profilehandler/data/<username>', profile.ProfileHandler),
     get_redirect_route(
-        r'/androidlistsubscriptionhandler',
-        profile.AndroidListSubscriptionHandler),
+        r'/mailinglistsubscriptionhandler',
+        profile.MailingListSubscriptionHandler),
     get_redirect_route(
         r'%s/<secret>' % feconf.BULK_EMAIL_WEBHOOK_ENDPOINT,
         profile.BulkEmailWebhookEndpoint),


Test patch:
diff --git a/core/controllers/profile_test.py b/core/controllers/profile_test.py
--- a/core/controllers/profile_test.py
+++ b/core/controllers/profile_test.py
@@ -1018,11 +1018,11 @@ def test_get_delete_account_page(self) -> None:
         self.assertIn(b'<oppia-root></oppia-root>', response.body)
 
 
-class AndroidListSubscriptionHandlerTests(test_utils.GenericTestBase):
+class MailingListSubscriptionHandlerTests(test_utils.GenericTestBase):
 
     def test_put_function(self) -> None:
         swap_add_fn = self.swap(
-            user_services, 'add_user_to_android_list', lambda *args: True)
+            user_services, 'add_user_to_mailing_list', lambda *args: True)
 
         self.signup(self.VIEWER_EMAIL, self.VIEWER_USERNAME)
         self.login(self.VIEWER_EMAIL)
@@ -1030,9 +1030,10 @@ def test_put_function(self) -> None:
         csrf_token = self.get_new_csrf_token()
         with swap_add_fn:
             json_response = self.put_json(
-                '/androidlistsubscriptionhandler', {
+                '/mailinglistsubscriptionhandler', {
                     'email': 'email@example.com',
-                    'name': 'Name'
+                    'name': 'Name',
+                    'tag': 'Web'
                 }, csrf_token=csrf_token)
             self.assertEqual(json_response, {'status': True})
 
@@ -1042,7 +1043,7 @@ def test_email_provider_error(self) -> None:
         def raise_exception() -> None:
             raise Exception('Backend error')
         swap_add_fn = self.swap(
-            user_services, 'add_user_to_android_list', raise_exception)
+            user_services, 'add_user_to_mailing_list', raise_exception)
 
         self.signup(self.VIEWER_EMAIL, self.VIEWER_USERNAME)
         self.login(self.VIEWER_EMAIL)
@@ -1050,16 +1051,17 @@ def raise_exception() -> None:
         csrf_token = self.get_new_csrf_token()
         with swap_add_fn:
             self.put_json(
-                '/androidlistsubscriptionhandler', {
+                '/mailinglistsubscriptionhandler', {
                     'email': 'email@example.com',
-                    'name': 'Name'
+                    'name': 'Name',
+                    'tag': 'Web'
                 }, csrf_token=csrf_token, expected_status_int=500)
 
         self.logout()
 
     def test_invalid_inputs(self) -> None:
         swap_add_fn = self.swap(
-            user_services, 'add_user_to_android_list', lambda *args: True)
+            user_services, 'add_user_to_mailing_list', lambda *args: True)
 
         self.signup(self.VIEWER_EMAIL, self.VIEWER_USERNAME)
         self.login(self.VIEWER_EMAIL)
@@ -1067,15 +1069,24 @@ def test_invalid_inputs(self) -> None:
         csrf_token = self.get_new_csrf_token()
         with swap_add_fn:
             self.put_json(
-                '/androidlistsubscriptionhandler', {
+                '/mailinglistsubscriptionhandler', {
                     'email': 'invalidemail.com',
-                    'name': 'Name'
+                    'name': 'Name',
+                    'tag': 'Web'
                 }, csrf_token=csrf_token, expected_status_int=400)
 
             self.put_json(
-                '/androidlistsubscriptionhandler', {
+                '/mailinglistsubscriptionhandler', {
                     'email': 'email@example.com',
-                    'name': ''
+                    'name': '',
+                    'tag': 'Web'
+                }, csrf_token=csrf_token, expected_status_int=400)
+
+            self.put_json(
+                '/mailinglistsubscriptionhandler', {
+                    'email': 'email@example.com',
+                    'name': 'Name',
+                    'tag': ''
                 }, csrf_token=csrf_token, expected_status_int=400)
 
         self.logout()
diff --git a/core/domain/user_services_test.py b/core/domain/user_services_test.py
--- a/core/domain/user_services_test.py
+++ b/core/domain/user_services_test.py
@@ -670,7 +670,7 @@ def test_get_users_email_preferences(self) -> None:
             user_email_prefs.can_receive_subscription_email,
             feconf.DEFAULT_SUBSCRIPTION_EMAIL_PREFERENCE)
 
-    def test_add_user_to_android_list(self) -> None:
+    def test_add_user_to_mailing_list(self) -> None:
         def _mock_add_or_update_user_status(
             unused_email: str,
             merge_fields: Dict[str, str],
@@ -689,8 +689,8 @@ def _mock_add_or_update_user_status(
             _mock_add_or_update_user_status)
         with fn_swap:
             self.assertTrue(
-                user_services.add_user_to_android_list(
-                    'email@example.com', 'Name'))
+                user_services.add_user_to_mailing_list(
+                    'email@example.com', 'Name', 'Android'))
 
     def test_set_and_get_user_email_preferences(self) -> None:
         auth_id = 'someUser'
diff --git a/core/tests/webdriverio_desktop/navigation.js b/core/tests/webdriverio_desktop/navigation.js
--- a/core/tests/webdriverio_desktop/navigation.js
+++ b/core/tests/webdriverio_desktop/navigation.js
@@ -104,16 +104,11 @@ describe('DEV MODE Test', function() {
 describe('Donation flow', function() {
   it('should be able to donate via PayPal', async function() {
     await browser.url('/donate');
-    var payPalButton = $('.e2e-test-paypal-donate-button');
-    await action.click('PayPal button', payPalButton);
-    expect(await browser.getUrl()).toContain('www.paypal.com');
-  });
-
-  it('should be able to donate via credit card', async function() {
-    await browser.url('/donate');
-    var creditCardButton = $('.e2e-test-credit-card-donate-button');
-    await action.click('Credit Card button', creditCardButton);
-    expect(await browser.getUrl()).toContain('www.paypal.com');
+    let iframeElement = $('.e2e-test-donate-page-iframe');
+    await waitFor.presenceOf(
+      iframeElement,
+      'Donorbox Iframe taking too long to appear.'
+    );
   });
 });
 
@@ -199,7 +194,11 @@ describe('Static Pages Tour', function() {
   it('should visit the Donate page', async function() {
     await browser.url('/donate');
     await waitFor.pageToFullyLoad();
-    expect(await $('.e2e-test-donate-page').isExisting()).toBe(true);
+    let iframeElement = $('.e2e-test-donate-page-iframe');
+    await waitFor.presenceOf(
+      iframeElement,
+      'Donorbox Iframe taking too long to appear.'
+    );
   });
 
   it('should visit the Partnerships page', async function() {