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:
@@ -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'
|
||||
)
|
||||
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user