From 1bba38edb618396fef69f9f995108ba374783e8c Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 19 Apr 2020 11:13:38 +0200 Subject: [PATCH] Add POST /apple/process_payment --- README.md | 8 ++ app/api/__init__.py | 1 + app/api/views/apple.py | 286 ++++++++++++++++++++++++++++++++++++++++ app/config.py | 2 +- tests/api/test_apple.py | 27 ++++ 5 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 app/api/views/apple.py create mode 100644 tests/api/test_apple.py diff --git a/README.md b/README.md index 4aac854b..98a62f17 100644 --- a/README.md +++ b/README.md @@ -1097,6 +1097,14 @@ If success, 200. } ``` +#### POST /apple/process_payment + +Process payment receipt + +Input: +- `Authentication` header that contains the api key +- `receipt_data` the receipt_data base64Encoded returned by StoreKit, i.e. `rawReceiptData.base64EncodedString` + ### Database migration The database migration is handled by `alembic` diff --git a/app/api/__init__.py b/app/api/__init__.py index 182ac59e..a272e69c 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -6,4 +6,5 @@ from .views import ( auth, auth_mfa, alias, + apple, ) diff --git a/app/api/views/apple.py b/app/api/views/apple.py new file mode 100644 index 00000000..ac36c024 --- /dev/null +++ b/app/api/views/apple.py @@ -0,0 +1,286 @@ +from typing import Optional + +import arrow +from flask import g +from flask import jsonify +from flask import request +from flask_cors import cross_origin +import requests + +from app.api.base import api_bp, verify_api_key +from app.api.serializer import ( + AliasInfo, + serialize_alias_info, + get_alias_infos_with_pagination, +) +from app.config import APPLE_API_SECRET +from app.log import LOG +from app.models import PlanEnum, AppleSubscription + +_MONTHLY_PRODUCT_ID = "io.simplelogin.ios_app.subscription.premium.monthly" +_YEARLY_PRODUCT_ID = "io.simplelogin.ios_app.subscription.premium.yearly" + +# Apple API URL +_SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt" +_PROD_URL = "https://buy.itunes.apple.com/verifyReceipt" + + +@api_bp.route("/apple/process_payment", methods=["POST"]) +@cross_origin() +@verify_api_key +def apple_process_payment(): + """ + Process payment + Input: + receipt_data: in body + Output: + 200 of the payment is successful, i.e. user is upgraded to premium + + """ + user = g.user + receipt_data = request.get_json().get("receipt_data") + + apple_sub = verify_receipt(receipt_data, user) + if apple_sub: + return jsonify(ok=True), 200 + + return jsonify(ok=False), 400 + + +def verify_receipt(receipt_data, user) -> Optional[AppleSubscription]: + """Call verifyReceipt endpoint and create/update AppleSubscription table + Call the production URL for verifyReceipt first, + and proceed to verify with the sandbox URL if receive a 21007 status code. + + Return AppleSubscription object if success + + https://developer.apple.com/documentation/appstorereceipts/verifyreceipt + """ + r = requests.post( + _PROD_URL, json={"receipt-data": receipt_data, "password": APPLE_API_SECRET} + ) + + if r.json() == {"status": 21007}: + # try sandbox_url + LOG.warning("Use the sandbox url instead") + r = requests.post( + _SANDBOX_URL, + json={"receipt-data": receipt_data, "password": APPLE_API_SECRET}, + ) + + data = r.json() + LOG.d("response from Apple %s", data) + # data has the following format + # { + # "status": 0, + # "environment": "Sandbox", + # "receipt": { + # "receipt_type": "ProductionSandbox", + # "adam_id": 0, + # "app_item_id": 0, + # "bundle_id": "io.simplelogin.ios-app", + # "application_version": "2", + # "download_id": 0, + # "version_external_identifier": 0, + # "receipt_creation_date": "2020-04-18 16:36:34 Etc/GMT", + # "receipt_creation_date_ms": "1587227794000", + # "receipt_creation_date_pst": "2020-04-18 09:36:34 America/Los_Angeles", + # "request_date": "2020-04-18 16:46:36 Etc/GMT", + # "request_date_ms": "1587228396496", + # "request_date_pst": "2020-04-18 09:46:36 America/Los_Angeles", + # "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT", + # "original_purchase_date_ms": "1375340400000", + # "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles", + # "original_application_version": "1.0", + # "in_app": [ + # { + # "quantity": "1", + # "product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "transaction_id": "1000000653584474", + # "original_transaction_id": "1000000653584474", + # "purchase_date": "2020-04-18 16:27:42 Etc/GMT", + # "purchase_date_ms": "1587227262000", + # "purchase_date_pst": "2020-04-18 09:27:42 America/Los_Angeles", + # "original_purchase_date": "2020-04-18 16:27:44 Etc/GMT", + # "original_purchase_date_ms": "1587227264000", + # "original_purchase_date_pst": "2020-04-18 09:27:44 America/Los_Angeles", + # "expires_date": "2020-04-18 16:32:42 Etc/GMT", + # "expires_date_ms": "1587227562000", + # "expires_date_pst": "2020-04-18 09:32:42 America/Los_Angeles", + # "web_order_line_item_id": "1000000051847459", + # "is_trial_period": "false", + # "is_in_intro_offer_period": "false", + # }, + # { + # "quantity": "1", + # "product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "transaction_id": "1000000653584861", + # "original_transaction_id": "1000000653584474", + # "purchase_date": "2020-04-18 16:32:42 Etc/GMT", + # "purchase_date_ms": "1587227562000", + # "purchase_date_pst": "2020-04-18 09:32:42 America/Los_Angeles", + # "original_purchase_date": "2020-04-18 16:27:44 Etc/GMT", + # "original_purchase_date_ms": "1587227264000", + # "original_purchase_date_pst": "2020-04-18 09:27:44 America/Los_Angeles", + # "expires_date": "2020-04-18 16:37:42 Etc/GMT", + # "expires_date_ms": "1587227862000", + # "expires_date_pst": "2020-04-18 09:37:42 America/Los_Angeles", + # "web_order_line_item_id": "1000000051847461", + # "is_trial_period": "false", + # "is_in_intro_offer_period": "false", + # }, + # ], + # }, + # "latest_receipt_info": [ + # { + # "quantity": "1", + # "product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "transaction_id": "1000000653584474", + # "original_transaction_id": "1000000653584474", + # "purchase_date": "2020-04-18 16:27:42 Etc/GMT", + # "purchase_date_ms": "1587227262000", + # "purchase_date_pst": "2020-04-18 09:27:42 America/Los_Angeles", + # "original_purchase_date": "2020-04-18 16:27:44 Etc/GMT", + # "original_purchase_date_ms": "1587227264000", + # "original_purchase_date_pst": "2020-04-18 09:27:44 America/Los_Angeles", + # "expires_date": "2020-04-18 16:32:42 Etc/GMT", + # "expires_date_ms": "1587227562000", + # "expires_date_pst": "2020-04-18 09:32:42 America/Los_Angeles", + # "web_order_line_item_id": "1000000051847459", + # "is_trial_period": "false", + # "is_in_intro_offer_period": "false", + # "subscription_group_identifier": "20624274", + # }, + # { + # "quantity": "1", + # "product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "transaction_id": "1000000653584861", + # "original_transaction_id": "1000000653584474", + # "purchase_date": "2020-04-18 16:32:42 Etc/GMT", + # "purchase_date_ms": "1587227562000", + # "purchase_date_pst": "2020-04-18 09:32:42 America/Los_Angeles", + # "original_purchase_date": "2020-04-18 16:27:44 Etc/GMT", + # "original_purchase_date_ms": "1587227264000", + # "original_purchase_date_pst": "2020-04-18 09:27:44 America/Los_Angeles", + # "expires_date": "2020-04-18 16:37:42 Etc/GMT", + # "expires_date_ms": "1587227862000", + # "expires_date_pst": "2020-04-18 09:37:42 America/Los_Angeles", + # "web_order_line_item_id": "1000000051847461", + # "is_trial_period": "false", + # "is_in_intro_offer_period": "false", + # "subscription_group_identifier": "20624274", + # }, + # { + # "quantity": "1", + # "product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "transaction_id": "1000000653585235", + # "original_transaction_id": "1000000653584474", + # "purchase_date": "2020-04-18 16:38:16 Etc/GMT", + # "purchase_date_ms": "1587227896000", + # "purchase_date_pst": "2020-04-18 09:38:16 America/Los_Angeles", + # "original_purchase_date": "2020-04-18 16:27:44 Etc/GMT", + # "original_purchase_date_ms": "1587227264000", + # "original_purchase_date_pst": "2020-04-18 09:27:44 America/Los_Angeles", + # "expires_date": "2020-04-18 16:43:16 Etc/GMT", + # "expires_date_ms": "1587228196000", + # "expires_date_pst": "2020-04-18 09:43:16 America/Los_Angeles", + # "web_order_line_item_id": "1000000051847500", + # "is_trial_period": "false", + # "is_in_intro_offer_period": "false", + # "subscription_group_identifier": "20624274", + # }, + # { + # "quantity": "1", + # "product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "transaction_id": "1000000653585760", + # "original_transaction_id": "1000000653584474", + # "purchase_date": "2020-04-18 16:44:25 Etc/GMT", + # "purchase_date_ms": "1587228265000", + # "purchase_date_pst": "2020-04-18 09:44:25 America/Los_Angeles", + # "original_purchase_date": "2020-04-18 16:27:44 Etc/GMT", + # "original_purchase_date_ms": "1587227264000", + # "original_purchase_date_pst": "2020-04-18 09:27:44 America/Los_Angeles", + # "expires_date": "2020-04-18 16:49:25 Etc/GMT", + # "expires_date_ms": "1587228565000", + # "expires_date_pst": "2020-04-18 09:49:25 America/Los_Angeles", + # "web_order_line_item_id": "1000000051847566", + # "is_trial_period": "false", + # "is_in_intro_offer_period": "false", + # "subscription_group_identifier": "20624274", + # }, + # ], + # "latest_receipt": "very long string", + # "pending_renewal_info": [ + # { + # "auto_renew_product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "original_transaction_id": "1000000653584474", + # "product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "auto_renew_status": "1", + # } + # ], + # } + + if data["status"] != 0: + LOG.error( + "verifyReceipt status !=0, probably invalid receipt. User %s", user, + ) + return None + + # each item in data["receipt"]["in_app"] has the following format + # { + # "quantity": "1", + # "product_id": "io.simplelogin.ios_app.subscription.premium.monthly", + # "transaction_id": "1000000653584474", + # "original_transaction_id": "1000000653584474", + # "purchase_date": "2020-04-18 16:27:42 Etc/GMT", + # "purchase_date_ms": "1587227262000", + # "purchase_date_pst": "2020-04-18 09:27:42 America/Los_Angeles", + # "original_purchase_date": "2020-04-18 16:27:44 Etc/GMT", + # "original_purchase_date_ms": "1587227264000", + # "original_purchase_date_pst": "2020-04-18 09:27:44 America/Los_Angeles", + # "expires_date": "2020-04-18 16:32:42 Etc/GMT", + # "expires_date_ms": "1587227562000", + # "expires_date_pst": "2020-04-18 09:32:42 America/Los_Angeles", + # "web_order_line_item_id": "1000000051847459", + # "is_trial_period": "false", + # "is_in_intro_offer_period": "false", + # } + transactions = data["receipt"]["in_app"] + latest_transaction = max(transactions, key=lambda t: int(t["expires_date_ms"])) + original_transaction_id = latest_transaction["original_transaction_id"] + expires_date = arrow.get(int(latest_transaction["expires_date_ms"]) / 1000) + plan = ( + PlanEnum.monthly + if latest_transaction["product_id"] == _MONTHLY_PRODUCT_ID + else PlanEnum.yearly + ) + + apple_sub: AppleSubscription = AppleSubscription.get_by(user_id=user.id) + + if apple_sub: + LOG.d( + "Create new AppleSubscription for user %s, expired at %s, plan %s", + user, + expires_date, + plan, + ) + apple_sub.receipt_data = receipt_data + apple_sub.expires_date = expires_date + apple_sub.original_transaction_id = original_transaction_id + apple_sub.plan = plan + else: + LOG.d( + "Create new AppleSubscription for user %s, expired at %s, plan %s", + user, + expires_date, + plan, + ) + apple_sub = AppleSubscription.create( + user_id=user.id, + receipt_data=receipt_data, + expires_date=expires_date, + original_transaction_id=original_transaction_id, + plan=plan, + ) + + return apple_sub diff --git a/app/config.py b/app/config.py index 7b7c31e1..b0fbad06 100644 --- a/app/config.py +++ b/app/config.py @@ -245,4 +245,4 @@ with open(get_abs_path(DISPOSABLE_FILE_PATH), "r") as f: ] # Used when querying info on Apple API -APPLE_API_SECRET = os.environ.get("APPLE_API_SECRET") \ No newline at end of file +APPLE_API_SECRET = os.environ.get("APPLE_API_SECRET") diff --git a/tests/api/test_apple.py b/tests/api/test_apple.py new file mode 100644 index 00000000..8bb17477 --- /dev/null +++ b/tests/api/test_apple.py @@ -0,0 +1,27 @@ +from flask import url_for + +from app.extensions import db +from app.models import User, ApiKey + + +def test_apple_process_payment(flask_client): + user = User.create( + email="a@b.c", password="password", name="Test User", activated=True + ) + db.session.commit() + + # create api_key + api_key = ApiKey.create(user.id, "for test") + db.session.commit() + + receipt_data = """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""" + + r = flask_client.post( + url_for("api.apple_process_payment"), + headers={"Authentication": api_key.code}, + json={"receipt_data": receipt_data}, + ) + + # will fail anyway as there's apple secret is not valid + assert r.status_code == 400 + assert r.json == {"ok": False}