2020-03-08 01:07:09 +00:00
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2020-01-30 23:51:51 +00:00
|
|
|
import datetime
|
|
|
|
import json
|
|
|
|
import requests
|
|
|
|
from rest_framework.exceptions import ValidationError
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
from django.db.models import Sum
|
|
|
|
from django.utils import timezone
|
2020-02-12 09:19:27 +00:00
|
|
|
from django.utils.timezone import now
|
2020-01-30 23:51:51 +00:00
|
|
|
|
|
|
|
from . import models, serializers, utils
|
2021-12-22 06:02:05 +00:00
|
|
|
from .. import settings
|
2020-01-30 23:51:51 +00:00
|
|
|
|
2020-02-13 22:51:57 +00:00
|
|
|
SANDBOX = False
|
2020-01-30 23:51:51 +00:00
|
|
|
if SANDBOX:
|
|
|
|
VERIFY_URL = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'
|
|
|
|
OUR_EMAIL = 'seller@paypalsandbox.com'
|
|
|
|
OUR_CURRENCY = 'USD'
|
|
|
|
else:
|
|
|
|
VERIFY_URL = 'https://ipnpb.paypal.com/cgi-bin/webscr'
|
2020-02-14 22:40:42 +00:00
|
|
|
OUR_EMAIL = 'paypal@protospace.ca'
|
2020-01-30 23:51:51 +00:00
|
|
|
OUR_CURRENCY = 'CAD'
|
|
|
|
|
|
|
|
def parse_paypal_date(string):
|
|
|
|
'''
|
2020-02-01 07:39:02 +00:00
|
|
|
Convert paypal date string into python datetime. PayPal's a bunch of idiots.
|
2020-01-30 23:51:51 +00:00
|
|
|
Their API returns dates in some custom format, so we have to parse it.
|
|
|
|
|
|
|
|
Stolen from:
|
|
|
|
https://github.com/spookylukey/django-paypal/blob/master/paypal/standard/forms.py
|
|
|
|
|
|
|
|
Return the UTC python datetime.
|
|
|
|
'''
|
|
|
|
MONTHS = [
|
|
|
|
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
|
|
|
|
'Sep', 'Oct', 'Nov', 'Dec',
|
|
|
|
]
|
|
|
|
|
2020-02-12 09:19:27 +00:00
|
|
|
if not string: return now()
|
|
|
|
|
2020-01-30 23:51:51 +00:00
|
|
|
value = string.strip()
|
|
|
|
try:
|
|
|
|
time_part, month_part, day_part, year_part, zone_part = value.split()
|
|
|
|
month_part = month_part.strip('.')
|
|
|
|
day_part = day_part.strip(',')
|
|
|
|
month = MONTHS.index(month_part) + 1
|
|
|
|
day = int(day_part)
|
|
|
|
year = int(year_part)
|
|
|
|
hour, minute, second = map(int, time_part.split(':'))
|
|
|
|
dt = datetime.datetime(year, month, day, hour, minute, second)
|
|
|
|
except ValueError as e:
|
|
|
|
raise ValidationError('Invalid date format {} {}'.format(
|
|
|
|
value, str(e)
|
|
|
|
))
|
|
|
|
|
|
|
|
if zone_part in ['PDT', 'PST']:
|
|
|
|
# PST/PDT is 'US/Pacific' and ignored, localize only cares about date
|
|
|
|
dt = timezone.pytz.timezone('US/Pacific').localize(dt)
|
|
|
|
dt = dt.astimezone(timezone.pytz.UTC)
|
|
|
|
else:
|
|
|
|
raise ValidationError('Bad timezone: ' + zone_part)
|
|
|
|
return dt
|
|
|
|
|
|
|
|
def record_ipn(data):
|
|
|
|
'''
|
|
|
|
Record each individual IPN (even dupes) for logging and debugging
|
|
|
|
'''
|
|
|
|
return models.IPN.objects.create(
|
|
|
|
data=data.urlencode(),
|
|
|
|
status='New',
|
|
|
|
)
|
|
|
|
|
|
|
|
def update_ipn(ipn, status):
|
|
|
|
ipn.status = status
|
|
|
|
ipn.save()
|
|
|
|
|
|
|
|
def verify_paypal_ipn(data):
|
2021-12-22 06:02:05 +00:00
|
|
|
if settings.DEBUG:
|
|
|
|
return True
|
|
|
|
|
2020-01-30 23:51:51 +00:00
|
|
|
params = data.copy()
|
|
|
|
params['cmd'] = '_notify-validate'
|
|
|
|
headers = {
|
|
|
|
'content-type': 'application/x-www-form-urlencoded',
|
|
|
|
'user-agent': 'spaceport',
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
2020-07-03 22:50:14 +00:00
|
|
|
r = requests.post(VERIFY_URL, params=params, headers=headers, timeout=4)
|
2020-01-30 23:51:51 +00:00
|
|
|
r.raise_for_status()
|
2020-07-26 00:06:17 +00:00
|
|
|
logger.info('Result: ' + r.text)
|
2020-07-03 22:50:14 +00:00
|
|
|
if r.text == 'VERIFIED':
|
|
|
|
return True
|
2020-01-30 23:51:51 +00:00
|
|
|
except BaseException as e:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.error('IPN verify - {} - {}'.format(e.__class__.__name__, str(e)))
|
2020-01-30 23:51:51 +00:00
|
|
|
|
2020-07-03 22:50:14 +00:00
|
|
|
logger.info('IPN - verification failed, retrying...')
|
|
|
|
|
|
|
|
try:
|
|
|
|
r = requests.post(VERIFY_URL, params=params, headers=headers, timeout=4)
|
|
|
|
r.raise_for_status()
|
2020-07-26 00:06:17 +00:00
|
|
|
logger.info('Result: ' + r.text)
|
2020-07-03 22:50:14 +00:00
|
|
|
if r.text == 'VERIFIED':
|
|
|
|
return True
|
|
|
|
except BaseException as e:
|
|
|
|
logger.error('IPN verify - {} - {}'.format(e.__class__.__name__, str(e)))
|
|
|
|
|
2020-07-03 23:03:00 +00:00
|
|
|
utils.alert_tanner('IPN failed to verify:\n\n' + str(data.dict()))
|
|
|
|
|
2020-07-03 22:50:14 +00:00
|
|
|
return False
|
2020-01-30 23:51:51 +00:00
|
|
|
|
|
|
|
def build_tx(data):
|
2020-02-12 09:19:27 +00:00
|
|
|
amount = float(data.get('mc_gross', 0))
|
2020-01-30 23:51:51 +00:00
|
|
|
return dict(
|
|
|
|
account_type='PayPal',
|
|
|
|
amount=amount,
|
2020-02-12 09:19:27 +00:00
|
|
|
date=parse_paypal_date(data.get('payment_date', '')),
|
2020-01-30 23:51:51 +00:00
|
|
|
info_source='PayPal IPN',
|
2020-02-12 09:19:27 +00:00
|
|
|
payment_method=data.get('payment_type', 'unknown'),
|
|
|
|
paypal_payer_id=data.get('payer_id', 'unknown'),
|
|
|
|
paypal_txn_id=data.get('txn_id', 'unknown'),
|
2022-01-23 01:52:23 +00:00
|
|
|
paypal_txn_type=data.get('txn_type', 'unknown'),
|
2020-02-12 09:19:27 +00:00
|
|
|
reference_number=data.get('txn_id', 'unknown'),
|
2020-01-30 23:51:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def create_unmatched_member_tx(data):
|
|
|
|
transactions = models.Transaction.objects
|
|
|
|
|
2020-08-08 22:45:43 +00:00
|
|
|
report_memo = 'Cant link sender name, {} {}, email: {}, note: {} - {}'.format(
|
2020-02-12 09:19:27 +00:00
|
|
|
data.get('first_name', 'unknown'),
|
|
|
|
data.get('last_name', 'unknown'),
|
|
|
|
data.get('payer_email', 'unknown'),
|
|
|
|
data.get('custom', 'none'),
|
2020-08-08 22:45:43 +00:00
|
|
|
data.get('memo', 'none'),
|
2020-01-30 23:51:51 +00:00
|
|
|
)
|
|
|
|
|
2022-08-23 20:36:03 +00:00
|
|
|
tx = transactions.create(
|
2020-01-30 23:51:51 +00:00
|
|
|
**build_tx(data),
|
|
|
|
report_memo=report_memo,
|
|
|
|
report_type='Unmatched Member',
|
|
|
|
)
|
|
|
|
|
2022-08-23 20:36:03 +00:00
|
|
|
utils.log_transaction(tx)
|
|
|
|
return tx
|
|
|
|
|
2020-02-23 04:31:58 +00:00
|
|
|
def create_member_dues_tx(data, member, num_months, deal):
|
2020-01-30 23:51:51 +00:00
|
|
|
transactions = models.Transaction.objects
|
|
|
|
|
|
|
|
# new member 3 for 2 will have to be manual anyway
|
2020-02-23 04:31:58 +00:00
|
|
|
if deal == 12 and num_months == 11:
|
2020-01-30 23:51:51 +00:00
|
|
|
num_months = 12
|
2020-02-23 04:31:58 +00:00
|
|
|
deal_str = '12 for 11, '
|
|
|
|
elif deal == 3 and num_months == 2:
|
|
|
|
num_months = 3
|
|
|
|
deal_str = '3 for 2, '
|
2023-05-15 17:19:59 +00:00
|
|
|
elif num_months == 11: # handle pre-Spaceport yearly subs
|
|
|
|
num_months = 12
|
|
|
|
deal_str = '12 for 11 (legacy), '
|
2020-01-30 23:51:51 +00:00
|
|
|
else:
|
2020-02-23 04:31:58 +00:00
|
|
|
deal_str = ''
|
2020-01-30 23:51:51 +00:00
|
|
|
|
|
|
|
user = getattr(member, 'user', None)
|
|
|
|
memo = '{}{} {} - Protospace Membership, {}'.format(
|
2020-02-23 04:31:58 +00:00
|
|
|
deal_str,
|
2020-02-12 09:19:27 +00:00
|
|
|
data.get('first_name', 'unknown'),
|
|
|
|
data.get('last_name', 'unknown'),
|
|
|
|
data.get('payer_email', 'unknown'),
|
2020-01-30 23:51:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
tx = transactions.create(
|
|
|
|
**build_tx(data),
|
|
|
|
memo=memo,
|
2020-02-22 05:21:33 +00:00
|
|
|
category='Membership',
|
2020-01-30 23:51:51 +00:00
|
|
|
number_of_membership_months=num_months,
|
|
|
|
user=user,
|
|
|
|
)
|
|
|
|
utils.tally_membership_months(member)
|
2022-08-23 20:36:03 +00:00
|
|
|
utils.log_transaction(tx)
|
2020-01-30 23:51:51 +00:00
|
|
|
return tx
|
|
|
|
|
|
|
|
def create_unmatched_purchase_tx(data, member):
|
|
|
|
transactions = models.Transaction.objects
|
|
|
|
|
|
|
|
user = getattr(member, 'user', None)
|
2020-08-08 22:45:43 +00:00
|
|
|
report_memo = 'Unknown payment reason, {} {}, email: {}, note: {} - {}'.format(
|
2020-02-12 09:19:27 +00:00
|
|
|
data.get('first_name', 'unknown'),
|
|
|
|
data.get('last_name', 'unknown'),
|
|
|
|
data.get('payer_email', 'unknown'),
|
|
|
|
data.get('custom', 'none'),
|
2020-08-08 22:45:43 +00:00
|
|
|
data.get('memo', 'none'),
|
2020-01-30 23:51:51 +00:00
|
|
|
)
|
|
|
|
|
2022-08-23 20:36:03 +00:00
|
|
|
tx = transactions.create(
|
2020-01-30 23:51:51 +00:00
|
|
|
**build_tx(data),
|
|
|
|
report_memo=report_memo,
|
|
|
|
report_type='Unmatched Purchase',
|
|
|
|
user=user,
|
|
|
|
)
|
|
|
|
|
2022-08-23 20:36:03 +00:00
|
|
|
utils.log_transaction(tx)
|
|
|
|
return tx
|
|
|
|
|
2020-01-30 23:51:51 +00:00
|
|
|
def create_member_training_tx(data, member, training):
|
|
|
|
transactions = models.Transaction.objects
|
|
|
|
|
|
|
|
user = getattr(member, 'user', None)
|
|
|
|
memo = '{} {} - {} Course, email: {}, session: {}, training: {}'.format(
|
2020-02-12 09:19:27 +00:00
|
|
|
data.get('first_name', 'unknown'),
|
|
|
|
data.get('last_name', 'unknown'),
|
2020-01-30 23:51:51 +00:00
|
|
|
training.session.course.name,
|
2020-02-12 09:19:27 +00:00
|
|
|
data.get('payer_email', 'unknown'),
|
2020-01-30 23:51:51 +00:00
|
|
|
str(training.session.id),
|
|
|
|
str(training.id),
|
|
|
|
)
|
|
|
|
|
2022-08-23 20:36:03 +00:00
|
|
|
tx = transactions.create(
|
2020-01-30 23:51:51 +00:00
|
|
|
**build_tx(data),
|
2020-02-14 23:00:20 +00:00
|
|
|
category='OnAcct',
|
2020-01-30 23:51:51 +00:00
|
|
|
memo=memo,
|
|
|
|
user=user,
|
|
|
|
)
|
|
|
|
|
2022-08-23 20:36:03 +00:00
|
|
|
utils.log_transaction(tx)
|
|
|
|
return tx
|
|
|
|
|
2020-02-10 00:23:02 +00:00
|
|
|
def check_training(data, training_id, amount):
|
2020-01-30 23:51:51 +00:00
|
|
|
trainings = models.Training.objects
|
|
|
|
|
|
|
|
if not trainings.filter(id=training_id).exists():
|
|
|
|
return False
|
|
|
|
|
|
|
|
training = trainings.get(id=training_id)
|
|
|
|
|
2022-05-23 21:27:24 +00:00
|
|
|
#if training.attendance_status != 'Waiting for payment':
|
|
|
|
# return False
|
2020-01-30 23:51:51 +00:00
|
|
|
|
|
|
|
if not training.session:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if training.session.is_cancelled:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if training.session.cost != amount:
|
|
|
|
return False
|
|
|
|
|
2020-02-10 00:23:02 +00:00
|
|
|
member = training.user.member
|
2020-01-30 23:51:51 +00:00
|
|
|
|
2022-06-06 06:42:09 +00:00
|
|
|
if training.attendance_status == 'Waiting for payment':
|
|
|
|
training.attendance_status = 'Confirmed'
|
2020-02-10 00:23:02 +00:00
|
|
|
training.paid_date = datetime.date.today()
|
2020-01-30 23:51:51 +00:00
|
|
|
training.save()
|
|
|
|
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Amount valid for training cost, id: ' + str(training.id))
|
2020-01-30 23:51:51 +00:00
|
|
|
return create_member_training_tx(data, member, training)
|
|
|
|
|
2022-08-22 22:15:03 +00:00
|
|
|
def create_category_tx(data, member, custom_json, amount):
|
2020-02-23 04:31:58 +00:00
|
|
|
transactions = models.Transaction.objects
|
|
|
|
|
|
|
|
user = getattr(member, 'user', None)
|
2022-08-22 22:15:03 +00:00
|
|
|
category = custom_json['category']
|
|
|
|
|
|
|
|
if category == 'Exchange':
|
|
|
|
protocoin = amount
|
|
|
|
note = '{} Protocoin Purchase'.format(amount)
|
|
|
|
else:
|
|
|
|
protocoin = 0
|
|
|
|
note = custom_json.get('memo', 'none')
|
|
|
|
|
2020-02-23 04:31:58 +00:00
|
|
|
memo = '{} {} - {}, email: {}, note: {}'.format(
|
|
|
|
data.get('first_name', 'unknown'),
|
|
|
|
data.get('last_name', 'unknown'),
|
2022-08-22 22:15:03 +00:00
|
|
|
category,
|
2020-02-23 04:31:58 +00:00
|
|
|
data.get('payer_email', 'unknown'),
|
2022-08-22 22:15:03 +00:00
|
|
|
note,
|
2020-02-23 04:31:58 +00:00
|
|
|
)
|
|
|
|
|
2022-08-23 20:36:03 +00:00
|
|
|
tx = transactions.create(
|
2020-02-23 04:31:58 +00:00
|
|
|
**build_tx(data),
|
2022-08-22 22:15:03 +00:00
|
|
|
category=category,
|
2020-02-23 04:31:58 +00:00
|
|
|
memo=memo,
|
|
|
|
user=user,
|
2022-08-22 22:15:03 +00:00
|
|
|
protocoin=protocoin,
|
2020-02-23 04:31:58 +00:00
|
|
|
)
|
|
|
|
|
2022-08-23 20:36:03 +00:00
|
|
|
utils.log_transaction(tx)
|
|
|
|
return tx
|
|
|
|
|
2020-01-30 23:51:51 +00:00
|
|
|
|
|
|
|
def process_paypal_ipn(data):
|
|
|
|
'''
|
|
|
|
Receive IPN from PayPal, then verify it. If it's good, try to associate it
|
|
|
|
with a member. If the value is a multiple of member dues, credit that many
|
|
|
|
months of membership. Ignore if payment incomplete or duplicate IPN.
|
|
|
|
|
|
|
|
Blocks the IPN POST response, so keep it quick.
|
|
|
|
'''
|
|
|
|
ipn = record_ipn(data)
|
|
|
|
|
2020-02-23 07:44:02 +00:00
|
|
|
if verify_paypal_ipn(data):
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - verified')
|
2020-01-30 23:51:51 +00:00
|
|
|
else:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.error('IPN - verification failed')
|
2020-01-30 23:51:51 +00:00
|
|
|
update_ipn(ipn, 'Verification Failed')
|
|
|
|
return False
|
|
|
|
|
2020-02-12 09:19:27 +00:00
|
|
|
amount = float(data.get('mc_gross', '0'))
|
2020-01-30 23:51:51 +00:00
|
|
|
|
2020-02-12 09:19:27 +00:00
|
|
|
if data.get('payment_status', 'unknown') != 'Completed':
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Payment not yet completed, ignoring')
|
2020-01-30 23:51:51 +00:00
|
|
|
update_ipn(ipn, 'Payment Incomplete')
|
|
|
|
return False
|
|
|
|
|
2020-02-12 09:19:27 +00:00
|
|
|
if data.get('receiver_email', 'unknown') != OUR_EMAIL:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Payment not for us, ignoring')
|
2020-01-30 23:51:51 +00:00
|
|
|
update_ipn(ipn, 'Invalid Receiver')
|
|
|
|
return False
|
|
|
|
|
2020-02-12 09:19:27 +00:00
|
|
|
if data.get('mc_currency', 'unknown') != OUR_CURRENCY:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Payment currency invalid, ignoring')
|
2020-01-30 23:51:51 +00:00
|
|
|
update_ipn(ipn, 'Invalid Currency')
|
|
|
|
return False
|
|
|
|
|
|
|
|
transactions = models.Transaction.objects
|
|
|
|
members = models.Member.objects
|
|
|
|
hints = models.PayPalHint.objects
|
|
|
|
|
2020-02-14 22:40:42 +00:00
|
|
|
if 'txn_id' not in data:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Missing transaction ID, ignoring')
|
2020-02-14 22:40:42 +00:00
|
|
|
update_ipn(ipn, 'Missing ID')
|
|
|
|
return False
|
|
|
|
|
2021-12-22 06:02:05 +00:00
|
|
|
# TODO: index txn_id?
|
2020-02-14 22:40:42 +00:00
|
|
|
if transactions.filter(paypal_txn_id=data['txn_id']).exists():
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Duplicate transaction, ignoring')
|
2020-01-30 23:51:51 +00:00
|
|
|
update_ipn(ipn, 'Duplicate')
|
|
|
|
return False
|
|
|
|
|
2020-02-10 00:23:02 +00:00
|
|
|
try:
|
2022-07-12 06:13:18 +00:00
|
|
|
custom_json = json.loads(data.get('custom', '').replace('`', '"'))
|
2020-02-10 00:23:02 +00:00
|
|
|
except (KeyError, ValueError):
|
2020-02-23 04:31:58 +00:00
|
|
|
custom_json = {}
|
2020-02-10 00:23:02 +00:00
|
|
|
|
2020-02-23 04:31:58 +00:00
|
|
|
if 'training' in custom_json:
|
2020-02-10 00:23:02 +00:00
|
|
|
tx = check_training(data, custom_json['training'], amount)
|
|
|
|
if tx:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Training matched, adding hint and returning')
|
2020-02-23 04:31:58 +00:00
|
|
|
update_ipn(ipn, 'Accepted, training')
|
2020-02-14 23:00:20 +00:00
|
|
|
hints.update_or_create(
|
2020-02-12 09:19:27 +00:00
|
|
|
account=data.get('payer_id', 'unknown'),
|
2021-11-16 22:18:47 +00:00
|
|
|
defaults=dict(user=tx.user),
|
2020-02-10 00:23:02 +00:00
|
|
|
)
|
|
|
|
return tx
|
|
|
|
|
2021-12-22 06:02:05 +00:00
|
|
|
user = False
|
2020-02-23 04:31:58 +00:00
|
|
|
|
2021-12-22 06:02:05 +00:00
|
|
|
try:
|
|
|
|
user = hints.get(account=data['payer_id']).user
|
2021-12-22 06:25:02 +00:00
|
|
|
except models.PayPalHint.DoesNotExist:
|
2021-12-22 06:02:05 +00:00
|
|
|
logger.info('IPN - No PayPalHint found for %s', data['payer_id'])
|
2020-02-23 04:31:58 +00:00
|
|
|
|
2021-12-22 06:02:05 +00:00
|
|
|
if not user and 'member' in custom_json:
|
2020-02-23 04:31:58 +00:00
|
|
|
member_id = custom_json['member']
|
2021-12-22 06:02:05 +00:00
|
|
|
try:
|
|
|
|
user = members.get(id=member_id).user
|
2021-12-22 06:25:02 +00:00
|
|
|
except models.Member.DoesNotExist:
|
2021-12-22 06:02:05 +00:00
|
|
|
pass
|
2020-02-23 04:31:58 +00:00
|
|
|
|
2021-12-22 06:02:05 +00:00
|
|
|
if not user:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Unable to associate with member, reporting')
|
2020-01-30 23:51:51 +00:00
|
|
|
update_ipn(ipn, 'Accepted, Unmatched Member')
|
|
|
|
return create_unmatched_member_tx(data)
|
|
|
|
|
2021-12-22 06:02:05 +00:00
|
|
|
member = user.member
|
2020-02-23 04:31:58 +00:00
|
|
|
|
2021-11-16 22:18:47 +00:00
|
|
|
hints.update_or_create(
|
|
|
|
account=data.get('payer_id', 'unknown'),
|
2021-12-22 06:02:05 +00:00
|
|
|
defaults=dict(user=user),
|
2021-11-16 22:18:47 +00:00
|
|
|
)
|
|
|
|
|
2022-08-22 22:15:03 +00:00
|
|
|
if custom_json.get('category', False) in ['Snacks', 'OnAcct', 'Donation', 'Consumables', 'Purchases', 'Exchange']:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Category matched')
|
2020-02-23 04:31:58 +00:00
|
|
|
update_ipn(ipn, 'Accepted, category')
|
2022-08-22 22:15:03 +00:00
|
|
|
return create_category_tx(data, member, custom_json, amount)
|
2020-02-23 04:31:58 +00:00
|
|
|
|
2020-01-30 23:51:51 +00:00
|
|
|
monthly_fees = member.monthly_fees
|
|
|
|
|
2020-02-13 22:49:08 +00:00
|
|
|
if amount.is_integer() and monthly_fees and amount % monthly_fees == 0:
|
2020-01-30 23:51:51 +00:00
|
|
|
num_months = int(amount // monthly_fees)
|
|
|
|
else:
|
|
|
|
num_months = 0
|
|
|
|
|
|
|
|
if num_months:
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Amount valid for membership dues, adding months')
|
2020-01-30 23:51:51 +00:00
|
|
|
update_ipn(ipn, 'Accepted, Member Dues')
|
2020-02-23 04:31:58 +00:00
|
|
|
deal = custom_json.get('deal', False)
|
|
|
|
return create_member_dues_tx(data, member, num_months, deal)
|
2020-01-30 23:51:51 +00:00
|
|
|
|
2020-03-08 01:07:09 +00:00
|
|
|
logger.info('IPN - Unable to find a reason for payment, reporting')
|
2020-01-30 23:51:51 +00:00
|
|
|
update_ipn(ipn, 'Accepted, Unmatched Purchase')
|
|
|
|
return create_unmatched_purchase_tx(data, member)
|