def _notify_user(self, patched_product): if self._product_name is not None: try: not_handler = NotificationsHandler() order = Order.objects.get( order_id=patched_product['name'].split('=')[-1]) not_handler.send_product_upgraded_notification( order, order.get_product_contract(unicode(patched_product['id'])), self._product_name) except: # A failure in the email notification is not relevant pass
def _check_renovation_date(self, renovation_date, order, contract): now = datetime.now() timed = renovation_date - now if timed.days < 7: handler = NotificationsHandler() if timed.days < 0: # Notify that the subscription has finished handler.send_payment_required_notification(order, contract) # Set the product as suspended client = InventoryClient() client.suspend_product(contract.product_id) else: # There is less than a week remaining handler.send_near_expiration_notification(order, contract, timed.days)
def end_charging(self, transactions, concept): """ Process the second step of a payment once the customer has approved the charge :param transactions: List of transactions applied including the total price and the related model :param concept: Concept of the charge, it can be initial, renovation, or use """ # Update purchase state if self._order.state == 'pending': self._order.state = 'paid' self._order.save() time_stamp = datetime.now() self._order.pending_payment = {} for transaction in transactions: contract = self._order.get_item_contract(transaction['item']) # Update contracts contract.charges.append({ 'date': time_stamp, 'cost': transaction['price'], 'currency': transaction['currency'], 'concept': concept }) contract.last_charge = time_stamp self.end_processors[concept](contract, transaction) # If the customer has been charged create the CDR cdr_manager = CDRManager(self._order, contract) cdr_manager.generate_cdr(transaction['related_model'], unicode(time_stamp)) self._order.save() # TODO: Improve the rollback in case of unexpected exception try: # Generate the invoice invoice_builder = InvoiceBuilder(self._order) invoice_builder.generate_invoice(transactions, concept) # Send notifications if required handler = NotificationsHandler() if concept == 'initial': # Send customer and provider notifications handler.send_acquired_notification(self._order) for cont in self._order.contracts: handler.send_provider_notification(self._order, cont) elif concept == 'renovation' or concept == 'use': handler.send_renovation_notification(self._order, transactions) except: pass
def _send_notification(self, concept, transactions): # TODO: Improve the rollback in case of unexpected exception try: # Send notifications if required handler = NotificationsHandler() if concept == 'initial': # Send customer and provider notifications handler.send_acquired_notification(self._order) for cont in self._order.contracts: handler.send_provider_notification(self._order, cont) elif concept == 'recurring' or concept == 'usage': handler.send_renovation_notification(self._order, transactions) except: pass
def _check_renovation_date(self, renovation_date, order, contract): now = datetime.utcnow() timed = renovation_date - now if timed.days < 7: handler = NotificationsHandler() if timed.days < 0: # Suspend the access to the service on_product_suspended(order, contract) # Notify that the subscription has finished handler.send_payment_required_notification(order, contract) # Set the product as suspended client = InventoryClient() client.suspend_product(contract.product_id) else: # There is less than a week remaining handler.send_near_expiration_notification( order, contract, timed.days)
def __init__(self, payouts, reports): super().__init__() self.payouts = payouts self.reports = reports self.notifications = NotificationsHandler()
class PayoutWatcher(threading.Thread): def __init__(self, payouts, reports): super().__init__() self.payouts = payouts self.reports = reports self.notifications = NotificationsHandler() def _mark_as_paid(self, report, paid=True): headers = { 'content-type': 'application/json', 'X-Nick-Name': settings.STORE_NAME, 'X-Roles': settings.ADMIN_ROLE, 'X-Email': settings.WSTOREMAIL } data = [{'op': 'replace', 'path': '/paid', 'value': paid}] # Make request url = settings.RSS if not url.endswith('/'): url += '/' url += 'rss/settlement/reports/{}'.format(report) response = requests.patch(url, json=data, headers=headers) if response.status_code != 200: print("Error mark as paid report {}: {}".format( report, response.reason)) return [] return response.json() def _update_status(self, payout): rpayout = ReportsPayout.objects.get( payout_id=payout['batch_header']['payout_batch_id']) rpayout.status = payout['batch_header']['batch_status'] rpayout.save() def _safe_get_semi_paid(self, report_id): try: report = ReportSemiPaid.objects.get(report=report_id) return report except ObjectDoesNotExist: r = ReportSemiPaid(report=report_id) r.save() return r def _safe_get_semi_paid_from_item(self, item): sender_id = item['payout_item']['sender_item_id'] report_id = sender_id.split('_')[0] return self._safe_get_semi_paid(report_id) def _analyze_item(self, item): status = item['transaction_status'] semipaid = self._safe_get_semi_paid_from_item(item) pitem = item['payout_item'] mail = pitem['receiver'] if status != 'SUCCESS': errors = item['errors'] if mail not in semipaid.failed: semipaid.failed.append(mail) if mail in semipaid.success: semipaid.success.remove(mail) semipaid.errors[mail.replace(".", "(dot)")] = { 'error_message': errors['message'], 'error_name': errors['name'], 'item_id': item['payout_item_id'], 'payout_id': item['payout_batch_id'], 'transaction_status': status, 'transaction_id': item['transaction_id'] } semipaid.save() try: # Only send the notification if it's an user error (DENIED and FAILED?) if status in [ 'DENIED', 'PENDING', 'UNCLAIMED', 'RETURNED', 'ONHOLD', 'BLOCKED', 'FAILED' ]: self.notifications.send_payout_error( mail, errors['message']) except: pass return False if mail in semipaid.failed: semipaid.failed.remove(mail) if semipaid.errors.get(mail.replace(".", "(dot)")) is not None: del semipaid.errors[mail.replace(".", "(dot)")] if mail not in semipaid.success: semipaid.success.append(mail) semipaid.save() return True def _check_reports_payout(self, payout): reports_id = { item['payout_item']['sender_item_id'].split('_')[0] for item in payout['items'] } for report_id in reports_id: filtered = list( filter(lambda x: x.get('id') == int(report_id), self.reports)) if len(filtered) == 0: continue report = filtered[0] reportmails = [ User.objects.get(username=report['ownerProviderId']).email ] reportmails.extend([ User.objects.get(username=stake['stakeholderId']).email for stake in report.get('stakeholders', []) ]) semipaid = self._safe_get_semi_paid(report_id) semipaid.failed = [x for x in semipaid.failed if x in reportmails ] # Clean mails not in report if len(semipaid.failed) == 0 and all( [mail in semipaid.success for mail in reportmails]): # Mark as paid in remote self._mark_as_paid(report_id) # Remove semipaid semipaid.delete() else: semipaid.save() def _payout_success(self, payout): for item in payout['items']: self._analyze_item(item) self._check_reports_payout(payout) def _check_payout(self, payout): try: pay = Payout.find(payout['batch_header']['payout_batch_id']) status = pay['batch_header']['batch_status'] self._update_status(pay) if status == 'DENIED': return False if status == 'SUCCESS': self._payout_success(pay) return False if pay['batch_header']['batch_status'] in [ 'PENDING', 'PROCESSING' ]: return True return False except Exception: return False def _check_payouts(self): new = [] for payout in self.payouts: pending = self._check_payout(payout) if pending: # Still pending or processing new.append(payout) self.payouts = new def run(self): while len(self.payouts) != 0: self._check_payouts() time.sleep(1)
def __init__(self, payouts, reports): threading.Thread.__init__(self) self.payouts = payouts self.reports = reports self.notifications = NotificationsHandler()
class PayoutWatcher(threading.Thread): def __init__(self, payouts, reports): threading.Thread.__init__(self) self.payouts = payouts self.reports = reports self.notifications = NotificationsHandler() def _mark_as_paid(self, report, paid=True): headers = { "content-type": "application/json", "X-Nick-Name": settings.STORE_NAME, "X-Roles": "provider", "X-Email": settings.WSTOREMAIL, } data = [{"op": "replace", "path": "/paid", "value": paid}] # Make request url = settings.RSS if not url.endswith("/"): url += "/" url += "rss/settlement/reports/{}".format(report) response = requests.patch(url, json=data, headers=headers) if response.status_code != 200: print("Error mark as paid report {}: {}".format(report, response.reason)) return [] return response.json() def _update_status(self, payout): rpayout = ReportsPayout.objects.get(payout_id=payout["batch_header"]["payout_batch_id"]) rpayout.status = payout["batch_header"]["batch_status"] rpayout.save() def _safe_get_semi_paid(self, report_id): try: report = ReportSemiPaid.objects.get(report=report_id) return report except ObjectDoesNotExist: r = ReportSemiPaid(report=report_id) r.save() return r def _safe_get_semi_paid_from_item(self, item): sender_id = item["payout_item"]["sender_item_id"] report_id = sender_id.split("_")[0] return self._safe_get_semi_paid(report_id) def _analyze_item(self, item): status = item["transaction_status"] semipaid = self._safe_get_semi_paid_from_item(item) pitem = item["payout_item"] mail = pitem["receiver"] if status != "SUCCESS": errors = item["errors"] if mail not in semipaid.failed: semipaid.failed.append(mail) if mail in semipaid.success: semipaid.success.remove(mail) semipaid.errors[mail.replace(".", "(dot)")] = { "error_message": errors["message"], "error_name": errors["name"], "item_id": item["payout_item_id"], "payout_id": item["payout_batch_id"], "transaction_status": status, "transaction_id": item["transaction_id"], } semipaid.save() try: # Only send the notification if it's an user error (DENIED and FAILED?) if status in ["DENIED", "PENDING", "UNCLAIMED", "RETURNED", "ONHOLD", "BLOCKED", "FAILED"]: self.notifications.send_payout_error(mail, errors["message"]) except: pass return False if mail in semipaid.failed: semipaid.failed.remove(mail) if semipaid.errors.get(mail.replace(".", "(dot)")) is not None: del semipaid.errors[mail.replace(".", "(dot)")] if mail not in semipaid.success: semipaid.success.append(mail) semipaid.save() return True def _check_reports_payout(self, payout): reports_id = {item["payout_item"]["sender_item_id"].split("_")[0] for item in payout["items"]} for report_id in reports_id: filtered = list(filter(lambda x: x.get("id") == int(report_id), self.reports)) if len(filtered) == 0: continue report = filtered[0] reportmails = [User.objects.get(username=report["ownerProviderId"]).email] reportmails.extend( [User.objects.get(username=stake["stakeholderId"]).email for stake in report.get("stakeholders", [])] ) semipaid = self._safe_get_semi_paid(report_id) semipaid.failed = [x for x in semipaid.failed if x in reportmails] # Clean mails not in report if len(semipaid.failed) == 0 and all([mail in semipaid.success for mail in reportmails]): # Mark as paid in remote self._mark_as_paid(report_id) # Remove semipaid semipaid.delete() else: semipaid.save() def _payout_success(self, payout): for item in payout["items"]: self._analyze_item(item) self._check_reports_payout(payout) def _check_payout(self, payout): try: pay = Payout.find(payout["batch_header"]["payout_batch_id"]) status = pay["batch_header"]["batch_status"] self._update_status(pay) if status == "DENIED": return False if status == "SUCCESS": self._payout_success(pay) return False if pay["batch_header"]["batch_status"] in ["PENDING", "PROCESSING"]: return True return False except Exception: return False def _check_payouts(self): new = [] for payout in self.payouts: pending = self._check_payout(payout) if pending: # Still pending or processing new.append(payout) self.payouts = new def run(self): while len(self.payouts) != 0: self._check_payouts() time.sleep(1)
def end_charging(self, transactions, concept): """ Process the second step of a payment once the customer has approved the charge :param transactions: List of transactions applied including the total price and the related model :param concept: Concept of the charge, it can be initial, renovation, or use """ # Update purchase state if self._order.state == 'pending': self._order.state = 'paid' self._order.save() time_stamp = datetime.utcnow() self._order.pending_payment = {} invoice_builder = InvoiceBuilder(self._order) billing_client = BillingClient() if concept != 'initial' else None for transaction in transactions: contract = self._order.get_item_contract(transaction['item']) contract.last_charge = time_stamp valid_from, valid_to = self.end_processors[concept](contract, transaction) # If the customer has been charged create the CDR cdr_manager = CDRManager(self._order, contract) cdr_manager.generate_cdr(transaction['related_model'], time_stamp.isoformat() + 'Z') # Generate the invoice invoice_path = '' try: invoice_path = invoice_builder.generate_invoice(contract, transaction, concept) except: pass # Update contracts charge = Charge( date=time_stamp, cost=transaction['price'], duty_free=transaction['duty_free'], currency=transaction['currency'], concept=concept, invoice=invoice_path ) contract.charges.append(charge) # Send the charge to the billing API to allow user accesses if concept != 'initial': # When the change concept is initial, the product has not been yet created in the inventory billing_client.create_charge(charge, contract.product_id, start_date=valid_from, end_date=valid_to) self._order.save() # TODO: Improve the rollback in case of unexpected exception try: # Send notifications if required handler = NotificationsHandler() if concept == 'initial': # Send customer and provider notifications handler.send_acquired_notification(self._order) for cont in self._order.contracts: handler.send_provider_notification(self._order, cont) elif concept == 'recurring' or concept == 'usage': handler.send_renovation_notification(self._order, transactions) except: pass