Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

feat: create ios products on appstore for given course key #4090

Merged
merged 5 commits into from
Jan 11, 2024

Conversation

jawad-khan
Copy link
Contributor

⛔️ MAIN BRANCH WARNING! 2U EMPLOYEES must make branches against the 2u/main BRANCH

  • I have checked the branch to which I would like to merge.

⛔️ DEPRECATION WARNING

This repository is deprecated and in maintainence-only operation while we work on a replacement, please see this announcement for more information.

Although we have stopped integrating new contributions, we always appreciate security disclosures and patches sent to [email protected]

Anyone internally merging to this repository is expected to release and monitor their changes; if you are not able to do this DO NOT MERGE, please coordinate with someone who can to ensure that the changes are released.

Required Testing

  • Before deploying this change, complete a purchase in the stage environment.
    (^ We can remove that manual check once REV-2624 is done and the corresponding e2e test runs again)

Description

We need to increase IAP exposure to the edX mobile app user base. In order to do so we need to create course products within AppStore Connect to support purchases within the app.

Jira ticket: https://2u-internal.atlassian.net/browse/LEARNER-9758

Useful information to include:

  • Which edX user roles will this change impact? Common user roles are "Learner", "Course Author", "Developer", and "Operator".
  • Include screenshots for changes to the UI (ideally, both "before" and "after" screenshots, if applicable).
  • Provide links to the description of corresponding configuration changes. Remember to correctly annotate these changes.

Supporting information

Link to other information about the change, such as Jira issues, GitHub issues, or Discourse discussions.
Be sure to check they are publicly readable, or if not, repeat the information here.

Testing instructions

Please provide detailed step-by-step instructions for testing this change; how did YOU test this change?

Other information

Include anything else that will help reviewers and consumers understand the change.

  • Does this change depend on other changes elsewhere?
  • Any special concerns or limitations? For example: deprecations, migrations, OpenEdx vs. edx.org differences, development vs. production environment differences, security, or accessibility.

@jawad-khan jawad-khan requested a review from a team as a code owner January 5, 2024 14:34
Copy link
Contributor

@moeez96 moeez96 left a comment

Choose a reason for hiding this comment

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

Please add docstrings in the methods in utils so it is clear as to what each method accomplishes and any related details.

}

private_key = configuration['private_key']
logger.error(private_key)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should remove this logger statement

Comment on lines 95 to 100
"reviewNote": 'This in-app purchase will unlock all the content of the course {course_name}\n\n'
'For testing the end-to-end payment flow, please follow the following steps:\n1. '
'Go to the Discover tab\n2. Search for "{course_name}"\n3. Enroll in the course'
' "{course_name}"\n4. Hit \"Upgrade to access more features\", it will open a '
'detail unlock features page\n5. Hit "Upgrade now for ${course_price}" from the'
' detail page'.format(course_name=course['name'], course_price=course['price']),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move the reviewNote string to ecommerce/extensions/iap/api/v1/constants.py?

Comment on lines 114 to 115
logger.error(response.content)
logger.error(response.status_code)
Copy link
Contributor

Choose a reason for hiding this comment

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

To be removed

Comment on lines 167 to 209
data = {
"data": {
"type": "inAppPurchasePriceSchedules",
"attributes": {},
"relationships": {
"inAppPurchase": {
"data": {
"type": "inAppPurchases",
"id": in_app_purchase_id
}
},
"manualPrices": {
"data": [
{
"type": "inAppPurchasePrices",
"id": "${price}"
}
]
},
"baseTerritory": {
"data": {
"type": "territories",
"id": "USA"
}
}
}
},
"included": [
{
"id": "${price}",
"relationships": {
"inAppPurchasePricePoint": {
"data": {
"type": "inAppPurchasePricePoints",
"id": nearest_low_price_id
}
}
},
"type": "inAppPurchasePrices",
"attributes": {
"startDate": None
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move this hardcoded dict to constants and only set variables here?
This would clean up a lot of code.

@@ -17,3 +24,271 @@ def products_in_basket_already_purchased(user, basket, site):
UserAlreadyPlacedOrder.user_already_placed_order(user=user, product=product, site=site):
return True
return False

def create_ios_skus(course, ios_sku, configuration):
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rename this to something like create_ios_products? Since it conflicts with creating skus in Ecommerce itself, where as this method is about creating products on the Appstore. Also adding a docstring would be really helpful.

Comment on lines 244 to 250
with staticfiles_storage.open('images/mobile_ios_product_screenshot.png', 'rb') as image:
img_headers = headers.copy()
img_headers['Content-Type'] = 'image/png'
response = request_connect_store(url, headers=img_headers, data=image.read(), method='put')

if not response.status_code == 200:
raise AppStoreRequestException("Couldn't upload screenshot")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add breif description in a comment or a docstring about what is happening with the screenshot image here from the staticfiles? Where does the screenshot images/mobile_ios_product_screenshot.png come from in the first place?

@@ -385,6 +385,7 @@ def post(self, request):
missing_course_runs = []
failed_course_runs = []
created_skus = {}
failed_ios_skus = []
Copy link
Contributor

Choose a reason for hiding this comment

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

failed_ios_products in favor of failed_ios_skus?

Copy link
Contributor

@julianajlk julianajlk left a comment

Choose a reason for hiding this comment

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

I've noticed the exception messages do not contain any identifiable information, is it on purpose? Curious where these are logged and would they be any helpful for debugging?

Also, could add tests? Coverage is missing on several lines. Thank you!

ecommerce/extensions/iap/api/v1/utils.py Show resolved Hide resolved
http = Session()
retries = Retry(
total=3,
backoff_factor=3,
Copy link
Contributor

Choose a reason for hiding this comment

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

total and backoff_factor, would they ever have a different value? Should they be hard coded? Or can you add a comment on what this is/how this could change for the future

if response.status_code == 201:
return response.json()["data"]["id"]

raise AppStoreRequestException("Couldn't create inapp purchase id")
Copy link
Contributor

Choose a reason for hiding this comment

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

You could add the request call on a try/except, unless assuming if status_code is anything other than 201 it will always be the same exception and it won't error out before it gets to the raise

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you explain it a little?

ecommerce/extensions/iap/api/v1/utils.py Show resolved Hide resolved
}

response = request_connect_store(url, headers, data=data)
if not response.status_code == 201:
Copy link
Contributor

Choose a reason for hiding this comment

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

I see sometimes you use not ... == and sometimes !=, for readability the later is easier

@jawad-khan
Copy link
Contributor Author

I've noticed the exception messages do not contain any identifiable information, is it on purpose? Curious where these are logged and would they be any helpful for debugging?

Also, could add tests? Coverage is missing on several lines. Thank you!

I am just throwing the actual exception in parent function(create_ios_product) where we are adding more context to the actual error.

@jawad-khan jawad-khan merged commit 77569a7 into 2u/main Jan 11, 2024
8 checks passed
@jawad-khan jawad-khan deleted the jawad/LEARNER-9758 branch January 11, 2024 07:04
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants