mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-05 16:58:59 +00:00
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:
parent
0a2a9607f0
commit
01ad0e8656
6 changed files with 94 additions and 49 deletions
14
Makefile
14
Makefile
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"service": {
|
"service": {
|
||||||
"name": "db-mongo-staging",
|
"name": "db-mongo",
|
||||||
"id": "{{ inventory_hostname }}",
|
"id": "{{ inventory_hostname }}",
|
||||||
"tags": [
|
"tags": [
|
||||||
"db"
|
"db"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue