def retrieve_funding_sources(account): if not account.has_dwolla_customer: return None client = get_dwolla_client() funding_sources = client.get('customers/%s/funding-sources' % account.dwolla_account_id) for source in funding_sources.body['_embedded']['funding-sources']: if source['type'] == 'bank' and not source['removed']: status = source['status'] if 'initiate-micro-deposits' in source['_links']: client.post(source['initiate-micro-deposits']) if 'verify-micro-deposits' in source['_links']: status = 'verify-micro-deposits' account.update(funding_id=source['id'], funding_source_name=source['name'], funding_status=status) break else: # There are no funding sources account.update(funding_id=None, funding_source_name=None)
def save(self): client = get_dwolla_client() amount1 = Decimal(self.validated_data['amount_1']) amount2 = Decimal(self.validated_data['amount_2']) payload = { "amount1": { "value": str('{0:.2f}'.format(amount1)), "currency": "USD" }, "amount2": { "value": str('{0:.2f}'.format(amount2)), "currency": "USD" } } try: microdeposits = client.post( 'funding-sources/%s/micro-deposits' % str(self.user.funding_id), payload) except dwolla.ValidationError: raise serializers.ValidationError( 'The amount you provided is not correct.') except dwolla.InvalidResourceStateError: raise serializers.ValidationError( 'Your deposit could not be found, please contact support.') funding_response = client.get('funding-sources/%s' % self.user.funding_id) self.user.update(funding_status=funding_response.body['status'])
def create_transfer(transaction): seller = transaction.deal.seller buyer = transaction.deal.buyer # retrieve_funding_sources(buyer) if not (seller.has_dwolla_customer and buyer.has_dwolla_funding_src): raise TransactionCreationException( "Seller or buyer for transaction {} missing Dwolla credentials". format(transaction.id)) seller_endpoint = '{}/customers/{}'.format(settings.DWOLLA_BASE_URL, seller.dwolla_account_id) payload = { '_links': { 'source': { 'href': '{}/funding-sources/{}'.format(settings.DWOLLA_BASE_URL, buyer.funding_id), }, 'destination': { 'href': seller_endpoint, }, }, 'fees': [ { '_links': { 'charge-to': { 'href': seller_endpoint, } }, 'amount': { 'value': str('{0:.2f}'.format(transaction.fee)), 'currency': 'USD', }, }, ], 'amount': { 'currency': 'USD', 'value': str('{0:.2f}'.format(transaction.paid_to_seller)), }, } try: client = get_dwolla_client() transfer = client.post('transfers', payload) if transfer.status != status.HTTP_201_CREATED: raise TransactionCreationException( "Transaction {} not created by Dwolla".format(transaction.id)) transaction.update( status='pending', dwolla_transaction_id=transfer.headers['location'].split('/')[-1]) except dwolla.BadRequestError: raise TransactionCreationException('Invalid request parameters.') except dwolla.ValidationError: raise TransactionCreationException(json.dumps(payload))
def delete_webhook_subscription(subscriptions): """Delete a Dwolla webhook subscription""" client = get_dwolla_client() for subscription in subscriptions: webhook_subscription = client.delete('webhook-subscriptions/' + str(subscription)) print(json.dumps(webhook_subscription.body))
def list_webhook_subscriptions(): """List Dwolla webhook subscriptions""" client = get_dwolla_client() subscriptions = client.get('webhook-subscriptions') for subscription in subscriptions.body['_embedded'][ 'webhook-subscriptions']: print(json.dumps(subscription))
def fetch_resource_links(webhook): client = get_dwolla_client() resources = {} # For each link provided in the API response, we go fetch the link resource. for name, data in webhook['_links'].items(): resource = client.get(webhook['_links'][name]['href']) resources[name.replace('-', '_')] = clean_resource(resource.body) return resources
def create_webhook_subscription(): """Create a new Dwolla webhook subscription""" webhook_url = settings.BASE_URL + reverse('rest_framework:webhook_dwolla') print('Creating webhook at: ' + webhook_url) client = get_dwolla_client() webhook_subscription = client.post('webhook-subscriptions', { 'url': webhook_url, 'secret': settings.DWOLLA_WEBHOOK_SECRET }) print(json.dumps(webhook_subscription.body))
def get_certify_status(account): if account.has_dwolla_customer: try: client = get_dwolla_client() r = client.get('customers/%s/beneficial-ownership' % account.dwolla_account_id) account.update(certification_status=r.body['status']) return r.body['status'] except Exception as e: pass return account.certification_status
def certify_ownership(account): if account.has_dwolla_customer: try: client = get_dwolla_client() r = client.post( 'customers/%s/beneficial-ownership' % account.dwolla_account_id, {'status': 'certified'}) account.update(certification_status=r.body['status']) return r.body['status'] except Exception as e: pass return None
def get_iav_token(request): if not request.user.dwolla_account_id: raise Exception('Unable to get token') client = get_dwolla_client() response = client.post('customers/%s/iav-token' % request.user.dwolla_account_id) if 'token' not in response.body: raise Exception('Unable to get token') return JSONResponse({ "token": response.body['token'], })
def retrieve_transfer(transfer_id): """ :param transfer_id: dwolla transfer id :function: retrieve a transfer from dwolla :return: transfer status or None """ try: client = get_dwolla_client() r_transfer = client.get('transfers/{}'.format(transfer_id)) return r_transfer.body['status'] # e.g: 'pending' except Exception as e: return None
def create(self, validated_data): if validated_data.get('dwolla_customer_type', None) == 'true': validated_data['dwolla_customer_type'] = 'business' elif validated_data.get('dwolla_customer_type', None) == 'false': validated_data['dwolla_customer_type'] = 'personal' else: validated_data['dwolla_customer_type'] = 'unverified' geocode_data = validated_data.pop('geocode_data') try: load_zone = get_loadzone(geocode_data['lat'], geocode_data['lng']) except Exception as e: load_zone = '' validated_data['load_zone'] = load_zone password = validated_data.pop('password') email = validated_data.pop('email') user = Account.objects.create_user(email, password, **validated_data) try: client = get_dwolla_client() customer = client.post( 'customers', { 'firstName': user.get_first_name(), 'lastName': user.get_last_name(), 'email': email }) customer = client.get(customer.headers['location']) user.dwolla_account_id = customer.body['id'] except (dwolla.NotFoundError, dwolla.ValidationError) as e: raise serializers.ValidationError( 'Cannot create Dwolla customer profile.') user.is_active = True # need this in order to access buyer portal (despite model default) user.save() log_action(action='signup', ip_address=self.request.META['REMOTE_ADDR'], user=user) try: send_email_confirmation(self.request, user, signup=True) except OSError: pass return user
def cancel_transfer(transfer_id): """ :param transfer_id: dwolla transfer id :function: cancel a transfer from dwolla :return: transfer status or None """ try: client = get_dwolla_client() r_transfer = client.post('transfers/{}'.format(transfer_id), {"status": "cancelled"}) return r_transfer.body['status'] # expected: 'cancelled' except Exception as e: return None
def get_business_classification(request): client = get_dwolla_client() business_classifications = client.get('business-classifications') classifications = [] for group in business_classifications.body['_embedded'][ 'business-classifications']: for cls in group['_embedded']['industry-classifications']: classifications.append({ 'name': cls['name'], 'id': cls['id'], 'category': group['name'] }) return JSONResponse(classifications)
def retrieve_balance(funding_id): """ :param funding_id: :function: get balance for seller from dwolla :return: balance(decimal) or None """ try: client = get_dwolla_client() funding_source = client.get( 'funding-sources/{}/balance'.format(funding_id)) balance = Decimal(funding_source['body']['balance']['value']) return balance except Exception as e: return None
def save(self): client = get_dwolla_client() date_of_birth = self.validated_data['beneficial_date_of_birth'][:10] payload = { 'firstName': self.validated_data['beneficial_first_name'], 'lastName': self.validated_data['beneficial_last_name'], 'dateOfBirth': date_of_birth, 'ssn': self.validated_data['beneficial_ssn'], 'address': { 'address1': self.validated_data['beneficial_address'], 'city': self.validated_data['beneficial_city'], 'stateProvinceRegion': self.validated_data['beneficial_state'], 'postalCode': self.validated_data['beneficial_zip_code'], 'country': 'US', } } try: r = client.post( 'customers/%s/beneficial-owners' % self.user.dwolla_account_id, payload) beneficial_owner = client.get(r.headers['location']) BeneficialOwner.objects.create( seller=self.user, beneficial_first_name=self. validated_data['beneficial_first_name'], beneficial_last_name=self. validated_data['beneficial_last_name'], beneficial_date_of_birth=datetime.datetime.strptime( date_of_birth, '%Y-%m-%d'), beneficial_ssn=self.validated_data['beneficial_ssn'], beneficial_address=self.validated_data['beneficial_address'], beneficial_city=self.validated_data['beneficial_city'], beneficial_state=self.validated_data['beneficial_state'], beneficial_country='US', beneficial_zip_code=self.validated_data['beneficial_zip_code'], verification_status=beneficial_owner. body['verificationStatus'], beneficial_owner_id=beneficial_owner.body['id']) except Exception as e: logger.error(e) raise serializers.ValidationError( 'Cannot create beneficial owner.')
def save(self): client = get_dwolla_client() file = self.validated_data['file'] document = client.post( 'customers/%s/documents' % str(self.user.dwolla_account_id), file=(file.name, open(file.temporary_file_path(), 'rb'), file.content_type), documentType=self.validated_data['document_type']) verification_document = VerificationDocument() verification_document.user = self.user verification_document.document_type = self.validated_data[ 'document_type'] verification_document.document_id = document.headers['location'] verification_document.save()
def remove_funding_source(funding_id): """ :param funding_id: :function: remove a dwolla funding source by id :return: True/False """ try: client = get_dwolla_client() funding_source = client.post('funding-sources/{}'.format(funding_id), {"removed": True}) # Resource already removed except dwolla.InvalidResourceStateError: pass # Resource already removed except dwolla.NotFoundError: pass
def index(request): """Index page.""" current_iav_token = None user_data = None if request.user and request.user.is_authenticated(): if request.user.has_dwolla_customer: client = get_dwolla_client() try: token_response = client.post('customers/%s/iav-token' % request.user.dwolla_account_id) current_iav_token = token_response.body['token'] except AttributeError: pass except dwolla.InvalidResourceStateError: pass user_data = json.dumps(get_profile(request.user)) config = { 'dwollaOauthUrl': '', 'dwollaLoginOauthUrl': '', 'utilityAPIPortalUrl': settings.UTILITY_API_PORTAL, 'googleMapsApiKey': settings.GOOGLE_API_KEY, 'recaptcha_sitekey': settings.GOOGLE_RECAPTCHA_SITE_KEY, 'useFake': settings.USE_FAKE, 'googleAnalyticsTrackingId': settings.GOOGLE_ANALYTICS_TRACKING_ID, 'googleAnalyticsExperimentKey': settings.GOOGLE_ANALYTICS_EXPERIMENT_KEY, 'currentIavToken': current_iav_token, } config = json.dumps(config) return render(request, 'index.html', context={ 'config': config, 'user_data': user_data, 'ga_experiment_key': settings.GOOGLE_ANALYTICS_EXPERIMENT_KEY })
def get_profile(user): current_deals = None current_status = None funding_status = None try: retrieve_funding_sources(user) except DwollaAccessDeniedError: pass serializer = AccountSerializer(user) user_data = serializer.data if user_data['role'] == AccountRole.BUYER: current_deals = Deal.objects.filter(status=DealStatus.ACTIVE, buyer_id=user_data['id']) community_solar_deals = Deal.objects.filter( buyer_id=user_data['id'], seller__role=AccountRole.COMMUNITY_SOLAR, status__in=[DealStatus.ACTIVE, DealStatus.PENDING_SELLER]) if community_solar_deals: deal = community_solar_deals.first() if deal.seller.role == AccountRole.COMMUNITY_SOLAR: user_data['used_code'] = True user_data['purchased_quantity'] = current_deals.aggregate( quantity=Sum('quantity'))['quantity'] if user_data['purchased_quantity'] is None: user_data['purchased_quantity'] = 0 # calculate total savings aggregated_values = Transaction.objects.filter( status=TransactionStatus.PROCESSED, deal__in=current_deals).aggregate( total_bill_credit=Sum('bill_transfer_amount'), total_paid=Sum('paid_to_seller')) if aggregated_values[ 'total_bill_credit'] is not None and aggregated_values[ 'total_paid'] is not None: user_data['savings'] = aggregated_values[ 'total_bill_credit'] - aggregated_values['total_paid'] else: user_data['savings'] = 0 if user_data['role'] == AccountRole.SELLER: current_deals = Deal.objects.filter(status=DealStatus.ACTIVE, seller_id=user_data['id']) pending_deals = Deal.objects.filter(status=DealStatus.PENDING_SELLER, seller_id=user_data['id']) user_data['pending_deals'] = pending_deals.count() user_data['active_deals'] = current_deals.count() user_data['sold_quantity'] = current_deals.aggregate( quantity=Sum('quantity'))['quantity'] if user_data['sold_quantity'] is None: user_data['sold_quantity'] = 0 user_data[ 'left'] = user_data['credit_to_sell'] - user_data['sold_quantity'] user_data['solar_complete'] = bool(user.credit_to_sell_percent) user_data['earnings'] = Transaction.objects.filter( deal__seller_id=user_data['id']).aggregate( earnings=Sum('paid_to_seller'))['earnings'] or 0 if user.dwolla_customer_type == 'business': user_data['beneficial_count'] = user.beneficial_owners.count() if current_deals is not None: user_data['buyer_zipcodes'] = [] user_data['seller_zipcodes'] = [] for deal in current_deals: user_data['buyer_zipcodes'].append(deal.buyer.zip_code) user_data['seller_zipcodes'].append(deal.seller.zip_code) if user_data['role'] == AccountRole.COMMUNITY_SOLAR: user_data['module_types'] = ModuleType.get_list() user_data['complete'] = user.has_complete_profile user_data['loadzone_error'] = user.load_zone == '' user_data['has_utility_access'] = user.has_utility_access if user.has_dwolla_customer: client = get_dwolla_client() try: customer_response = client.get('customers/%s' % user.dwolla_account_id) current_status = customer_response.body['status'] except (AttributeError, KeyError): pass if user.has_dwolla_funding_src: funding_response = client.get('funding-sources/%s' % user.funding_id) funding_status = funding_response.body['status'] if 'initiate-micro-deposits' in funding_response.body['_links']: client.post(funding_response.body['initiate-micro-deposits']) if 'verify-micro-deposits' in funding_response.body['_links']: funding_status = 'verify-micro-deposits' user_data['current_status'] = current_status user_data['funding_status'] = funding_status return user_data
def save(self): client = get_dwolla_client() user_data = { 'firstName': self.user.get_first_name(), 'lastName': self.user.get_last_name(), 'email': self.user.email, 'address1': self.user.address, 'city': self.user.city, 'state': self.user.state, 'postalCode': self.user.zip_code } date_of_birth = self.validated_data[ 'controller_date_of_birth'][: 10] # e.g: # 2018-09-02T16:00:00.000Z -> 2018-09-02 # upgrade unverified to 'personal' verified if self.user.dwolla_customer_type == 'personal': user_data.update({ 'ssn': self.validated_data['controller_ssn'], 'type': 'personal', 'dateOfBirth': date_of_birth }) # upgrade unverified to 'business' verified elif self.user.dwolla_customer_type == 'business': user_data.update({ 'type': 'business', 'controller': { 'firstName': self.validated_data['controller_first_name'], 'lastName': self.validated_data['controller_last_name'], 'title': self.validated_data['controller_job_title'], 'dateOfBirth': date_of_birth, 'ssn': self.validated_data['controller_ssn'], 'address': { 'address1': self.validated_data['controller_address'], 'city': self.validated_data['controller_city'], 'stateProvinceRegion': self.validated_data['controller_state'], 'postalCode': self.validated_data['controller_zip_code'], 'country': 'US', } }, 'businessClassification': self.validated_data['business_classification'], 'businessType': self.validated_data['business_type'], 'businessName': self.validated_data['business_name'], 'ein': self.validated_data['ein'], }) try: business_information = self.user.business_information_seller except SellerBusinessInformation.DoesNotExist: business_information = SellerBusinessInformation() business_information.seller = self.user business_information.business_name = self.validated_data[ 'business_name'] business_information.business_type = self.validated_data[ 'business_type'] business_information.business_classification = self.validated_data[ 'business_classification'] business_information.ein = self.validated_data['ein'] business_information.controller_first_name = self.validated_data[ 'controller_first_name'] business_information.controller_last_name = self.validated_data[ 'controller_last_name'] business_information.controller_job_title = self.validated_data[ 'controller_job_title'] business_information.controller_date_of_birth = datetime.datetime.strptime( date_of_birth, '%Y-%m-%d') business_information.controller_ssn = self.validated_data[ 'controller_ssn'] business_information.controller_address = self.validated_data[ 'controller_address'] business_information.controller_city = self.validated_data[ 'controller_city'] business_information.controller_state = self.validated_data[ 'controller_state'] business_information.controller_country = 'US' business_information.controller_zip_code = self.validated_data[ 'controller_zip_code'] business_information.save() try: client.post('customers/' + self.user.dwolla_account_id, user_data) except Exception as e: logger.error(e) raise serializers.ValidationError( 'Cannot create verified customer profile.')
def send_email_from_webhook(webhook): try: client = get_dwolla_client() template = webhook['topic'] context_data = fetch_resource_links(webhook) additional_context = { 'today_date': datetime.now().strftime("%m/%d/%Y") } # For a micro-deposit, we are fetching the details. if "_microdeposits_" in webhook['topic']: try: micro_deposit = client.get(webhook['_links']['resource']['href'] + '/micro-deposits') context_data['microdeposit'] = clean_resource(micro_deposit.body) except NotFoundError: print('Race condition. Micro-Deposit does not exist since funding source is already validated...') # If the webhook is relating to transfers, we are pulling extra contextual objects for deeper template metadata. if "_transfer_" in webhook['topic']: additional_context = fetch_resource_links(context_data['resource']) data = merge_two_dicts(context_data, additional_context) # In the case of a customer transfer, we have different templates for sender and receiver. if "customer_transfer_" in webhook['topic']: if data['customer']['id'] == data['destination']['id']: template += "_seller" # In the case of the seller, we need to calculate the fees in the total. amount = Decimal(data['resource']['amount']['value']) fees = Decimal(data['fees']['transactions'][0]['amount']['value']) total = amount - fees data['net_total'] = "${:.2f}".format(total) else: template += "_buyer" # handle_beneficial_owner_webhook if 'customer_beneficial_owner_' in webhook['topic']: try: beneficial_owner = BeneficialOwner.objects.get(beneficial_owner_id=webhook['resourceId']) if webhook['topic'] == 'customer_beneficial_owner_verified': beneficial_owner.verification_status = 'verified' beneficial_owner.save() elif webhook['topic'] == 'customer_beneficial_owner_verification_document_needed': beneficial_owner.verification_status = 'document' beneficial_owner.save() except Exception as e: logger.error(e) context = Context(data) plaintext = get_template('email/webhooks/' + template + '.txt') html = get_template('email/webhooks/' + template + '.html') subject = email_subject_map[webhook['topic']] if webhook['topic'] in email_subject_map else 'Account event' text_content = plaintext.render(context) html_content = html.render(context) msg = EmailMultiAlternatives( subject, text_content, settings.DEFAULT_FROM_EMAIL, [data['customer']['email']] ) msg.attach_alternative(html_content, 'text/html') msg.send() except TemplateDoesNotExist: print('Skipping webhook processing since no template exists for: %s' % webhook['topic'])