def _process_subscription_charges(self, charges, subscription, family): for charge in charges: state = charge["state"] if state == "refunded": refund = True elif state == "captured": refund = False elif state == "failed": continue else: print("State not handled: " + state) continue sale = Sale() sale.payer_name = subscription['payer_name'] sale.payer_email = subscription['payer_email'] if subscription['fee_payer'] == "payer": print( "Fee is paid by payer. Situation has not yet been analyzed." ) sale.payment_method = Sale.PAID_BY_WEPAY sale.sale_date = date.fromtimestamp(int( charge['create_time'])) # TODO: This is UTC timezone. sale.total_paid_by_customer = charge["amount"] sale.processing_fee = charge["fee"] sale.ctrlid = "{}:{}".format(self.CTRLID_PREFIX, charge['subscription_charge_id']) django_sale = self.upsert(sale) mship = Membership() mship.sale = Sale(id=django_sale['id']) mship.sale_price = sale.total_paid_by_customer if subscription['fee_payer'] == 'payer': mship.sale_price -= sale.processing_fee if family > 0: mship.sale_price -= Decimal(10.00) * Decimal(family) mship.ctrlid = "{}:{}".format(self.CTRLID_PREFIX, charge['subscription_charge_id']) mship.start_date = sale.sale_date mship.end_date = mship.start_date + relativedelta(months=1, days=-1) self.upsert(mship) for n in range(family): fam = Membership() fam.sale = mship.sale fam.sale_price = 10.00 fam.membership_type = Membership.MT_FAMILY fam.start_date = mship.start_date fam.end_date = mship.end_date fam.ctrlid = "{}:{}:{}".format(self.CTRLID_PREFIX, mship.ctrlid, n) self.upsert(fam)
def _process_membership_item(self, sale, item, item_num, membership_type, dur_amt, dur_unit): month = self.month_in_str(item['name']) family = None for modifier in item["modifiers"]: lmod = modifier["name"].lower() if lmod == "just myself": family = 0 elif lmod == "1 add'l family member": family = 1 elif lmod == "2 add'l family members": family = 2 elif lmod == "3 add'l family members": family = 3 elif lmod == "4 add'l family members": family = 4 elif lmod == "5 add'l family members": family = 5 if membership_type == Membership.MT_WORKTRADE and month is None: month = self.month_in_str(lmod) if family is None: ctrlid = sale['ctrlid'] # A special case because the 12 month membership item was set up incorrectly. if ctrlid == "SQ:kZxawM1NBAxvqr2vKU2ZnzMF" and item_num == 1: family = 0 else: print("Couldn't determine family count for {}:{}".format( sale['ctrlid'], item_num)) family = 0 quantity = int(float(item['quantity'])) for n in range(1, quantity + 1): mship = Membership() mship.sale = Sale(id=sale['id']) mship.membership_type = membership_type mship.ctrlid = "{}:{}:{}".format(sale['ctrlid'], item_num, n) mship.start_date = parse(sale['sale_date']).date() if mship.membership_type == Membership.MT_WORKTRADE: mship.start_date = mship.start_date.replace( day=1) # WT always starts on the 1st. if month is not None: # Hopefully, the buyer specified the month. mship.start_date = mship.start_date.replace(month=month) mship.end_date = mship.start_date + relativedelta(**{ dur_unit: dur_amt, "days": -1 }) mship.sale_price = Decimal( item['gross_sales_money']['amount']) / Decimal( quantity * 100.0) mship.sale_price -= Decimal(10.00) * Decimal(family) self.upsert(mship) for f in range(family): fam = Membership() fam.sale = mship.sale fam.sale_price = 10 fam.membership_type = Membership.MT_FAMILY fam.start_date = mship.start_date fam.end_date = mship.end_date fam.ctrlid = "{}:{}".format(mship.ctrlid, f) self.upsert(fam)
def _special_case_7cQ69ctaeYok1Ry3KOTFbyMF(self, sale): mship = Membership() mship.sale = Sale(id=sale['id']) mship.membership_type = Membership.MT_REGULAR mship.ctrlid = "{}:1:1".format(sale['ctrlid']) mship.start_date = date(2016, 4, 5) mship.end_date = date(2015, 4, 18) mship.sale_price = 25.00 self.upsert(mship)
def _special_case_ixStxgstn56QI8jnJtcCtzMF(self, sale): mship = Membership() mship.sale = Sale(id=sale['id']) mship.membership_type = Membership.MT_REGULAR mship.ctrlid = "{}:1:1".format(sale['ctrlid']) mship.start_date = date(2014, 12, 12) mship.end_date = date(2015, 6, 11) mship.sale_price = 225.00 self.upsert(mship)
def _process_giftcard_item(self, sale, item, item_num): quantity = int(float(item['quantity'])) for n in range(1, quantity + 1): cardref = MembershipGiftCardReference() cardref.ctrlid = "{}:{}:{}".format(sale['ctrlid'], item_num, n) cardref.sale = Sale(id=sale['id']) cardref.sale_price = Decimal( item["net_sales_money"]["amount"]) / Decimal(quantity * 100.0) self.upsert(cardref)
def _process_donation(self, sale, checkout, earmark=None): don = MonetaryDonation() don.ctrlid = "{}:{}".format(self.CTRLID_PREFIX, checkout['checkout_id']) don.sale = Sale(id=sale.id) don.amount = sale.total_paid_by_customer if checkout['fee_payer'] == 'payer': don.amount -= sale.processing_fee if earmark is not None: don.earmark = earmark self.upsert(don)
def _process_donation_item(self, sale, item, item_num): quantity = int(float(item['quantity'])) for n in range(1, quantity + 1): don = MonetaryDonation() don.ctrlid = "{}:{}:{}".format(sale['ctrlid'], item_num, n) don.sale = Sale(id=sale['id']) don.amount = Decimal( item["gross_sales_money"]["amount"]) / Decimal( quantity * 100.0) self.upsert(don)
def _special_case_0JFN0loJ0kcy8DXCvuDVwwMF(self, sale): # Verify: This was erroneously entered as a donation but was really a work-trade payment. mship = Membership() mship.sale = Sale(id=sale['id']) mship.member = Member( id=19 ) # Lookup by name would be better but I don't want to have names in the code. mship.membership_type = Membership.MT_WORKTRADE mship.ctrlid = "{}:1:1".format(sale['ctrlid']) mship.start_date = date(2015, 12, 1) mship.end_date = date(2015, 12, 31) mship.sale_price = 10.00 self.upsert(mship)
def _process_membership_sale(self, sale, checkout, months, family): mship = Membership() mship.sale = Sale(id=sale.id) mship.sale_price = sale.total_paid_by_customer if checkout['fee_payer'] == 'payer': mship.sale_price -= sale.processing_fee if family > 0: mship.sale_price -= Decimal(10.00) * Decimal(family) mship.ctrlid = "{}:{}".format(self.CTRLID_PREFIX, checkout['checkout_id']) mship.start_date = sale.sale_date mship.end_date = mship.start_date + relativedelta(months=months, days=-1) self.upsert(mship) for n in range(family): fam = Membership() fam.sale = mship.sale fam.sale_price = 10.00 fam.membership_type = Membership.MT_FAMILY fam.start_date = mship.start_date fam.end_date = mship.end_date fam.ctrlid = "{}:{}:{}".format(self.CTRLID_PREFIX, mship.ctrlid, n) self.upsert(fam)
def _process_recurring_payment(self, agreement: sdk.BillingAgreement, transaction): when_datetime = parse(transaction["time_stamp"]) # type: datetime when_local_date = localtime(when_datetime).date() payer_name = transaction["payer_name"] payer_email = transaction["payer_email"] paid_amount = transaction["amount"]["value"] net_amount = transaction["net_amount"]["value"] fee_amount = str( Decimal(-1.0) * Decimal(transaction["fee_amount"]["value"])) trans_id = transaction["transaction_id"] fam_count = int((float(paid_amount) - 50.00) / 10.00) assert transaction["net_amount"]["currency"] == "USD" assert transaction["fee_amount"]["currency"] == "USD" assert transaction["amount"]["currency"] == "USD" assert transaction["time_zone"] == "GMT" assert float(paid_amount) >= 50.00 sale = Sale() sale.payment_method = Sale.PAID_BY_PAYPAL sale.payer_email = payer_email sale.payer_name = payer_name sale.sale_date = when_local_date sale.total_paid_by_customer = Decimal( paid_amount ) # The full amount paid by the person, including payment processing fee IF CUSTOMER PAID IT. sale.processing_fee = Decimal(fee_amount) sale.ctrlid = "{}:{}".format(self.CTRLID_PREFIX, trans_id) django_sale = self.upsert(sale) sale.id = django_sale['id'] if django_sale["protected"]: # If the sale is protected then all details are also protected. # Otherwise there's no way to protect a deletion. return self._member_and_family(sale, 1)
def _process_other_item(self, sale, item, item_num): quantity = int(float(item['quantity'])) if item['name'] not in self.OTHER_ITEM_TYPE_MAP: print("Fetcher does not map: " + item['name']) return mappedname = self.OTHER_ITEM_TYPE_MAP[item['name']] typepk = self._get_id(self.URLS[OtherItemType], {'name': mappedname}) if typepk is None: print("Server does not have other item type: " + mappedname) return other = OtherItem() other.type = OtherItemType(id=typepk) other.sale = Sale(id=sale['id']) other.sale_price = Decimal( item["net_sales_money"]["amount"]) / Decimal(quantity * 100.0) other.qty_sold = int(float(item['quantity'])) other.ctrlid = "{}:{}".format(sale['ctrlid'], item_num) self.upsert(other)
def _process_checkouts(self, checkouts): assert len(checkouts) < self.limit for checkout in checkouts: # Filter out questionable checkouts if not checkout['state'].startswith("captured"): continue # At this point we know we'll be creating an IncomeTransaction (currently called Sale) sale = Sale() sale.payment_method = Sale.PAID_BY_WEPAY sale.payer_email = checkout['payer_email'] sale.payer_name = checkout['payer_name'] sale.sale_date = date.fromtimestamp(int( checkout['create_time'])) # TODO: This is UTC timezone. sale.total_paid_by_customer = Decimal( checkout['gross'] ) # Docs: "The total dollar amount paid by the payer" sale.processing_fee = Decimal(checkout['fee']) + Decimal( checkout['app_fee']) if checkout['fee_payer'] == 'payer': sale.fee_payer = Sale.FEE_PAID_BY_CUSTOMER else: sale.fee_payer = Sale.FEE_PAID_BY_US sale.ctrlid = "{}:{}".format(self.CTRLID_PREFIX, checkout['checkout_id']) django_sale = self.upsert(sale) sale.id = django_sale['id'] desc = checkout['short_description'] if checkout['checkout_id'] in [1877931854, 390559320]: # These are membership purchases that were erroneously entered as donations. self._process_membership_sale(sale, checkout, 6, 0) continue if checkout['amount'] == 20 \ and desc == "Recurring Payment to Donation" \ and md5(checkout['payer_name'].encode('utf-8')).hexdigest() == "95c53a5e254c1847ad8526b625862294": # Dale says that this recurring donation should be treated as a grandfathered membership. # I'm checking against md5 of the payer-name so I don't have personal info in source. self._process_membership_sale(sale, checkout, 1, 0) continue months = None if desc.startswith("One Month Membership"): months = 1 elif desc.startswith("Three Month Membership"): months = 3 elif desc.startswith("Six Month Membership"): months = 6 elif desc.startswith("Recurring Payment to Dues-Paying Member"): months = 1 elif desc.startswith("Payment to Dues-Paying Member ONE-TIME"): months = 1 if months is not None: if desc.endswith("+ 1 family member"): family = 1 elif desc.endswith("+ 2 family member"): family = 2 elif desc.endswith("+ 3 family member"): family = 3 elif desc.endswith("+ 4 family member"): family = 4 elif desc.endswith("+ 5 family member"): family = 5 elif desc.endswith("+ 6 family member"): family = 6 else: family = 0 self._process_membership_sale(sale, checkout, months, family) continue if desc in [ "Xerocraft KMKR Radio Donation", "Single Maketopolis Ticket Purchase" ]: # The Maketopolis tickets were for KMKR's musical portion of the event. self._process_donation(sale, checkout, ACCT_KMKR_CAMPAIGN) continue if desc.endswith("Event Payment") \ or desc.startswith("Recurring Payment to Donation") \ or desc.startswith("Payment to Donation at"): self._process_donation(sale, checkout) continue print("Didn't recognize: " + desc)
def _process_payments(self, payments): for payment in payments: # TODO: Clean out any existing sale & line items in case of refund, and then skip. # Refund sensing logic will be something like this: # if len(payment.refunds)>0 # and payment.refunds[0].payment_id == payment.id # and payment.refunds[0].type == "FULL" if payment['tender'][0]['type'] == "NO_SALE": continue if payment['id'] in self.SALES_TO_SKIP: continue if len(payment["tender"]) != 1: print( "Code doesn't handle multiple tenders as in {}. Skipping.". format(payment['id'])) continue sale = Sale() sale.sale_date = parse(payment["created_at"]).date() sale.payer_name = self.get_name_from_receipt( payment['receipt_url']) sale.payer_email = "" # Annoyingly, not provided by Square. sale.payment_method = Sale.PAID_BY_SQUARE sale.method_detail = self._get_tender_type(payment) sale.total_paid_by_customer = Decimal( payment["tender"][0]["total_money"]["amount"]) / Decimal(100) sale.processing_fee = abs( Decimal( payment["processing_fee_money"]["amount"])) / Decimal(100) sale.ctrlid = "SQ:" + payment["id"] #sale.payer_notes = payment.get("notes", "").strip() if payment['id'] == "ixStxgstn56QI8jnJtcCtzMF": sale.payer_name = sale.payer_name.replace("M ", "MIKE ") django_sale = self.upsert(sale) if payment['id'] == "7cQ69ctaeYok1Ry3KOTFbyMF": # Person wanted to pay for two weeks while he was in town. # I added an item for this but don't like the way it worked out. # So I'm treating this as a special case. self._special_case_7cQ69ctaeYok1Ry3KOTFbyMF(django_sale) elif payment['id'] == "0JFN0loJ0kcy8DXCvuDVwwMF": # This is a work trade payment that was erroneously entered as a custom payment. # So we do this special processing to ingest it as a 1 month Work Trade membership. self._special_case_0JFN0loJ0kcy8DXCvuDVwwMF(django_sale) elif payment['id'] == "ixStxgstn56QI8jnJtcCtzMF": # This is a six month membership that was erroneously entered as a custom payment. # So we do this special processing to ingest it as a 6 month membership. self._special_case_ixStxgstn56QI8jnJtcCtzMF(django_sale) else: if django_sale["protected"] == True: # If the sale is protected then all details are also protected. # Otherwise there's no way to protect a deletion. return itemizations = payment['itemizations'] self._process_itemizations(itemizations, django_sale)
def _process_payment(self, payment: sdk.Payment): payment_id = payment['id'] if payment_id in self.PAYMENTS_TO_IGNORE: return state = payment['state'] # e.g. 'approved' when_datetime = parse(payment['create_time']) # type: datetime when_local_date = localtime(when_datetime).date() if 'payer' not in payment: print(payment) return if 'payer_info' not in payment['payer']: print(payment) return who_fname = payment['payer']['payer_info']['first_name'] who_lname = payment['payer']['payer_info']['last_name'] try: who_email = payment['payer']['payer_info']['email'] except: who_email = "" assert len( payment['transactions'] ) == 1, "Code can't yet deal with multiple transactions payment." transaction = payment['transactions'][0] payment_amount = transaction['amount']['total'] if 'custom' in transaction: # Xerocraft.org puts an indication of what was paid for in "custom" what = transaction['custom'] else: what = None resources = transaction['related_resources'] sale_amt = None refund_amt = None if len(resources) == 0: # This has only been observed in a test transaction generated by Kyle print("K", end="") return for resource in resources: if 'sale' in resource: sale = resource['sale'] if sale_amt is not None: print( "WARNING (not handled): Multiple sale resources in sale to " + who_email + " on " + str(when_local_date)) sale_amt = float(sale['amount']['total']) pay_mode = sale['payment_mode'] # e.g. 'INSTANT_TRANSFER' trans_fee = float(sale['transaction_fee']['value']) if 'refund' in resource: refund = resource['refund'] if refund['state'] is "failed": continue if refund_amt is not None: print( "WARNING (not handled): Multiple refunds in sale to " + who_email + " on " + str(when_local_date)) refund_amt = float(refund['amount']['total']) if sale_amt is not None and refund_amt is not None: # NOTE: Refunds will require manual processing. Adjusted transactions should be marked "protected". if sale_amt == refund_amt: print("R", end="") return else: print( "Code doesn't yet deal with partial refunds. Sale was to " + who_email + " on " + str(when_local_date)) return sale = Sale() sale.payment_method = Sale.PAID_BY_PAYPAL sale.payer_email = who_email sale.payer_name = "{} {}".format(who_fname, who_lname) sale.sale_date = when_local_date sale.total_paid_by_customer = Decimal( sale_amt ) # The full amount paid by the person, including payment processing fee IF CUSTOMER PAID IT. sale.processing_fee = Decimal(trans_fee) sale.ctrlid = "{}:{}".format(self.CTRLID_PREFIX, payment_id) django_sale = self.upsert(sale) sale.id = django_sale['id'] if django_sale["protected"] == True: # If the sale is protected then all details are also protected. # Otherwise there's no way to protect a deletion. return if what is None: self._process_unknown_item(sale) elif what.startswith("DON_"): self._process_donation_item(sale, what) elif what.startswith("MSHIP_"): self._process_non_recurring_membership_item(sale, what) else: print("Unkown item: " + what)