diff --git a/.gitignore b/.gitignore index 4efb326..057bfa0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .DS_Store /data/ con_specific +.env .idea .vscode artshow-utils/*.csv diff --git a/Pipfile b/Pipfile index ab430b6..ea92498 100644 --- a/Pipfile +++ b/Pipfile @@ -13,7 +13,6 @@ django-formtools = "*" django-ses = "*" gunicorn = "*" "num2words" = "*" -squareconnect = "*" pdfrw = "*" reportlab = "*" requests = "*" @@ -22,6 +21,7 @@ psycopg = {extras = ["binary"],version = "*"} supervisor = "*" environs = {extras = ["django"],version = "*"} celery = {extras = ["sqs"],version = "*"} +squareup = "*" [dev-packages] "django-debug-toolbar" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 2a0fbc8..a785671 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f25bc8d724268a1bb3d2e69acff63a392520fc3a0674a3a5d0e3f8285edbfbc5" + "sha256": "3b3e685cd0f172c1ee36a8ea75bcff17310cc8e4a9954048c1f35b3cb6d9dd13" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,27 @@ "markers": "python_version >= '3.6'", "version": "==5.1.1" }, + "apimatic-core": { + "hashes": [ + "sha256:2b3305e47a0efb6edd12e9173238c1cfd74b757c9091fcec2f7af436d896da84", + "sha256:53b2e69d9d2551338ae4204143ed79d6a6399956b167eec23091bdf00c5c48a9" + ], + "version": "==0.2.4" + }, + "apimatic-core-interfaces": { + "hashes": [ + "sha256:10969dc0d93ab83c8421854a621d0732a31d26e6471bcfd68bb2b61285ce4e15", + "sha256:c5159f552e776a1bda8e5a9e3221a3296c52b543d36857376d3523c1af9e6fac" + ], + "version": "==0.1.4" + }, + "apimatic-requests-client-adapter": { + "hashes": [ + "sha256:6b37cbe700178524191cbe7644aa604e60f23f4513f61ccf9022eca6ecc7bf8d", + "sha256:dc1f6a859a93e844751c7d91683cadda45860c7c24b062cccb461f08a03c10ad" + ], + "version": "==0.1.6" + }, "asgiref": { "hashes": [ "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", @@ -42,19 +63,26 @@ }, "boto3": { "hashes": [ - "sha256:0dfa2fc96ccafce4feb23044d6cba8b25075ad428a0c450d369d099c6a1059d2", - "sha256:148eeba0f1867b3db5b3e5ae2997d75a94d03fad46171374a0819168c36f7ed0" + "sha256:65d052ec13197460586ee385aa2d6bba0e7378d2d2c7f3e93c044c43ae1ca782", + "sha256:94218aba2feb5b404b665b8d76c172dc654f79b4c5fa0e9e92459c098da87bf4" ], - "markers": "python_version >= '3.7'", - "version": "==1.28.62" + "version": "==1.28.63" }, "botocore": { "hashes": [ - "sha256:272b78ac65256b6294cb9cdb0ac484d447ad3a85642e33cb6a3b1b8afee15a4c", - "sha256:be792d806afc064694a2d0b9b25779f3ca0c1584b29a35ac32e67f0064ddb8b7" + "sha256:6e582c811ea74f25bdb490ac372b2645de4a60286b42ddd8c69f3b6df82b6b12", + "sha256:cb9db5db5af865b1fc2e1405b967db5d78dd0f4d84e5dc1974e082733c1034b7" ], "markers": "python_version >= '3.7'", - "version": "==1.31.62" + "version": "==1.31.63" + }, + "cachecontrol": { + "hashes": [ + "sha256:1c2939be362a70c4e5f02c6249462b3b7a24441e4f1ced5e9ef028172edf356a", + "sha256:d1087f45781c0e00616479bfd282c78504371ca71da017b49df9f5365a95feba" + ], + "markers": "python_version >= '3.6'", + "version": "==0.12.14" }, "celery": { "extras": [ @@ -202,6 +230,13 @@ "markers": "python_version >= '3.6'", "version": "==0.3.0" }, + "deprecation": { + "hashes": [ + "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", + "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a" + ], + "version": "==2.1.0" + }, "dj-database-url": { "hashes": [ "sha256:04bc34b248d4c21aaa13e4ab419ae6575ef5f10f3df735ce7da97722caa356e0", @@ -288,6 +323,14 @@ ], "version": "==0.6.2" }, + "enum34": { + "hashes": [ + "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53", + "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328", + "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248" + ], + "version": "==1.1.10" + }, "environs": { "extras": [ "django" @@ -324,6 +367,22 @@ "markers": "python_version >= '3.7'", "version": "==1.0.1" }, + "jsonpickle": { + "hashes": [ + "sha256:4a8442d97ca3f77978afa58068768dba7bff2dbabe79a9647bc3cdafd4ef019f", + "sha256:e37abba4bfb3ca4a4647d28bb9f4706436f7b46c8a8333b4a718abafa8e46b37" + ], + "markers": "python_version >= '3.7'", + "version": "==3.0.2" + }, + "jsonpointer": { + "hashes": [ + "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a", + "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==2.4" + }, "kombu": { "hashes": [ "sha256:0ba213f630a2cb2772728aef56ac6883dc3a2f13435e10048f6e97d48506dbbd", @@ -340,6 +399,68 @@ "markers": "python_version >= '3.8'", "version": "==3.20.1" }, + "msgpack": { + "hashes": [ + "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862", + "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d", + "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3", + "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672", + "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0", + "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9", + "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee", + "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46", + "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524", + "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819", + "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc", + "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc", + "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1", + "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82", + "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81", + "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6", + "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d", + "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2", + "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c", + "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87", + "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84", + "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e", + "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95", + "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f", + "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b", + "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93", + "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf", + "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61", + "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c", + "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8", + "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d", + "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c", + "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4", + "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba", + "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415", + "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee", + "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d", + "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9", + "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075", + "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f", + "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7", + "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681", + "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329", + "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1", + "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf", + "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c", + "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5", + "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b", + "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5", + "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e", + "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b", + "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad", + "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd", + "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7", + "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002", + "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.7" + }, "num2words": { "hashes": [ "sha256:7e7c0b0f080405aa3a1dd9d32b1ca90b3bf03bab17b8e54db05e1b78301a0988", @@ -374,63 +495,63 @@ }, "pillow": { "hashes": [ - "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff", - "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f", - "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21", - "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635", - "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a", - "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f", - "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1", - "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d", - "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db", - "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849", - "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7", - "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876", - "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3", - "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317", - "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91", - "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d", - "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b", - "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd", - "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed", - "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500", - "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7", - "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a", - "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a", - "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0", - "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf", - "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f", - "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1", - "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088", - "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971", - "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a", - "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205", - "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54", - "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08", - "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21", - "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d", - "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08", - "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e", - "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf", - "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b", - "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145", - "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2", - "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d", - "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d", - "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf", - "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad", - "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d", - "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1", - "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4", - "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2", - "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19", - "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37", - "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4", - "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68", - "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1" + "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d", + "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de", + "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616", + "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839", + "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099", + "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a", + "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219", + "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106", + "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b", + "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412", + "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b", + "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7", + "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2", + "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7", + "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14", + "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f", + "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27", + "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57", + "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262", + "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28", + "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610", + "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172", + "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273", + "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e", + "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d", + "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818", + "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f", + "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9", + "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01", + "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7", + "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651", + "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312", + "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80", + "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666", + "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061", + "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b", + "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992", + "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593", + "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4", + "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db", + "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba", + "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd", + "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e", + "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212", + "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb", + "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2", + "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34", + "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256", + "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f", + "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2", + "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38", + "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996", + "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a", + "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793" ], "markers": "python_version >= '3.8'", - "version": "==10.0.1" + "version": "==10.1.0" }, "prompt-toolkit": { "hashes": [ @@ -552,12 +673,12 @@ }, "reportlab": { "hashes": [ - "sha256:1344dbe779b9049a1888105503837d0e5b62163bf5c6b33bd1fbe84bad484f50", - "sha256:9c68f277736f585c5c9938755b826dd57c877fcaeb203e21cefea12b3b1db4f5" + "sha256:069aa35da7c882921f419f6e26327e14dac1d9d0adeb40b584cdadd974d99fc0", + "sha256:ec062675202eb76f6100ed44da64f38ed3c7feb5016cf4fe7f17ce35423ab14a" ], "index": "pypi", "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==4.0.5" + "version": "==4.0.6" }, "requests": { "hashes": [ @@ -587,11 +708,11 @@ }, "setuptools": { "hashes": [ - "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87", - "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a" + "sha256:a78d01d1e2c175c474884671dde039962c9d74c7223db7369771fcf6e29ceeab", + "sha256:bd6eb2d6722568de6d14b87c44a96fac54b2a45ff5e940e639979a3d1792adb6" ], - "markers": "python_version >= '3.8'", - "version": "==68.2.2" + "markers": "python_version >= '3.7'", + "version": "==66.0.0" }, "six": { "hashes": [ @@ -609,12 +730,13 @@ "markers": "python_version >= '3.5'", "version": "==0.4.4" }, - "squareconnect": { + "squareup": { "hashes": [ - "sha256:28779d7dab1b200a105dabc9f467d2447e5372a30c8aef5d2e9e959a6197c6a6" + "sha256:69af34d2eb64f8dcb5720980404ee2c646dde9c642ee94df4d9b4b6297907c71" ], "index": "pypi", - "version": "==2.20190724.1" + "markers": "python_version >= '3.7'", + "version": "==31.0.0.20230925" }, "supervisor": { "hashes": [ @@ -718,11 +840,11 @@ }, "pycodestyle": { "hashes": [ - "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0", - "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8" + "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f", + "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67" ], "markers": "python_version >= '3.8'", - "version": "==2.11.0" + "version": "==2.11.1" }, "pyflakes": { "hashes": [ diff --git a/artshow/admin.py b/artshow/admin.py index ba01a9e..99d9500 100644 --- a/artshow/admin.py +++ b/artshow/admin.py @@ -19,12 +19,15 @@ from django.urls import reverse from django.utils.html import format_html +import json + from . import email1 from . import processbatchscan from .models import ( Agent, Allocation, Artist, BatchScan, Bid, Bidder, BidderId, Checkoff, ChequePayment, EmailSignature, EmailTemplate, Invoice, InvoiceItem, - InvoicePayment, Payment, PaymentType, Piece, Location, Space + InvoicePayment, Payment, PaymentType, Piece, Location, Space, SquarePayment, + SquareWebhook ) User = get_user_model() @@ -591,3 +594,50 @@ def print_cheques(self, request, cheqs): admin.site.register(ChequePayment, ChequePaymentAdmin) + + +@admin.register(SquarePayment) +class SquarePaymentAdmin(admin.ModelAdmin): + @admin.display(description='Artist') + def clickable_artist(self, obj): + return format_html('{}', + reverse('admin:artshow_artist_change', + args=(obj.artist.pk,)), + str(obj.artist)) + + list_display = ('id', 'clickable_artist', 'amount', 'payment_type', 'date') + list_filter = ('payment_type',) + raw_id_fields = ('artist',) + readonly_fields = ('payment_link_id', 'payment_link_url', 'order_id') + + +@admin.register(SquareWebhook) +class SquareWebhookAdmin(admin.ModelAdmin): + list_display = ('webhook_event_id', 'timestamp', 'webhook_type', 'webhook_data_id') + fields = ('timestamp', 'pretty_json') + readonly_fields = ('timestamp', 'pretty_json') + + @admin.display(description='ID') + def webhook_event_id(self, webhook): + if 'event_id' in webhook.body: + return webhook.body['event_id'] + return '(unknown)' + + @admin.display(description='Type') + def webhook_type(self, webhook): + if 'type' in webhook.body: + return webhook.body['type'] + return '(unknown)' + + @admin.display(description='Object ID') + def webhook_data_id(self, webhook): + if 'data' in webhook.body and 'id' in webhook.body['data']: + return webhook.body['data']['id'] + return '(unknown)' + + @admin.display(description='Body') + def pretty_json(self, webhook): + return format_html( + '
{}
', + json.dumps(webhook.body, sort_keys=True, indent=2), + ) diff --git a/artshow/manage.py b/artshow/manage.py index 06116f0..386567e 100644 --- a/artshow/manage.py +++ b/artshow/manage.py @@ -5,10 +5,9 @@ from django.forms import HiddenInput, ModelChoiceField from django.forms.formsets import formset_factory from django.shortcuts import render, redirect, get_object_or_404 -from django.utils.timezone import now from django.urls import reverse from .models import ( - Allocation, Artist, Location, Payment, Piece, Space, validate_space, + Allocation, Artist, Location, Piece, Space, SquarePayment, validate_space, validate_space_increments ) from django import forms @@ -339,57 +338,45 @@ def person_details(request, artist_id): "artshow_settings": artshow_settings}) -class PaymentForm(forms.Form): - amount = forms.DecimalField(required=True, max_digits=7, decimal_places=2) - nonce = forms.CharField(required=False) - - def clean_amount(self): - amount = self.cleaned_data["amount"] - if amount <= 0: - raise forms.ValidationError("Amount must be above zero") - return amount - - @login_required -@user_edits_allowable def make_payment(request, artist_id): artist = get_object_or_404(Artist.objects.viewable_by(request.user), pk=artist_id) total_requested_cost, deduction_to_date, deduction_remaining, payment_remaining = \ artist.payment_remaining_with_details() payment_remaining = Decimal(payment_remaining).quantize(Decimal('1.00')) - if request.method == "POST": - form = PaymentForm(request.POST) - if form.is_valid(): - payment = Payment(artist=artist, - amount=form.cleaned_data["amount"], - payment_type_id=settings.ARTSHOW_PAYMENT_PENDING_PK, - description="", - date=now()) - - transaction = square.charge(payment, form.cleaned_data["nonce"]) - if transaction: - payment.payment_type_id = settings.ARTSHOW_PAYMENT_RECEIVED_PK - payment.description = "Square " + transaction - payment.save() - return redirect(reverse("artshow-manage-payment-square", - args=(artist_id,))) - else: - form.add_error(None, "Failed to charge payment.") - else: - form = PaymentForm(initial={'amount': payment_remaining}) - - context = {"form": form, - "artist": artist, - "allocations": artist.allocation_set.order_by("id"), - "total_requested_cost": total_requested_cost, - "deduction_to_date": deduction_to_date, - "deduction_remaining": deduction_remaining, - "account_balance": artist.balance(), - "payment_remaining": payment_remaining, - "sq_application_id": settings.ARTSHOW_SQUARE_APPLICATION_ID, - "sq_location_id": settings.ARTSHOW_SQUARE_LOCATION_ID, - } + if request.method == "POST" and payment_remaining > 0: + payment_url = square.create_payment_url( + artist, + f'Art Show space reservation for {artist}', + payment_remaining, + request.build_absolute_uri(reverse('artshow-manage-payment-square', + args=(artist_id,))), + ) + if payment_url is not None: + return redirect(payment_url) + else: + return redirect(reverse('artshow-manage-payment-square-error', + args=(artist_id,))) + + pending_square_payment = SquarePayment.objects.filter( + artist=artist, + payment_type_id=settings.ARTSHOW_PAYMENT_PENDING_PK, + ).first() + pending_payment_url = None + if pending_square_payment is not None: + pending_payment_url = pending_square_payment.payment_link_url + + context = { + "artist": artist, + "allocations": artist.allocation_set.order_by("id"), + "total_requested_cost": total_requested_cost, + "deduction_to_date": deduction_to_date, + "deduction_remaining": deduction_remaining, + "account_balance": artist.balance(), + "payment_remaining": payment_remaining, + "payment_url": pending_payment_url, + } return render(request, "artshow/make_payment.html", context) @@ -404,3 +391,9 @@ def payment_made_mail(request, artist_id): def payment_made_square(request, artist_id): artist = get_object_or_404(Artist.objects.viewable_by(request.user), pk=artist_id) return render(request, "artshow/payment_made_square.html", {"artist": artist}) + + +@login_required +def payment_error_square(request, artist_id): + artist = get_object_or_404(Artist.objects.viewable_by(request.user), pk=artist_id) + return render(request, "artshow/payment_error_square.html", {"artist": artist}) diff --git a/artshow/manage_urls.py b/artshow/manage_urls.py index ba7585a..c204cc8 100644 --- a/artshow/manage_urls.py +++ b/artshow/manage_urls.py @@ -32,6 +32,8 @@ manage.payment_made_mail, name='artshow-manage-payment-mail'), re_path(r'^artist/(?P\d+)/makepayment/complete/square/$', manage.payment_made_square, name='artshow-manage-payment-square'), + re_path(r'^artist/(?P\d+)/makepayment/error/square/$', + manage.payment_error_square, name='artshow-manage-payment-square-error'), re_path(r'^register/$', register.main, name='artshow-register'), re_path(r'^announcement/$', announcement.index, name="view_announcements"), re_path(r'^announcement/(?P\d+)/$', announcement.show, diff --git a/artshow/migrations/0010_alter_invoicepayment_payment_method.py b/artshow/migrations/0010_alter_invoicepayment_payment_method.py new file mode 100644 index 0000000..4bfb1e0 --- /dev/null +++ b/artshow/migrations/0010_alter_invoicepayment_payment_method.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.6 on 2023-10-08 19:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('artshow', '0009_location'), + ] + + operations = [ + migrations.AlterField( + model_name='invoicepayment', + name='payment_method', + field=models.IntegerField(choices=[(0, 'Not Paid'), (1, 'Cash'), (3, 'Card')], default=0), + ), + ] diff --git a/artshow/migrations/0011_squarepayment.py b/artshow/migrations/0011_squarepayment.py new file mode 100644 index 0000000..bb5a80e --- /dev/null +++ b/artshow/migrations/0011_squarepayment.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.6 on 2023-10-08 19:30 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('artshow', '0010_alter_invoicepayment_payment_method'), + ] + + operations = [ + migrations.CreateModel( + name='SquarePayment', + fields=[ + ('payment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='artshow.payment')), + ('payment_link_id', models.CharField(max_length=192)), + ('payment_link_url', models.CharField(max_length=255)), + ('order_id', models.CharField(max_length=192)), + ('payment_id', models.CharField(blank=True, max_length=192)), + ], + bases=('artshow.payment',), + ), + ] diff --git a/artshow/migrations/0012_squarewebhook.py b/artshow/migrations/0012_squarewebhook.py new file mode 100644 index 0000000..c0e7f02 --- /dev/null +++ b/artshow/migrations/0012_squarewebhook.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.6 on 2023-10-11 04:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('artshow', '0011_squarepayment'), + ] + + operations = [ + migrations.CreateModel( + name='SquareWebhook', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField()), + ('body', models.JSONField()), + ], + ), + ] diff --git a/artshow/models.py b/artshow/models.py index 235efc3..f5622ec 100644 --- a/artshow/models.py +++ b/artshow/models.py @@ -653,6 +653,13 @@ def amount_words(self): return '%s dollars and %s cents' % (num2words(dollars), num2words(cents)) +class SquarePayment (Payment): + payment_link_id = models.CharField(max_length=192) + payment_link_url = models.CharField(max_length=255) + order_id = models.CharField(max_length=192) + payment_id = models.CharField(max_length=192, blank=True) + + class Invoice (models.Model): payer = models.ForeignKey(Bidder, on_delete=models.CASCADE) tax_paid = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True) @@ -759,3 +766,8 @@ class Agent(models.Model): help_text="Person is allowed to retrieve pieces from the show") can_arbitrate = models.BooleanField(default=False, help_text="Person is allowed to make executive decisions regarding pieces") + + +class SquareWebhook(models.Model): + timestamp = models.DateTimeField() + body = models.JSONField() diff --git a/artshow/square.py b/artshow/square.py index b4419a0..f9bb0df 100644 --- a/artshow/square.py +++ b/artshow/square.py @@ -1,34 +1,114 @@ +import json import logging import uuid -from squareconnect.rest import ApiException -from squareconnect.apis.transactions_api import TransactionsApi +from decimal import Decimal + +from django.http import HttpResponse +from django.utils.timezone import now +from django.views.decorators.csrf import csrf_exempt + +from square.client import Client +from square.utilities.webhooks_helper import is_valid_webhook_event_signature from .conf import settings +from .models import SquarePayment, SquareWebhook logger = logging.getLogger(__name__) -def charge(payment, nonce): - api_instance = TransactionsApi() - api_instance.api_client.configuration.access_token = \ - settings.ARTSHOW_SQUARE_ACCESS_TOKEN - idempotency_key = str(uuid.uuid1()) - body = { - 'idempotency_key': idempotency_key, - 'card_nonce': nonce, - 'amount_money': { - 'amount': int(payment.amount * 100), - 'currency': 'USD' +def create_payment_url(artist, name, amount, redirect_url): + client = Client( + access_token=settings.ARTSHOW_SQUARE_ACCESS_TOKEN, + environment=settings.ARTSHOW_SQUARE_ENVIRONMENT) + + result = client.checkout.create_payment_link({ + 'idempotency_key': str(uuid.uuid4()), + 'quick_pay': { + 'name': name, + 'price_money': { + 'amount': int(amount * 100), + 'currency': 'USD', + }, + 'location_id': settings.ARTSHOW_SQUARE_LOCATION_ID, }, - 'buyer_email_address': payment.artist.person.email, - 'note': 'Art Show space reservation for Artist #%d' % payment.artist.artistid, - } - try: - location_id = settings.ARTSHOW_SQUARE_LOCATION_ID - api_response = api_instance.charge(location_id, body) - logger.debug('Square charge successful: %s' % api_response.transaction) - return api_response.transaction.id - except ApiException: - logger.exception('Exception when charging through Square') + 'checkout_options': { + 'redirect_url': redirect_url, + }, + }) + + if result.is_success(): + payment_link = result.body['payment_link'] + payment = SquarePayment( + artist=artist, + amount=amount, + payment_type_id=settings.ARTSHOW_PAYMENT_PENDING_PK, + description='Square payment', + date=now(), + payment_link_id=payment_link['id'], + payment_link_url=payment_link['long_url'], + order_id=payment_link['order_id'], + ) + payment.save() + return payment.payment_link_url + + elif result.is_error(): + for error in result.errors: + logger.error(f"Square error {error['category']}:{error['code']}: {error['detail']}") return None + + +def process_payment_created_or_updated(body): + payment = body['data']['object']['payment'] + + if payment['status'] != 'COMPLETED': + return + + currency = payment['total_money']['currency'] + if currency != 'USD': + raise Exception(f'Unexpected currency: {currency}') + + order_id = payment['order_id'] + payment_id = payment['id'] + payment_amount = Decimal(payment['total_money']['amount'] / 100) + + square_payment = SquarePayment.objects.get(order_id=order_id) + square_payment.amount = payment_amount + square_payment.payment_type_id = settings.ARTSHOW_PAYMENT_RECEIVED_PK + square_payment.payment_id = payment_id + square_payment.save() + + +def process_webhook(body): + if body['type'] in ('payment.created', 'payment.updated'): + process_payment_created_or_updated(body) + + +@csrf_exempt +def webhook(request): + body = request.body.decode('utf-8') + valid = is_valid_webhook_event_signature( + body, + request.headers['x-square-hmacsha256-signature'], + settings.ARTSHOW_SQUARE_SIGNATURE_KEY, + settings.ARTSHOW_SQUARE_NOTIFICATION_URL) + + if not valid: + logger.debug('Received invalid webhook!') + return HttpResponse(status=403) + + try: + body = json.loads(body) + except json.JSONDecodeError: + logger.exception('Received webhook with invalid JSON!') + return HttpResponse(status=400) + + webhook = SquareWebhook(timestamp=now(), body=body) + webhook.save() + + try: + process_webhook(body) + except Exception: + logger.exception('Failed to process webhook!') + + return HttpResponse(status=200) diff --git a/artshow/templates/artshow/make_payment.html b/artshow/templates/artshow/make_payment.html index c6387cc..932dd3a 100644 --- a/artshow/templates/artshow/make_payment.html +++ b/artshow/templates/artshow/make_payment.html @@ -1,14 +1,5 @@ {% extends "artshow/manage_base.html" %} {% load static %} -{% block extra_head %} - - - - -{% endblock %} {% block breadcrumbs %}