From 5ebad160603dafe6f2751c57987b5b0d83db8b4a Mon Sep 17 00:00:00 2001 From: Matheus Lima Date: Fri, 8 Mar 2019 19:37:45 -0300 Subject: [PATCH 1/8] Starting to implement the stripe integration --- api/account/Pipfile.lock | 225 ++++++------------ .../tests/features/steps/new_account.py | 2 +- api/public/public_api/endpoints/device.py | 1 - shared/Pipfile | 1 + shared/Pipfile.lock | 17 +- shared/selene/api/endpoints/account.py | 14 +- 6 files changed, 99 insertions(+), 161 deletions(-) diff --git a/api/account/Pipfile.lock b/api/account/Pipfile.lock index 7f687ef3..8a6a53fd 100644 --- a/api/account/Pipfile.lock +++ b/api/account/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "daaf58243bb30ef012d7013696ccfce15271d61670ef9bd6cebe9d7ffadefd6e" + "sha256": "3e2ce31477884f9983a6eb644183790f946eafc0b88a068507a8e42512c86d70" }, "pipfile-spec": 6, "requires": { @@ -16,100 +16,6 @@ ] }, "default": { - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "version": "==7.0" - }, - "flask": { - "hashes": [ - "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", - "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" - ], - "index": "pypi", - "version": "==1.0.2" - }, - "itsdangerous": { - "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" - ], - "version": "==1.1.0" - }, - "jinja2": { - "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" - ], - "version": "==2.10" - }, - "markupsafe": { - "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" - ], - "version": "==1.1.0" - }, - "schematics": { - "hashes": [ - "sha256:8fcc6182606fd0b24410a1dbb066d9bbddbe8da9c9509f47b743495706239283", - "sha256:a40b20635c0e43d18d3aff76220f6cd95ea4decb3f37765e49529b17d81b0439" - ], - "index": "pypi", - "version": "==2.1.0" - }, - "uwsgi": { - "hashes": [ - "sha256:4972ac538800fb2d421027f49b4a1869b66048839507ccf0aa2fda792d99f583" - ], - "index": "pypi", - "version": "==2.0.18" - }, - "werkzeug": { - "hashes": [ - "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", - "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" - ], - "version": "==0.14.1" - } - }, - "develop": { - "behave": { - "hashes": [ - "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", - "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c" - ], - "index": "pypi", - "version": "==1.2.6" - }, "certifi": { "hashes": [ "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", @@ -133,10 +39,10 @@ }, "deprecated": { "hashes": [ - "sha256:8bfeba6e630abf42b5d111b68a05f7fe3d6de7004391b3cd614947594f87a4ff", - "sha256:b784e0ca85a8c1e694d77e545c10827bd99772392e79d5f5442e761515a1246e" + "sha256:2f293eb0eee34b1fcf3da530fe8fc4b0d71d43ddc2dc78e2ffb444b6c0868557", + "sha256:749f6cdcfbdc3f79258f8154bad43fced95adc632c337675d0385959895894bc" ], - "version": "==1.2.4" + "version": "==1.2.5" }, "flask": { "hashes": [ @@ -167,58 +73,38 @@ ], "version": "==2.10" }, - "jsonschema": { - "hashes": [ - "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", - "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" - ], - "version": "==2.6.0" - }, "markupsafe": { "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], - "version": "==1.1.0" - }, - "parse": { - "hashes": [ - "sha256:870dd675c1ee8951db3e29b81ebe44fd131e3eb8c03a79483a58ea574f3145c2" - ], - "version": "==1.11.1" - }, - "parse-type": { - "hashes": [ - "sha256:6e906a66f340252e4c324914a60d417d33a4bea01292ea9bbf68b4fc123be8c9", - "sha256:f596bdc75d3dd93036fbfe3d04127da9f6df0c26c36e01e76da85adef4336b3c" - ], - "version": "==0.4.2" + "version": "==1.1.1" }, "passlib": { "hashes": [ @@ -273,7 +159,6 @@ "sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420", "sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd" ], - "index": "pypi", "version": "==1.9.0" }, "pyjwt": { @@ -323,11 +208,12 @@ ], "version": "==1.24.1" }, - "validator-collection": { + "uwsgi": { "hashes": [ - "sha256:e8ddec6d301bd3be40cacb9d4f9f85573bc003e3e17a66ba7267ef46b9a8e3d2" + "sha256:4972ac538800fb2d421027f49b4a1869b66048839507ccf0aa2fda792d99f583" ], - "version": "==1.3.2" + "index": "pypi", + "version": "==2.0.18" }, "werkzeug": { "hashes": [ @@ -342,5 +228,42 @@ ], "version": "==1.11.1" } + }, + "develop": { + "behave": { + "hashes": [ + "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", + "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c" + ], + "index": "pypi", + "version": "==1.2.6" + }, + "parse": { + "hashes": [ + "sha256:870dd675c1ee8951db3e29b81ebe44fd131e3eb8c03a79483a58ea574f3145c2" + ], + "version": "==1.11.1" + }, + "parse-type": { + "hashes": [ + "sha256:6e906a66f340252e4c324914a60d417d33a4bea01292ea9bbf68b4fc123be8c9", + "sha256:f596bdc75d3dd93036fbfe3d04127da9f6df0c26c36e01e76da85adef4336b3c" + ], + "version": "==0.4.2" + }, + "pyhamcrest": { + "hashes": [ + "sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420", + "sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd" + ], + "version": "==1.9.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + } } } diff --git a/api/account/tests/features/steps/new_account.py b/api/account/tests/features/steps/new_account.py index 91cc4f72..27984c52 100644 --- a/api/account/tests/features/steps/new_account.py +++ b/api/account/tests/features/steps/new_account.py @@ -39,7 +39,7 @@ def change_membership_option(context): context.new_account_request['support'].update( membership='Monthly Membership', paymentMethod='Stripe', - paymentAccountId='barstripe' + paymentToken='tok_visa' ) diff --git a/api/public/public_api/endpoints/device.py b/api/public/public_api/endpoints/device.py index e3740db7..1f1b4473 100644 --- a/api/public/public_api/endpoints/device.py +++ b/api/public/public_api/endpoints/device.py @@ -1,5 +1,4 @@ import json -from dataclasses import asdict from http import HTTPStatus from schematics import Model diff --git a/shared/Pipfile b/shared/Pipfile index b6da45ba..20e8bf83 100644 --- a/shared/Pipfile +++ b/shared/Pipfile @@ -11,6 +11,7 @@ passlib = "*" pyhamcrest = "*" schematics = "*" redis = "*" +stripe = "*" [dev-packages] diff --git a/shared/Pipfile.lock b/shared/Pipfile.lock index 119cf7ad..98ca0162 100644 --- a/shared/Pipfile.lock +++ b/shared/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c0c1a9ec56002f00c9a50fc67aba1b0463d05323813944a244860fd69f49b75c" + "sha256": "302bee79d7d85ce5050af796f262d7e1000ecf5be0353cdf565273cc1823785a" }, "pipfile-spec": 6, "requires": { @@ -32,10 +32,10 @@ }, "deprecated": { "hashes": [ - "sha256:8bfeba6e630abf42b5d111b68a05f7fe3d6de7004391b3cd614947594f87a4ff", - "sha256:b784e0ca85a8c1e694d77e545c10827bd99772392e79d5f5442e761515a1246e" + "sha256:2f293eb0eee34b1fcf3da530fe8fc4b0d71d43ddc2dc78e2ffb444b6c0868557", + "sha256:749f6cdcfbdc3f79258f8154bad43fced95adc632c337675d0385959895894bc" ], - "version": "==1.2.4" + "version": "==1.2.5" }, "idna": { "hashes": [ @@ -124,6 +124,7 @@ "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], + "markers": "python_version >= '3.0'", "version": "==2.21.0" }, "schematics": { @@ -141,6 +142,14 @@ ], "version": "==1.12.0" }, + "stripe": { + "hashes": [ + "sha256:170f76f2502888debf02da580138c840497b9359876ca3838f4692f2f02c9110", + "sha256:35441857c8d6969a3f319f0f3cb0ddf853fe36ed451b3b711bc0295241c38444" + ], + "index": "pypi", + "version": "==2.21.0" + }, "urllib3": { "hashes": [ "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", diff --git a/shared/selene/api/endpoints/account.py b/shared/selene/api/endpoints/account.py index 1b807daa..7350c639 100644 --- a/shared/selene/api/endpoints/account.py +++ b/shared/selene/api/endpoints/account.py @@ -22,6 +22,7 @@ from ..base_endpoint import SeleneEndpoint MONTHLY_MEMBERSHIP = 'Monthly Membership' YEARLY_MEMBERSHIP = 'Yearly Membership' NO_MEMBERSHIP = 'Maybe Later' +STRIPE_PAYMENT = 'Stripe' def agreement_accepted(value): @@ -55,8 +56,8 @@ class Support(Model): required=True, choices=(MONTHLY_MEMBERSHIP, YEARLY_MEMBERSHIP, NO_MEMBERSHIP) ) - payment_method = StringType() - payment_account_id = StringType() + payment_method = StringType(choices=[STRIPE_PAYMENT]) + payment_token = StringType() def validate_payment_account_id(self, data, value): if data['membership'] != NO_MEMBERSHIP: @@ -168,7 +169,7 @@ class AccountEndpoint(SeleneEndpoint): open_dataset=support_data.get('openDataset'), membership=support_data.get('membership'), payment_method=support_data.get('paymentMethod'), - payment_account_id=support_data.get('paymentAccountId') + payment_token=support_data.get('paymentToken') )) return support @@ -188,7 +189,9 @@ class AccountEndpoint(SeleneEndpoint): membership_type = self.request_data['support']['membership'] membership = None if membership_type != NO_MEMBERSHIP: - payment_account = self.request_data['support']['paymentAccountId'] + payment_token = self.request_data['support']['paymentToken'] + email = self.request.data['login']['userEnteredEmail'] + payment_account = self._create_stripe_subscription(payment_token, email) membership = AccountMembership( type=membership_type, start_date=date.today(), @@ -210,3 +213,6 @@ class AccountEndpoint(SeleneEndpoint): account, password=password ) + + def _create_stripe_subscription(self, token, account_email) -> str: + pass From ee879ff6b5399b5adb48ee76109c3c525cd16b45 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Fri, 8 Mar 2019 16:56:53 -0600 Subject: [PATCH 2/8] moved shared package out of dev --- api/sso/Pipfile | 2 +- api/sso/Pipfile.lock | 225 ++++++++++++++++--------------------------- 2 files changed, 85 insertions(+), 142 deletions(-) diff --git a/api/sso/Pipfile b/api/sso/Pipfile index e53a55bf..6ea80a9c 100644 --- a/api/sso/Pipfile +++ b/api/sso/Pipfile @@ -6,10 +6,10 @@ name = "pypi" [packages] flask = "*" certifi = "*" +selene = {editable = true,path = "./../../shared"} uwsgi = "*" [dev-packages] -selene = {editable = true,path = "./../../shared"} behave = "*" pyhamcrest = "*" diff --git a/api/sso/Pipfile.lock b/api/sso/Pipfile.lock index 2c3db34f..45eb43e4 100644 --- a/api/sso/Pipfile.lock +++ b/api/sso/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e27bc9018c42543c8594ffade1899d7d7c9cef2117f4c48462b0971310caeb0f" + "sha256": "8a88efc755cb5bfcda2c6e9db57fb62a506e475386cdcda3da69435427d0a2fe" }, "pipfile-spec": 6, "requires": { @@ -16,100 +16,6 @@ ] }, "default": { - "certifi": { - "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" - ], - "index": "pypi", - "version": "==2018.11.29" - }, - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "version": "==7.0" - }, - "flask": { - "hashes": [ - "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", - "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" - ], - "index": "pypi", - "version": "==1.0.2" - }, - "itsdangerous": { - "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" - ], - "version": "==1.1.0" - }, - "jinja2": { - "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" - ], - "version": "==2.10" - }, - "markupsafe": { - "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" - ], - "version": "==1.1.0" - }, - "uwsgi": { - "hashes": [ - "sha256:4972ac538800fb2d421027f49b4a1869b66048839507ccf0aa2fda792d99f583" - ], - "index": "pypi", - "version": "==2.0.18" - }, - "werkzeug": { - "hashes": [ - "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", - "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" - ], - "version": "==0.14.1" - } - }, - "develop": { - "behave": { - "hashes": [ - "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", - "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c" - ], - "index": "pypi", - "version": "==1.2.6" - }, "certifi": { "hashes": [ "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", @@ -134,10 +40,10 @@ }, "deprecated": { "hashes": [ - "sha256:8bfeba6e630abf42b5d111b68a05f7fe3d6de7004391b3cd614947594f87a4ff", - "sha256:b784e0ca85a8c1e694d77e545c10827bd99772392e79d5f5442e761515a1246e" + "sha256:2f293eb0eee34b1fcf3da530fe8fc4b0d71d43ddc2dc78e2ffb444b6c0868557", + "sha256:749f6cdcfbdc3f79258f8154bad43fced95adc632c337675d0385959895894bc" ], - "version": "==1.2.4" + "version": "==1.2.5" }, "flask": { "hashes": [ @@ -170,49 +76,36 @@ }, "markupsafe": { "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], - "version": "==1.1.0" - }, - "parse": { - "hashes": [ - "sha256:870dd675c1ee8951db3e29b81ebe44fd131e3eb8c03a79483a58ea574f3145c2" - ], - "version": "==1.11.1" - }, - "parse-type": { - "hashes": [ - "sha256:6e906a66f340252e4c324914a60d417d33a4bea01292ea9bbf68b4fc123be8c9", - "sha256:f596bdc75d3dd93036fbfe3d04127da9f6df0c26c36e01e76da85adef4336b3c" - ], - "version": "==0.4.2" + "version": "==1.1.1" }, "passlib": { "hashes": [ @@ -267,7 +160,6 @@ "sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420", "sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd" ], - "index": "pypi", "version": "==1.9.0" }, "pyjwt": { @@ -277,6 +169,13 @@ ], "version": "==1.7.1" }, + "redis": { + "hashes": [ + "sha256:724932360d48e5407e8f82e405ab3650a36ed02c7e460d1e6fddf0f038422b54", + "sha256:9b19425a38fd074eb5795ff2b0d9a55b46a44f91f5347995f27e3ad257a7d775" + ], + "version": "==3.2.0" + }, "requests": { "hashes": [ "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", @@ -309,6 +208,13 @@ ], "version": "==1.24.1" }, + "uwsgi": { + "hashes": [ + "sha256:4972ac538800fb2d421027f49b4a1869b66048839507ccf0aa2fda792d99f583" + ], + "index": "pypi", + "version": "==2.0.18" + }, "werkzeug": { "hashes": [ "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", @@ -322,5 +228,42 @@ ], "version": "==1.11.1" } + }, + "develop": { + "behave": { + "hashes": [ + "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", + "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c" + ], + "index": "pypi", + "version": "==1.2.6" + }, + "parse": { + "hashes": [ + "sha256:870dd675c1ee8951db3e29b81ebe44fd131e3eb8c03a79483a58ea574f3145c2" + ], + "version": "==1.11.1" + }, + "parse-type": { + "hashes": [ + "sha256:6e906a66f340252e4c324914a60d417d33a4bea01292ea9bbf68b4fc123be8c9", + "sha256:f596bdc75d3dd93036fbfe3d04127da9f6df0c26c36e01e76da85adef4336b3c" + ], + "version": "==0.4.2" + }, + "pyhamcrest": { + "hashes": [ + "sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420", + "sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd" + ], + "version": "==1.9.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + } } } From ae27dd07776d7390c6072a71a2318d5e410cd904 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Fri, 8 Mar 2019 16:57:56 -0600 Subject: [PATCH 3/8] removed unused module --- shared/selene/data/device/entity/setting.py | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 shared/selene/data/device/entity/setting.py diff --git a/shared/selene/data/device/entity/setting.py b/shared/selene/data/device/entity/setting.py deleted file mode 100644 index 436016db..00000000 --- a/shared/selene/data/device/entity/setting.py +++ /dev/null @@ -1,9 +0,0 @@ -from dataclasses import dataclass - - -@dataclass -class AccountPreferences(object): - id: str - date_format: str - time_format: str - measurement_system: str From 03f3cdfa8ed76610da063b45aaa4c5460121c336 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Fri, 8 Mar 2019 17:02:17 -0600 Subject: [PATCH 4/8] added stripe plan name column --- db/mycroft/account_schema/tables/membership.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/mycroft/account_schema/tables/membership.sql b/db/mycroft/account_schema/tables/membership.sql index 6a93d6e0..30835cb3 100644 --- a/db/mycroft/account_schema/tables/membership.sql +++ b/db/mycroft/account_schema/tables/membership.sql @@ -1,10 +1,11 @@ CREATE TABLE account.membership ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - type membership_type_enum NOT NULL + type membership_type_enum NOT NULL UNIQUE, rate NUMERIC NOT NULL, rate_period text NOT NULL, + stripe_plan text NOT NULL, insert_ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); From a0368aa2de1e1284daaff75a236ec015d4b55d99 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Fri, 8 Mar 2019 17:27:05 -0600 Subject: [PATCH 5/8] added stripe_plan to the insert statements --- db/mycroft/account_schema/data/membership.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/mycroft/account_schema/data/membership.sql b/db/mycroft/account_schema/data/membership.sql index 77feb905..aac7cfee 100644 --- a/db/mycroft/account_schema/data/membership.sql +++ b/db/mycroft/account_schema/data/membership.sql @@ -1,6 +1,6 @@ INSERT INTO - account.membership (type, rate, rate_period) + account.membership (type, rate, rate_period, stripe_plan) VALUES - ('Monthly Membership', 1.99, 'month'), - ('Yearly Membership', 19.99, 'year') + ('Monthly Membership', 1.99, 'month', 'monthly_premium'), + ('Yearly Membership', 19.99, 'year', 'mycroft_ai_premium_annual_1999') ; From 10b7ea675ab8d03b743ef44c9f29978536334bf2 Mon Sep 17 00:00:00 2001 From: Chris Veilleux Date: Fri, 8 Mar 2019 17:27:45 -0600 Subject: [PATCH 6/8] added endpoint to get membership types and the supporting repository logic --- api/account/account_api/api.py | 8 ++++++++ api/account/account_api/endpoints/membership.py | 16 ++++++++++++++++ shared/selene/data/account/entity/membership.py | 1 + .../selene/data/account/repository/membership.py | 8 ++++++++ .../repository/sql/get_membership_types.sql | 8 ++++++++ 5 files changed, 41 insertions(+) create mode 100644 api/account/account_api/endpoints/membership.py create mode 100644 shared/selene/data/account/repository/sql/get_membership_types.sql diff --git a/api/account/account_api/api.py b/api/account/account_api/api.py index fedf971f..9aef7864 100644 --- a/api/account/account_api/api.py +++ b/api/account/account_api/api.py @@ -8,6 +8,7 @@ from .endpoints.account_preferences import AccountPreferencesEndpoint from .endpoints.device import DeviceEndpoint from .endpoints.device_count import DeviceCountEndpoint from .endpoints.geography import GeographyEndpoint +from .endpoints.membership import MembershipEndpoint from .endpoints.skills import SkillsEndpoint from .endpoints.skill_settings import SkillSettingsEndpoint from .endpoints.voice_endpoint import VoiceEndpoint @@ -90,3 +91,10 @@ acct.add_url_rule( view_func=geography_endpoint, methods=['GET'] ) + +membership_endpoint = MembershipEndpoint.as_view('membership_endpoint') +acct.add_url_rule( + '/api/memberships', + view_func=membership_endpoint, + methods=['GET'] +) diff --git a/api/account/account_api/endpoints/membership.py b/api/account/account_api/endpoints/membership.py new file mode 100644 index 00000000..549705fc --- /dev/null +++ b/api/account/account_api/endpoints/membership.py @@ -0,0 +1,16 @@ +from http import HTTPStatus + +from selene.api import SeleneEndpoint +from selene.data.account import MembershipRepository +from selene.util.db import get_db_connection + + +class MembershipEndpoint(SeleneEndpoint): + def get(self): + with get_db_connection(self.config['DB_CONNECTION_POOL']) as db: + membership_repository = MembershipRepository(db) + membership_types = membership_repository.get_membership_types() + + for membership_type in membership_types: + membership_type.rate = float(membership_type.rate) + return membership_types, HTTPStatus.OK diff --git a/shared/selene/data/account/entity/membership.py b/shared/selene/data/account/entity/membership.py index 6d716564..05c1432f 100644 --- a/shared/selene/data/account/entity/membership.py +++ b/shared/selene/data/account/entity/membership.py @@ -7,4 +7,5 @@ class Membership(object): type: str rate: Decimal rate_period: str + stripe_plan: str id: str = None diff --git a/shared/selene/data/account/repository/membership.py b/shared/selene/data/account/repository/membership.py index de498822..5db464b2 100644 --- a/shared/selene/data/account/repository/membership.py +++ b/shared/selene/data/account/repository/membership.py @@ -6,6 +6,14 @@ class MembershipRepository(RepositoryBase): def __init__(self, db): super(MembershipRepository, self).__init__(db, __file__) + def get_membership_types(self): + db_request = self._build_db_request( + sql_file_name='get_membership_types.sql' + ) + db_result = self.cursor.select_all(db_request) + + return [Membership(**row) for row in db_result] + def add(self, membership: Membership): db_request = self._build_db_request( 'add_membership.sql', diff --git a/shared/selene/data/account/repository/sql/get_membership_types.sql b/shared/selene/data/account/repository/sql/get_membership_types.sql new file mode 100644 index 00000000..1b908c8f --- /dev/null +++ b/shared/selene/data/account/repository/sql/get_membership_types.sql @@ -0,0 +1,8 @@ +SELECT + id, + type, + rate, + rate_period, + stripe_plan +FROM + account.membership From f02d1f151e0652ae3db970b6aec16c496dcbf39d Mon Sep 17 00:00:00 2001 From: Matheus Lima Date: Sat, 9 Mar 2019 00:32:09 -0300 Subject: [PATCH 7/8] Implemented stripe integration --- .../tests/features/steps/new_account.py | 4 +-- api/public/Pipfile | 1 + api/public/Pipfile.lock | 10 +++++++- shared/selene/api/endpoints/account.py | 25 +++++++++++++------ .../data/account/repository/membership.py | 8 ++++++ .../repository/sql/get_membership_by_type.sql | 10 ++++++++ 6 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 shared/selene/data/account/repository/sql/get_membership_by_type.sql diff --git a/api/account/tests/features/steps/new_account.py b/api/account/tests/features/steps/new_account.py index 27984c52..b593d9f3 100644 --- a/api/account/tests/features/steps/new_account.py +++ b/api/account/tests/features/steps/new_account.py @@ -2,7 +2,7 @@ from datetime import date from behave import given, then, when from flask import json -from hamcrest import assert_that, equal_to, is_in, none, not_none +from hamcrest import assert_that, equal_to, is_in, none, not_none, starts_with from selene.data.account import AccountRepository, PRIVACY_POLICY, TERMS_OF_USE from selene.util.db import get_db_connection @@ -72,7 +72,7 @@ def check_db_for_account(context, membership_option): assert_that(account.membership.type, equal_to('Monthly Membership')) assert_that( account.membership.payment_account_id, - equal_to('barstripe') + starts_with('cus') ) elif membership_option == 'without a membership': assert_that(account.membership, none()) diff --git a/api/public/Pipfile b/api/public/Pipfile index a8034af9..84cd4456 100644 --- a/api/public/Pipfile +++ b/api/public/Pipfile @@ -12,6 +12,7 @@ flask = "*" requests = "*" SpeechRecognition = "*" uwsgi = "*" +stripe = "*" [requires] python_version = "3.7" diff --git a/api/public/Pipfile.lock b/api/public/Pipfile.lock index 9bb3128d..04fd3571 100644 --- a/api/public/Pipfile.lock +++ b/api/public/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5fa825684354d0d9bb2336a9ecdce746401ea0d5c3f1e96b5f508f8a5b237c4c" + "sha256": "334f43f32cee22ca9973853301b232a9e302331dacc5b00f348c93637f89cae3" }, "pipfile-spec": 6, "requires": { @@ -114,6 +114,14 @@ "index": "pypi", "version": "==3.8.1" }, + "stripe": { + "hashes": [ + "sha256:170f76f2502888debf02da580138c840497b9359876ca3838f4692f2f02c9110", + "sha256:35441857c8d6969a3f319f0f3cb0ddf853fe36ed451b3b711bc0295241c38444" + ], + "index": "pypi", + "version": "==2.21.0" + }, "urllib3": { "hashes": [ "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", diff --git a/shared/selene/api/endpoints/account.py b/shared/selene/api/endpoints/account.py index 7350c639..0b17d6ee 100644 --- a/shared/selene/api/endpoints/account.py +++ b/shared/selene/api/endpoints/account.py @@ -1,8 +1,10 @@ """API endpoint to return the a logged-in user's profile""" +import os from dataclasses import asdict from datetime import date, datetime, timedelta from http import HTTPStatus +import stripe from flask import json, jsonify from schematics import Model from schematics.exceptions import ValidationError @@ -14,8 +16,8 @@ from selene.data.account import ( AccountRepository, AccountMembership, PRIVACY_POLICY, - TERMS_OF_USE -) + TERMS_OF_USE, + MembershipRepository) from selene.util.db import get_db_connection from ..base_endpoint import SeleneEndpoint @@ -24,6 +26,8 @@ YEARLY_MEMBERSHIP = 'Yearly Membership' NO_MEMBERSHIP = 'Maybe Later' STRIPE_PAYMENT = 'Stripe' +stripe.api_key = os.environ['STRIPE_PRIVATE_KEY'] + def agreement_accepted(value): if not value: @@ -190,13 +194,14 @@ class AccountEndpoint(SeleneEndpoint): membership = None if membership_type != NO_MEMBERSHIP: payment_token = self.request_data['support']['paymentToken'] - email = self.request.data['login']['userEnteredEmail'] - payment_account = self._create_stripe_subscription(payment_token, email) + email = self.request_data['login']['userEnteredEmail'] + plan = self._get_plan(membership_type).stripe_plan + payment_account_id, start = self._create_stripe_subscription(payment_token, email, plan) membership = AccountMembership( type=membership_type, start_date=date.today(), payment_method=self.request_data['support']['paymentMethod'], - payment_account_id=payment_account + payment_account_id=payment_account_id ) account = Account( email_address=email_address, @@ -214,5 +219,11 @@ class AccountEndpoint(SeleneEndpoint): password=password ) - def _create_stripe_subscription(self, token, account_email) -> str: - pass + def _create_stripe_subscription(self, token, user_email, plan): + customer = stripe.Customer.create(source=token, email=user_email) + subscription = stripe.Subscription.create(customer=customer.id, items=[{'plan': plan}]) + return customer.id, subscription.current_period_start + + def _get_plan(self, plan): + with get_db_connection(self.config['DB_CONNECTION_POOL']) as db: + return MembershipRepository(db).get_membership_by_type(plan) diff --git a/shared/selene/data/account/repository/membership.py b/shared/selene/data/account/repository/membership.py index 5db464b2..92b23d5e 100644 --- a/shared/selene/data/account/repository/membership.py +++ b/shared/selene/data/account/repository/membership.py @@ -14,6 +14,14 @@ class MembershipRepository(RepositoryBase): return [Membership(**row) for row in db_result] + def get_membership_by_type(self, type: str): + db_request = self._build_db_request( + sql_file_name='get_membership_by_type.sql', + args=dict(type=type) + ) + db_result = self.cursor.select_one(db_request) + return Membership(**db_result) + def add(self, membership: Membership): db_request = self._build_db_request( 'add_membership.sql', diff --git a/shared/selene/data/account/repository/sql/get_membership_by_type.sql b/shared/selene/data/account/repository/sql/get_membership_by_type.sql new file mode 100644 index 00000000..53911168 --- /dev/null +++ b/shared/selene/data/account/repository/sql/get_membership_by_type.sql @@ -0,0 +1,10 @@ +SELECT + id, + type, + rate, + rate_period, + stripe_plan +FROM + account.membership +WHERE + type = %(type)s \ No newline at end of file From 7fc2a54ab03b99657b1feff9649c41ef973b47e9 Mon Sep 17 00:00:00 2001 From: Matheus Lima Date: Wed, 13 Mar 2019 18:19:13 -0300 Subject: [PATCH 8/8] Created PATCH request to update membership --- api/account/account_api/api.py | 4 +- .../tests/features/update_membership.feature | 5 ++ .../features/steps/get_device_subscription.py | 2 +- shared/selene/api/endpoints/account.py | 82 ++++++++++++++++--- shared/selene/data/account/entity/account.py | 4 +- .../selene/data/account/repository/account.py | 4 +- .../data/account/repository/membership.py | 25 +++++- .../repository/sql/finish_membership.sql | 6 ++ .../get_active_membership_by_account_id.sql | 6 ++ 9 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 api/account/tests/features/update_membership.feature create mode 100644 shared/selene/data/account/repository/sql/finish_membership.sql create mode 100644 shared/selene/data/account/repository/sql/get_active_membership_by_account_id.sql diff --git a/api/account/account_api/api.py b/api/account/account_api/api.py index 9aef7864..f72cd9a7 100644 --- a/api/account/account_api/api.py +++ b/api/account/account_api/api.py @@ -9,8 +9,8 @@ from .endpoints.device import DeviceEndpoint from .endpoints.device_count import DeviceCountEndpoint from .endpoints.geography import GeographyEndpoint from .endpoints.membership import MembershipEndpoint -from .endpoints.skills import SkillsEndpoint from .endpoints.skill_settings import SkillSettingsEndpoint +from .endpoints.skills import SkillsEndpoint from .endpoints.voice_endpoint import VoiceEndpoint from .endpoints.wake_word_endpoint import WakeWordEndpoint @@ -26,7 +26,7 @@ acct.register_blueprint(selene_api) acct.add_url_rule( '/api/account', view_func=AccountEndpoint.as_view('account_api'), - methods=['GET', 'POST'] + methods=['GET', 'POST', 'PATCH'] ) acct.add_url_rule( '/api/agreement/', diff --git a/api/account/tests/features/update_membership.feature b/api/account/tests/features/update_membership.feature new file mode 100644 index 00000000..1d1704dd --- /dev/null +++ b/api/account/tests/features/update_membership.feature @@ -0,0 +1,5 @@ +Feature: Test the API call to update a membership + + Scenario: user with free account opts into a membership + Given a user with a free account + When a monthly membership is added \ No newline at end of file diff --git a/api/public/tests/features/steps/get_device_subscription.py b/api/public/tests/features/steps/get_device_subscription.py index 9249b7f2..4704346f 100644 --- a/api/public/tests/features/steps/get_device_subscription.py +++ b/api/public/tests/features/steps/get_device_subscription.py @@ -43,7 +43,7 @@ def get_device_subscription(context): access_token = login['accessToken'] headers=dict(Authorization='Bearer {token}'.format(token=access_token)) with get_db_connection(context.client_config['DB_CONNECTION_POOL']) as db: - AccountRepository(db)._add_membership(context.account.id, membership) + AccountRepository(db).add_membership(context.account.id, membership) context.subscription_response = context.client.get( '/v1/device/{uuid}/subscription'.format(uuid=device_id), headers=headers diff --git a/shared/selene/api/endpoints/account.py b/shared/selene/api/endpoints/account.py index 0b17d6ee..f1165ac6 100644 --- a/shared/selene/api/endpoints/account.py +++ b/shared/selene/api/endpoints/account.py @@ -63,13 +63,21 @@ class Support(Model): payment_method = StringType(choices=[STRIPE_PAYMENT]) payment_token = StringType() - def validate_payment_account_id(self, data, value): - if data['membership'] != NO_MEMBERSHIP: - if not data['payment_account_id']: - raise ValidationError( - 'Membership requires a payment account ID' - ) - return value + +class AddMembership(Model): + membership = StringType( + required=True, + choices=(MONTHLY_MEMBERSHIP, YEARLY_MEMBERSHIP, NO_MEMBERSHIP) + ) + payment_method = StringType(required=True, choices=[STRIPE_PAYMENT]) + payment_token = StringType(required=True) + + +class UpdateMembership(Model): + membership = StringType( + required=True, + choices=(MONTHLY_MEMBERSHIP, YEARLY_MEMBERSHIP, NO_MEMBERSHIP) + ) class AddAccountRequest(Model): @@ -143,6 +151,11 @@ class AccountEndpoint(SeleneEndpoint): return jsonify('Account added successfully'), HTTPStatus.OK + def patch(self): + self._authenticate() + self.request_data = json.loads(self.request.data) + self._update_support() + def _validate_request(self): add_request = AddAccountRequest(dict( username=self.request_data.get('username'), @@ -219,11 +232,58 @@ class AccountEndpoint(SeleneEndpoint): password=password ) - def _create_stripe_subscription(self, token, user_email, plan): - customer = stripe.Customer.create(source=token, email=user_email) - subscription = stripe.Subscription.create(customer=customer.id, items=[{'plan': plan}]) - return customer.id, subscription.current_period_start + def _create_stripe_subscription(self, customer_id, token, user_email, plan): + if customer_id is None: + customer = stripe.Customer.create(source=token, email=user_email) + customer_id = customer.id + subscription = stripe.Subscription.create(customer=customer_id, items=[{'plan': plan}]) + + # TODO: store subscription.id + start = subscription.current_period_start + start = date.fromtimestamp(start) + return customer_id, start def _get_plan(self, plan): with get_db_connection(self.config['DB_CONNECTION_POOL']) as db: return MembershipRepository(db).get_membership_by_type(plan) + + def _update_support(self): + with get_db_connection(self.config['DB_CONNECTION_POOL']) as db: + membership_repository = MembershipRepository(db) + active_membership = membership_repository.get_active_membership_by_account_id(self.account.id) + if active_membership: + active_membership.end_date = datetime.now() + # TODO: use the subscription id to delete the membership on stripe + membership_repository.finish_membership(active_membership) + add_membership = UpdateMembership(self.request_data.get('support')) + add_membership.validate() + support = self.request_data['support'] + membership = support['membership'] + stripe_plan = self._get_plan(membership) + stripe_id, start_date = self._create_stripe_subscription( + active_membership.payment_account_id, + None, + self.account.email_address, + stripe_plan + ) + else: + add_membership = AddMembership(self.request_data.get('support')) + add_membership.validate() + support = self.request_data['support'] + membership = support['membership'] + token = support['payment'] + stripe_plan = self._get_plan(membership) + stripe_id, start_date = self._create_stripe_subscription( + None, + token, + self.account.email_address, + stripe_plan + ) + + new_membership = AccountMembership( + start_date=start_date, + payment_method=STRIPE_PAYMENT, + payment_account_id=stripe_id, + type=membership + ) + AccountRepository(db).add_membership(self.account.id, new_membership) diff --git a/shared/selene/data/account/entity/account.py b/shared/selene/data/account/entity/account.py index 3017e576..6f0cd515 100644 --- a/shared/selene/data/account/entity/account.py +++ b/shared/selene/data/account/entity/account.py @@ -1,5 +1,5 @@ -from datetime import date from dataclasses import dataclass +from datetime import date from typing import List @@ -19,6 +19,8 @@ class AccountMembership(object): payment_method: str payment_account_id: str id: str = None + account_id: str = None + end_date: date = None @dataclass diff --git a/shared/selene/data/account/repository/account.py b/shared/selene/data/account/repository/account.py index db0e4adb..cbf9753b 100644 --- a/shared/selene/data/account/repository/account.py +++ b/shared/selene/data/account/repository/account.py @@ -35,7 +35,7 @@ class AccountRepository(object): account_id = self._add_account(account, password) self._add_agreements(account_id, account.agreements) if account.membership is not None: - self._add_membership(account_id, account.membership) + self.add_membership(account_id, account.membership) _log.info('Added account {}'.format(account.email_address)) @@ -73,7 +73,7 @@ class AccountRepository(object): ) self.cursor.insert(request) - def _add_membership(self, acct_id: str, membership: AccountMembership): + def add_membership(self, acct_id: str, membership: AccountMembership): """A membership is optional, add it if one was selected""" request = DatabaseRequest( sql=get_sql_from_file( diff --git a/shared/selene/data/account/repository/membership.py b/shared/selene/data/account/repository/membership.py index 92b23d5e..27f32e69 100644 --- a/shared/selene/data/account/repository/membership.py +++ b/shared/selene/data/account/repository/membership.py @@ -1,3 +1,4 @@ +from selene.data.account import AccountMembership from ..entity.membership import Membership from ...repository_base import RepositoryBase @@ -22,6 +23,15 @@ class MembershipRepository(RepositoryBase): db_result = self.cursor.select_one(db_request) return Membership(**db_result) + def get_active_membership_by_account_id(self, account_id) -> AccountMembership: + db_request = self._build_db_request( + sql_file_name='get_active_membership_by_account_id.sql', + args=dict(account_id=account_id) + ) + db_result = self.cursor.select_one(db_request) + if db_result: + return AccountMembership(**db_result) + def add(self, membership: Membership): db_request = self._build_db_request( 'add_membership.sql', @@ -37,7 +47,20 @@ class MembershipRepository(RepositoryBase): def remove(self, membership: Membership): db_request = self._build_db_request( - 'delete_membership.sql', + sql_file_name='delete_membership.sql', args=dict(membership_id=membership.id) ) self.cursor.delete(db_request) + + def finish_membership(self, membership: AccountMembership): + db_request = self._build_db_request( + sql_file_name='finish_membership.sql', + args=dict( + id=membership.id, + membership_ts_range='[{start},{end}]'.format( + start=membership.start_date, + end=membership.end_date + ) + ) + ) + self.cursor.update(db_request) diff --git a/shared/selene/data/account/repository/sql/finish_membership.sql b/shared/selene/data/account/repository/sql/finish_membership.sql new file mode 100644 index 00000000..9f374ffa --- /dev/null +++ b/shared/selene/data/account/repository/sql/finish_membership.sql @@ -0,0 +1,6 @@ +UPDATE + account.account_membership +SET + membership_ts_range = %(membership_ts_range)s +WHERE + id = %(id)s \ No newline at end of file diff --git a/shared/selene/data/account/repository/sql/get_active_membership_by_account_id.sql b/shared/selene/data/account/repository/sql/get_active_membership_by_account_id.sql new file mode 100644 index 00000000..44950199 --- /dev/null +++ b/shared/selene/data/account/repository/sql/get_active_membership_by_account_id.sql @@ -0,0 +1,6 @@ +SELECT + * +FROM + account.account_membership +WHERE + account_id = %(account_id)s and membership_ts_range @> '[now,)' \ No newline at end of file