Paypal refunds are now working. Need to add partial balances when upgrading tiers on paypal. Eventually need to think about refunds for switching providers.

This commit is contained in:
Samuel Clay 2022-02-10 13:40:07 -05:00
parent 0a2a9607f0
commit 01ad0e8656
6 changed files with 94 additions and 49 deletions

View file

@ -84,10 +84,20 @@ keys:
- openssl dhparam -out config/certificates/dhparam-2048.pem 2048 - 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 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 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 - 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 - 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 # Digital Ocean / Terraform
list: list:

View file

@ -1,6 +1,6 @@
--- ---
- name: DEPLOY -> www - name: DEPLOY -> www
hosts: www,staging hosts: haproxy,staging
gather_facts: false gather_facts: false
vars_files: vars_files:
- ../env_vars/base.yml - ../env_vars/base.yml

View file

@ -1,6 +1,6 @@
{ {
"service": { "service": {
"name": "db-mongo-staging", "name": "db-mongo",
"id": "{{ inventory_hostname }}", "id": "{{ inventory_hostname }}",
"tags": [ "tags": [
"db" "db"

View file

@ -600,52 +600,85 @@ class Profile(models.Model):
return ','.join(failed) return ','.join(failed)
def refund_premium(self, partial=False): def refund_premium(self, partial=False, provider=None):
refunded = False refunded = False
if provider == "paypal":
if self.stripe_id: refunded = self.refund_latest_paypal_payment(partial=partial)
stripe.api_key = settings.STRIPE_SECRET elif provider == "stripe":
stripe_customer = stripe.Customer.retrieve(self.stripe_id) refunded = self.refund_latest_stripe_payment(partial=partial)
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)
else: else:
self.cancel_premium() # Find last payment, refund that
payment_history = PaymentHistory.objects.filter(user=self.user,
paypal_opts = { payment_provider__in=['paypal', 'stripe'])
'API_ENVIRONMENT': 'SANDBOX', if payment_history.count():
'API_USERNAME': settings.PAYPAL_API_USERNAME, provider = payment_history[0].payment_provider
'API_PASSWORD': settings.PAYPAL_API_PASSWORD, if provider == "stripe":
'API_SIGNATURE': settings.PAYPAL_API_SIGNATURE, refunded = self.refund_latest_stripe_payment(partial=partial)
'API_CA_CERTS': False, elif provider == "paypal":
} refunded = self.refund_latest_paypal_payment(partial=partial)
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
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 return refunded
def cancel_premium(self): def cancel_premium(self):
@ -680,7 +713,7 @@ class Profile(models.Model):
response = paypal_api.post(url, { response = paypal_api.post(url, {
'reason': f"Cancelled on {today}" '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) logging.user(self.user, "~FRCanceling Paypal subscription: %s" % paypal_id)
return True return True

View file

@ -645,15 +645,16 @@ def cancel_premium(request):
def refund_premium(request): def refund_premium(request):
user_id = request.POST.get('user_id') user_id = request.POST.get('user_id')
partial = request.POST.get('partial', False) partial = request.POST.get('partial', False)
provider = request.POST.get('provider', None)
user = User.objects.get(pk=user_id) user = User.objects.get(pk=user_id)
try: try:
refunded = user.profile.refund_premium(partial=partial) refunded = user.profile.refund_premium(partial=partial, provider=provider)
except stripe.error.InvalidRequestError as e: except stripe.error.InvalidRequestError as e:
refunded = e refunded = e
except PayPalAPIResponseError as e: except PayPalAPIResponseError as e:
refunded = 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 @staff_member_required
@ajax_login_required @ajax_login_required

View file

@ -7099,6 +7099,7 @@ form.opml_import_form input {
.NB-module-account-subscription .NB-module-stats-count-description { .NB-module-account-subscription .NB-module-stats-count-description {
color: #0D003C; color: #0D003C;
margin-bottom: 4px; margin-bottom: 4px;
padding: 0 8px;
} }
.NB-module-stats-count-graph { .NB-module-stats-count-graph {