From 01ad0e8656470eb873ce19e3574c52a19bf88e1a Mon Sep 17 00:00:00 2001 From: Samuel Clay Date: Thu, 10 Feb 2022 13:40:07 -0500 Subject: [PATCH] Paypal refunds are now working. Need to add partial balances when upgrading tiers on paypal. Eventually need to think about refunds for switching providers. --- Makefile | 14 ++- ansible/playbooks/deploy_www.yml | 2 +- .../roles/mongo/templates/consul_service.json | 2 +- apps/profile/models.py | 119 +++++++++++------- apps/profile/views.py | 5 +- media/css/reader/reader.css | 1 + 6 files changed, 94 insertions(+), 49 deletions(-) diff --git a/Makefile b/Makefile index 1c24866a2..da7680aff 100644 --- a/Makefile +++ b/Makefile @@ -84,10 +84,20 @@ keys: - openssl dhparam -out config/certificates/dhparam-2048.pem 2048 - openssl req -x509 -nodes -new -sha256 -days 1024 -newkey rsa:2048 -keyout config/certificates/RootCA.key -out config/certificates/RootCA.pem -subj "/C=US/CN=Example-Root-CA" - openssl x509 -outform pem -in config/certificates/RootCA.pem -out config/certificates/RootCA.crt - - openssl req -new -nodes -newkey rsa:2048 -keyout config/certificates/localhost.key -out config/certificates/localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Example-Certificates/CN=localhost.local" + - openssl req -new -nodes -newkey rsa:2048 -keyout config/certificates/localhost.key -out config/certificates/localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Example-Certificates/CN=localhost" - openssl x509 -req -sha256 -days 1024 -in config/certificates/localhost.csr -CA config/certificates/RootCA.pem -CAkey config/certificates/RootCA.key -CAcreateserial -out config/certificates/localhost.crt - cat config/certificates/localhost.crt config/certificates/localhost.key > config/certificates/localhost.pem - - /usr/bin/security add-trusted-cert -d -r trustAsRoot -k /Library/Keychains/System.keychain ./config/certificates/RootCA.crt + - sudo /usr/bin/security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ./config/certificates/RootCA.crt + +# Doesn't work yet +mkcert: + - mkdir config/mkcert + - docker run -v $(shell pwd)/config/mkcert:/root/.local/share/mkcert brunopadz/mkcert-docker:latest \ + /bin/sh -c "mkcert -install && \ + mkcert -cert-file /root/.local/share/mkcert/mkcert.pem \ + -key-file /root/.local/share/mkcert/mkcert.key localhost" + - cat config/mkcert/rootCA.pem config/mkcert/rootCA-key.pem > config/certificates/localhost.pem + - sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ./config/mkcert/rootCA.pem # Digital Ocean / Terraform list: diff --git a/ansible/playbooks/deploy_www.yml b/ansible/playbooks/deploy_www.yml index c77d175c1..86db1d586 100644 --- a/ansible/playbooks/deploy_www.yml +++ b/ansible/playbooks/deploy_www.yml @@ -1,6 +1,6 @@ --- - name: DEPLOY -> www - hosts: www,staging + hosts: haproxy,staging gather_facts: false vars_files: - ../env_vars/base.yml diff --git a/ansible/roles/mongo/templates/consul_service.json b/ansible/roles/mongo/templates/consul_service.json index 8ca05190b..192d901a0 100644 --- a/ansible/roles/mongo/templates/consul_service.json +++ b/ansible/roles/mongo/templates/consul_service.json @@ -1,6 +1,6 @@ { "service": { - "name": "db-mongo-staging", + "name": "db-mongo", "id": "{{ inventory_hostname }}", "tags": [ "db" diff --git a/apps/profile/models.py b/apps/profile/models.py index 3e9c4a0cd..39d6077d3 100644 --- a/apps/profile/models.py +++ b/apps/profile/models.py @@ -600,52 +600,85 @@ class Profile(models.Model): return ','.join(failed) - def refund_premium(self, partial=False): + def refund_premium(self, partial=False, provider=None): refunded = False - - if self.stripe_id: - stripe.api_key = settings.STRIPE_SECRET - stripe_customer = stripe.Customer.retrieve(self.stripe_id) - stripe_payments = stripe.Charge.list(customer=stripe_customer.id).data - if partial: - stripe_payments[0].refund(amount=1200) - refunded = 12 - else: - stripe_payments[0].refund() - self.cancel_premium() - refunded = stripe_payments[0].amount/100 - logging.user(self.user, "~FRRefunding stripe payment: $%s" % refunded) + if provider == "paypal": + refunded = self.refund_latest_paypal_payment(partial=partial) + elif provider == "stripe": + refunded = self.refund_latest_stripe_payment(partial=partial) else: - self.cancel_premium() - - paypal_opts = { - 'API_ENVIRONMENT': 'SANDBOX', - 'API_USERNAME': settings.PAYPAL_API_USERNAME, - 'API_PASSWORD': settings.PAYPAL_API_PASSWORD, - 'API_SIGNATURE': settings.PAYPAL_API_SIGNATURE, - 'API_CA_CERTS': False, - } - paypal = PayPalInterface(**paypal_opts) - transactions = PayPalIPN.objects.filter(custom=self.user.username, - txn_type='subscr_payment' - ).order_by('-payment_date') - if not transactions: - transactions = PayPalIPN.objects.filter(payer_email=self.user.email, - txn_type='subscr_payment' - ).order_by('-payment_date') - if transactions: - transaction = transactions[0] - refund = paypal.refund_transaction(transaction.txn_id) - try: - refunded = int(float(refund.raw['TOTALREFUNDEDAMOUNT'][0])) - except KeyError: - refunded = int(transaction.payment_gross) - logging.user(self.user, "~FRRefunding paypal payment: $%s" % refunded) - else: - logging.user(self.user, "~FRCouldn't refund paypal payment: not found by username or email") - refunded = 0 + # Find last payment, refund that + payment_history = PaymentHistory.objects.filter(user=self.user, + payment_provider__in=['paypal', 'stripe']) + if payment_history.count(): + provider = payment_history[0].payment_provider + if provider == "stripe": + refunded = self.refund_latest_stripe_payment(partial=partial) + elif provider == "paypal": + refunded = self.refund_latest_paypal_payment(partial=partial) + return refunded + + def refund_latest_stripe_payment(self, partial=False): + refunded = False + if not self.stripe_id: + return + stripe.api_key = settings.STRIPE_SECRET + stripe_customer = stripe.Customer.retrieve(self.stripe_id) + stripe_payments = stripe.Charge.list(customer=stripe_customer.id).data + if partial: + stripe_payments[0].refund(amount=1200) + refunded = 12 + else: + stripe_payments[0].refund() + self.cancel_premium_stripe() + refunded = stripe_payments[0].amount/100 + + logging.user(self.user, "~FRRefunding stripe payment: $%s" % refunded) + return refunded + + def refund_latest_paypal_payment(self, partial=False): + if not self.paypal_sub_id: + return + + paypal_api = self.paypal_api() + refunded = False + + # Find transaction from subscription + now = datetime.datetime.now() + # 200 days captures Paypal's 180 day limit on refunds + start_date = (now-datetime.timedelta(days=200)).strftime("%Y-%m-%dT%H:%M:%SZ") + end_date = now.strftime("%Y-%m-%dT%H:%M:%SZ") + try: + transactions = paypal_api.get(f"/v1/billing/subscriptions/{self.paypal_sub_id}/transactions?start_time={start_date}&end_time={end_date}") + except paypalrestsdk.ResourceNotFound: + transactions = {} + if 'transactions' not in transactions or not len(transactions['transactions']): + logging.user(self.user, f"~FRCouldn't find paypal transactions: {self.paypal_sub_id} {transactions}") + return + + # Refund the latest transaction + transaction = transactions['transactions'][0] + today = datetime.datetime.now().strftime('%B %d, %Y') + url = f"/v2/payments/captures/{transaction['id']}/refund" + try: + response = paypal_api.post(url, { + 'reason': f"Refunded on {today}" + }) + except paypalrestsdk.exceptions.ResourceInvalid as e: + response = e.response.json() + if len(response.get('details', [])): + response = response['details'][0]['description'] + if 'status' in response and response['status'] == "COMPLETED": + refunded = int(float(transaction['amount_with_breakdown']['gross_amount']['value'])) + logging.user(self.user, "~FRRefunding paypal payment: $%s" % refunded) + else: + logging.user(self.user, "~FRCouldn't refund paypal payment: %s" % response) + refunded = response + + self.cancel_premium_paypal() + return refunded def cancel_premium(self): @@ -680,7 +713,7 @@ class Profile(models.Model): response = paypal_api.post(url, { 'reason': f"Cancelled on {today}" }) - logging.user(self.user, f"response: {response}") + # logging.user(self.user, f"response: {response}") logging.user(self.user, "~FRCanceling Paypal subscription: %s" % paypal_id) return True diff --git a/apps/profile/views.py b/apps/profile/views.py index 5f72ee0dd..1c1f26286 100644 --- a/apps/profile/views.py +++ b/apps/profile/views.py @@ -645,15 +645,16 @@ def cancel_premium(request): def refund_premium(request): user_id = request.POST.get('user_id') partial = request.POST.get('partial', False) + provider = request.POST.get('provider', None) user = User.objects.get(pk=user_id) try: - refunded = user.profile.refund_premium(partial=partial) + refunded = user.profile.refund_premium(partial=partial, provider=provider) except stripe.error.InvalidRequestError as e: refunded = e except PayPalAPIResponseError as e: refunded = e - return {'code': 1 if refunded else -1, 'refunded': refunded} + return {'code': 1 if type(refunded) == int else -1, 'refunded': refunded} @staff_member_required @ajax_login_required diff --git a/media/css/reader/reader.css b/media/css/reader/reader.css index b68a437bd..9367180b1 100644 --- a/media/css/reader/reader.css +++ b/media/css/reader/reader.css @@ -7099,6 +7099,7 @@ form.opml_import_form input { .NB-module-account-subscription .NB-module-stats-count-description { color: #0D003C; margin-bottom: 4px; + padding: 0 8px; } .NB-module-stats-count-graph {