Enable In-App purchases on edx-mobile app (#31512)

* feat: added mobile skus in course mode

* fix: changed api

---------

Co-authored-by: jawad-khan <jawadkhan444@gmail.com>
Co-authored-by: Robert Raposa <rraposa@edx.org>
This commit is contained in:
Moeez Zahid
2023-02-10 16:16:37 +05:00
committed by GitHub
parent 4abb6b51cb
commit c8af0f607f
7 changed files with 115 additions and 12 deletions

View File

@@ -191,6 +191,8 @@ class CourseModeAdmin(admin.ModelAdmin):
'_expiration_datetime',
'verification_deadline',
'sku',
'android_sku',
'ios_sku',
'bulk_sku'
)
@@ -203,6 +205,8 @@ class CourseModeAdmin(admin.ModelAdmin):
'min_price',
'expiration_datetime_custom',
'sku',
'android_sku',
'ios_sku',
'bulk_sku'
)

View File

@@ -0,0 +1,33 @@
# Generated by Django 3.2.16 on 2023-02-07 12:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_modes', '0013_auto_20200115_2022'),
]
operations = [
migrations.AddField(
model_name='coursemode',
name='android_sku',
field=models.CharField(blank=True, help_text='OPTIONAL: This is the Android SKU registered on play store for this mode of the course. Leave this blank if the course has not yet been migrated to the ecommerce service.', max_length=255, null=True, verbose_name='Android SKU'),
),
migrations.AddField(
model_name='coursemode',
name='ios_sku',
field=models.CharField(blank=True, help_text='OPTIONAL: This is the iOS SKU registered on app store for this mode of the course. Leave this blank if the course has not yet been migrated to the ecommerce service.', max_length=255, null=True, verbose_name='iOS SKU'),
),
migrations.AddField(
model_name='historicalcoursemode',
name='android_sku',
field=models.CharField(blank=True, help_text='OPTIONAL: This is the Android SKU registered on play store for this mode of the course. Leave this blank if the course has not yet been migrated to the ecommerce service.', max_length=255, null=True, verbose_name='Android SKU'),
),
migrations.AddField(
model_name='historicalcoursemode',
name='ios_sku',
field=models.CharField(blank=True, help_text='OPTIONAL: This is the iOS SKU registered on app store for this mode of the course. Leave this blank if the course has not yet been migrated to the ecommerce service.', max_length=255, null=True, verbose_name='iOS SKU'),
),
]

View File

@@ -35,6 +35,8 @@ Mode = namedtuple('Mode',
'expiration_datetime',
'description',
'sku',
'android_sku',
'ios_sku',
'bulk_sku',
])
@@ -114,6 +116,30 @@ class CourseMode(models.Model):
)
)
# Optional Android SKU for integration with mobile and the ecommerce service
android_sku = models.CharField(
max_length=255,
null=True,
blank=True,
verbose_name="Android SKU",
help_text=_(
"OPTIONAL: This is the Android SKU registered on play store for this mode of the course. "
"Leave this blank if the course has not yet been migrated to the ecommerce service."
)
)
# Optional iOS SKU for integration with mobile and the ecommerce service
ios_sku = models.CharField(
max_length=255,
null=True,
blank=True,
verbose_name="iOS SKU",
help_text=_(
"OPTIONAL: This is the iOS SKU registered on app store for this mode of the course. "
"Leave this blank if the course has not yet been migrated to the ecommerce service."
)
)
# Optional bulk order SKU for integration with the ecommerce service
bulk_sku = models.CharField(
max_length=255,
@@ -150,6 +176,8 @@ class CourseMode(models.Model):
settings.COURSE_MODE_DEFAULTS['expiration_datetime'],
settings.COURSE_MODE_DEFAULTS['description'],
settings.COURSE_MODE_DEFAULTS['sku'],
settings.COURSE_MODE_DEFAULTS['android_sku'],
settings.COURSE_MODE_DEFAULTS['ios_sku'],
settings.COURSE_MODE_DEFAULTS['bulk_sku'],
)
DEFAULT_MODE_SLUG = settings.COURSE_MODE_DEFAULTS['slug']
@@ -817,6 +845,8 @@ class CourseMode(models.Model):
self.expiration_datetime,
self.description,
self.sku,
self.android_sku,
self.ios_sku,
self.bulk_sku
)

View File

@@ -95,7 +95,7 @@ class CourseModeModelTest(TestCase):
self.create_mode('verified', 'Verified Certificate', 10)
modes = CourseMode.modes_for_course(self.course_key)
mode = Mode('verified', 'Verified Certificate', 10, '', 'usd', None, None, None, None)
mode = Mode('verified', 'Verified Certificate', 10, '', 'usd', None, None, None, None, None, None)
assert [mode] == modes
modes_dict = CourseMode.modes_for_course_dict(self.course_key)
@@ -106,8 +106,8 @@ class CourseModeModelTest(TestCase):
"""
Finding the modes when there's multiple modes
"""
mode1 = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
mode2 = Mode('verified', 'Verified Certificate', 10, '', 'usd', None, None, None, None)
mode1 = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None, None, None)
mode2 = Mode('verified', 'Verified Certificate', 10, '', 'usd', None, None, None, None, None, None)
set_modes = [mode1, mode2]
for mode in set_modes:
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices)
@@ -126,14 +126,14 @@ class CourseModeModelTest(TestCase):
assert 0 == CourseMode.min_course_price_for_currency(self.course_key, 'usd')
# with mode with other currency, should get 0
mode = Mode('audit', 'Audit', 30, '', 'eur', None, None, None, None)
mode = Mode('audit', 'Audit', 30, '', 'eur', None, None, None, None, None, None)
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices, mode.currency)
assert 0 == CourseMode.min_course_price_for_currency(self.course_key, 'usd')
# create some modes
mode1 = Mode('honor', 'Honor Code Certificate', 10, '', 'usd', None, None, None, None)
mode2 = Mode('verified', 'Verified Certificate', 20, '', 'usd', None, None, None, None)
mode3 = Mode('honor', 'Honor Code Certificate', 80, '', 'cny', None, None, None, None)
mode1 = Mode('honor', 'Honor Code Certificate', 10, '', 'usd', None, None, None, None, None, None)
mode2 = Mode('verified', 'Verified Certificate', 20, '', 'usd', None, None, None, None, None, None)
mode3 = Mode('honor', 'Honor Code Certificate', 80, '', 'cny', None, None, None, None, None, None)
set_modes = [mode1, mode2, mode3]
for mode in set_modes:
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices, mode.currency)
@@ -148,7 +148,7 @@ class CourseModeModelTest(TestCase):
modes = CourseMode.modes_for_course(self.course_key)
assert [CourseMode.DEFAULT_MODE] == modes
mode1 = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
mode1 = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None, None, None)
self.create_mode(mode1.slug, mode1.name, mode1.min_price, mode1.suggested_prices)
modes = CourseMode.modes_for_course(self.course_key)
assert [mode1] == modes
@@ -165,6 +165,8 @@ class CourseModeModelTest(TestCase):
expiration_datetime,
None,
None,
None,
None,
None
)
modes = CourseMode.modes_for_course(self.course_key)

View File

@@ -407,7 +407,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
assert response.status_code == 200
expected_mode = [Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)]
expected_mode = [Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None, None, None)]
course_mode = CourseMode.modes_for_course(self.course.id)
assert course_mode == expected_mode
@@ -441,6 +441,8 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
None,
None,
None,
None,
None,
None
)
]
@@ -466,8 +468,8 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
url = reverse('create_mode', args=[str(self.course.id)])
self.client.get(url, parameters)
honor_mode = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
verified_mode = Mode('verified', 'Verified Certificate', 10, '10,20', 'usd', None, None, None, None)
honor_mode = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None, None, None)
verified_mode = Mode('verified', 'Verified Certificate', 10, '10,20', 'usd', None, None, None, None, None, None)
expected_modes = [honor_mode, verified_mode]
course_modes = CourseMode.modes_for_course(self.course.id)