def _process_invoices(self, invoices): invoices_to_skip = [ '105756939333', # Jeremy's initial test. '205811359970', # Adrian refunded this. Code to handle refunds is not yet written. ] for invoice in invoices: if invoice.invoice_id in invoices_to_skip: continue paid_status = invoice.payout_status.startswith("Paid") captured_status = invoice.payout_status.startswith("Captured") if not (paid_status or captured_status): # See http://help.2checkout.com/articles/Knowledge_Article/Payout-Status continue new_sale = Sale() new_sale.payer_name = self.payer_name new_sale.payer_email = self.payer_email new_sale.processing_fee = Decimal(invoice.fees_2co) new_sale.sale_date = parse(invoice.date_placed).date() new_sale.payment_method = Sale.PAID_BY_2CO new_sale.method_detail = self.method_detail new_sale.ctrlid = "2CO:"+invoice.invoice_id new_sale.total_paid_by_customer = invoice.customer_total if invoice.payout_status.startswith("Paid "): depdate = invoice.payout_status[5:] new_sale.deposit_date = parse(depdate).date() djgo_sale = self.upsert(new_sale) self._process_lineitems(djgo_sale, invoice.lineitems)
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_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_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_subscription_charges(self, charges, subscription, family): for charge in charges: if not charge["state"].startswith("captured"): if not charge["state"] == "failed": print(charge["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_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_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']) 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.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_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)