Пример #1
0
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)
Пример #2
0
    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'])
Пример #3
0
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))
Пример #4
0
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))
Пример #6
0
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))
Пример #8
0
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
Пример #9
0
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
Пример #10
0
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'],
    })
Пример #11
0
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
Пример #12
0
    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
Пример #13
0
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
Пример #14
0
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)
Пример #15
0
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
Пример #16
0
    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.')
Пример #17
0
    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()
Пример #18
0
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
Пример #19
0
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
                  })
Пример #20
0
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
Пример #21
0
    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.')
Пример #22
0
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'])