def commit_scraptf_donation(self, donation):
        '''
        From a ScrapTF donation, create a Donor and Donation. If
        incentives are present, create all DonationBids and
        BidSuggestions as well. We try very hard to ensure everything
        is valid for insertion. If anything doesn't look right, we
        fail the entire transaction.
        '''

        # Create the Donor
        user = donation['user']
        email = donation.get('email')
        if not email:
            raise Exception('Donation is missing an email.')
        validate_email(email)
        alias = user.get('name', '')[:DONOR_ALIAS_MAX_LENGTH]

        donor = Donor(email=email, alias=alias, visibility='ALIAS')

        # The only unique constraint on Donors is paypalemail, which is
        # not a concern here, so forget about IntegrityError.
        donor.full_clean()
        donor.save()

        # Create the Donation
        domain_id = donation['id']
        amount = Decimal(str(donation['cash_value']))
        time = datetime.fromtimestamp(donation['confirmed_time'], tz=pytz.utc)
        comment = donation.get('message')
        commentstate = 'PENDING' if comment else 'ABSENT'
        steamid = user.get('steamid')

        d = Donation(donor=donor,
                     domain='SCRAPTF',
                     domainId=domain_id,
                     transactionstate='COMPLETED',
                     amount=amount,
                     timereceived=time,
                     currency='USD',
                     comment=comment,
                     commentstate=commentstate,
                     requestedalias=alias,
                     requestedemail=email,
                     steamid=steamid)

        try:
            d.full_clean()
            d.save()
        except IntegrityError:
            raise Exception('Donation already exists')

        # Only proceed if incentives are present
        incentives = donation.get('incentives')
        if not incentives:
            return
        log.info('Donation has incentives, creating them now.',
                 incentives_total=len(incentives))

        # Ensure the incentives don't exceed the total donation amount
        inc_amounts = [Decimal(inc['amount']) for inc in incentives]
        inc_total = sum(inc_amounts)
        if inc_total > amount:
            raise Exception('sum of incentive amounts exceeds donation amount')

        for inc in incentives:
            # First grab the bid to make sure it exists
            bid = Bid.objects.get(pk=inc['incentive'])

            dbid = DonationBid(donation=d,
                               bid=bid,
                               amount=Decimal(inc['amount']))
            dbid.full_clean()
            dbid.save()

            # If the incentive has a custom value, create a BidSuggestion
            suggestion = inc.get('custom')
            if suggestion:
                s = BidSuggestion(bid=bid, name=suggestion)
                s.full_clean()
                s.save()

        eventutil.post_donation_to_postbacks(d)
Exemplo n.º 2
0
def generate_donation(
    rand,
    *,
    commentstate='APPROVED',
    donor=None,
    donors=None,
    no_donor=False,
    domain=None,
    event=None,
    min_amount=Decimal('0.01'),
    max_amount=Decimal('1000.00'),
    min_time=None,
    max_time=None,
    readstate='READ',
    transactionstate=None,
):
    donation = Donation()
    donation.amount = random_amount(rand,
                                    min_amount=min_amount,
                                    max_amount=max_amount)
    if not event:
        event = pick_random_instance(rand, Event)
    assert event, 'No event provided and none exist'
    donation.event = event
    if domain:
        donation.domain = domain
    else:
        donation.domain = rand.choice(DonationDomainChoices)[0]
    donation.domainId = str(rand.getrandbits(64))
    donation.fee = (donation.amount * Decimal(0.03)).quantize(
        Decimal('0.01'), rounding=decimal.ROUND_UP)
    donation.comment = random_name(rand, 'Comment')
    donation.commentstate = commentstate
    donation.readstate = readstate
    if not min_time:
        min_time = event.datetime
    if not max_time:
        max_time = min_time + datetime.timedelta(seconds=60 * 60 * 24 * 14)
    donation.timereceived = random_time(rand, min_time, max_time)
    donation.currency = 'USD'
    donation.transactionstate = transactionstate or 'COMPLETED'
    if donation.domain == 'LOCAL':
        assert (donation.transactionstate == 'COMPLETED'
                ), 'Local donations must be specified as COMPLETED'

    if not no_donor:
        if not donor:
            if donors:
                donor = rand.choice(donors)
            else:
                donor = pick_random_instance(rand, Donor)
        assert donor, 'No donor provided and none exist'
        donation.donor = donor
    donation.clean()
    return donation
Exemplo n.º 3
0
def generate_donation(
    rand,
    *,
    donor=None,
    domain=None,
    event=None,
    min_amount=Decimal('0.01'),
    max_amount=Decimal('1000.00'),
    min_time=None,
    max_time=None,
    donors=None,
    transactionstate=None,
):
    donation = Donation()
    donation.amount = random_amount(rand,
                                    min_amount=min_amount,
                                    max_amount=max_amount)
    if event:
        donation.event = event
    else:
        donation.event = pick_random_instance(rand, Event)
    if domain:
        donation.domain = domain
    else:
        donation.domain = pick_random_element(rand, DonationDomainChoices)[0]
    donation.domainId = str(rand.getrandbits(64))
    donation.fee = (donation.amount * Decimal(0.03)).quantize(
        Decimal('0.01'), rounding=decimal.ROUND_UP)
    donation.comment = random_name(rand, 'Comment')
    donation.commentstate = 'APPROVED'
    donation.readstate = 'READ'
    if not min_time:
        min_time = event.datetime
    if not max_time:
        max_time = min_time + datetime.timedelta(seconds=60 * 60 * 24 * 14)
    donation.timereceived = random_time(rand, min_time, max_time)
    donation.currency = 'USD'
    donation.transactionstate = transactionstate or 'COMPLETED'
    if donation.domain == 'LOCAL':
        assert donation.transactionstate == 'COMPLETED'

    if not donor:
        if donors:
            donor = pick_random_element(rand, donors)
        else:
            donor = pick_random_instance(rand, Donor)
    if not donor:  # no provided donors at all
        donor = generate_donor(rand)
        donor.save()
    donation.donor = donor
    donation.clean()
    return donation
Exemplo n.º 4
0
def initialize_paypal_donation(ipnObj):
    countrycode = (
        ipnObj.residence_country
        if not ipnObj.address_country_code
        else ipnObj.address_country_code
    )
    defaults = {
        'email': ipnObj.payer_email.lower(),
        'firstname': ipnObj.first_name,
        'lastname': ipnObj.last_name,
        'addressstreet': ipnObj.address_street,
        'addresscity': ipnObj.address_city,
        'addresscountry': Country.objects.get(alpha2=countrycode),
        'addressstate': ipnObj.address_state,
        'addresszip': ipnObj.address_zip,
        'visibility': 'ANON',
    }
    donor, created = Donor.objects.get_or_create(
        paypalemail=ipnObj.payer_email.lower(), defaults=defaults
    )

    fill_donor_address(donor, ipnObj)

    donation = get_ipn_donation(ipnObj)

    if donation:
        if donation.requestedvisibility != 'CURR':
            donor.visibility = donation.requestedvisibility
        if donation.requestedalias and (
            not donor.alias or donation.requestedalias.lower() != donor.alias.lower()
        ):
            foundAResult = False
            currentAlias = donation.requestedalias
            while not foundAResult:
                results = Donor.objects.filter(alias__iexact=currentAlias)
                if results.exists():
                    currentAlias = donation.requestedalias + str(random.getrandbits(8))
                else:
                    foundAResult = True
            donor.alias = currentAlias
        if (
            donation.requestedemail
            and donation.requestedemail != donor.email
            and not Donor.objects.filter(email=donation.requestedemail).exists()
        ):
            donor.email = donation.requestedemail
        if donation.requestedsolicitemail != 'CURR':
            donor.solicitemail = donation.requestedsolicitemail
        donor.save()
    else:
        donation = Donation()
        donation.modcomment = '*Donation for ipn was not found, creating new*'
        donation.event = Event.objects.latest()

    donation.domain = 'PAYPAL'
    donation.domainId = ipnObj.txn_id
    donation.donor = donor
    donation.amount = Decimal(ipnObj.mc_gross)
    donation.currency = ipnObj.mc_currency
    if not donation.timereceived:
        donation.timereceived = datetime.utcnow()
    donation.testdonation = ipnObj.test_ipn
    donation.fee = Decimal(ipnObj.mc_fee or 0)

    # if the user attempted to tamper with the donation amount, remove all bids
    if donation.amount != ipnObj.mc_gross:
        donation.modcomment += (
            '\n*Tampered donation amount from '
            + str(donation.amount)
            + ' to '
            + str(ipnObj.mc_gross)
            + ', removed all bids*'
        )
        donation.amount = ipnObj.mc_gross
        donation.bids.clear()
        viewutil.tracker_log(
            'paypal',
            'Tampered amount detected in donation {0} (${1} -> ${2})'.format(
                donation.id, donation.amount, ipnObj.mc_gross
            ),
            event=donation.event,
        )

    paymentStatus = ipnObj.payment_status.lower()

    if not ipnObj.flag:
        if paymentStatus == 'pending':
            donation.transactionstate = 'PENDING'
        elif (
            paymentStatus == 'completed'
            or paymentStatus == 'canceled_reversal'
            or paymentStatus == 'processed'
        ):
            donation.transactionstate = 'COMPLETED'
        elif (
            paymentStatus == 'refunded'
            or paymentStatus == 'reversed'
            or paymentStatus == 'failed'
            or paymentStatus == 'voided'
            or paymentStatus == 'denied'
        ):
            donation.transactionstate = 'CANCELLED'
        else:
            donation.transactionstate = 'FLAGGED'
            viewutil.tracker_log(
                'paypal',
                'Unknown payment status in donation {0} ({1})'.format(
                    donation.id, paymentStatus
                ),
                event=donation.event,
            )
    else:
        donation.transactionstate = 'FLAGGED'
        viewutil.tracker_log(
            'paypal',
            'IPN object flagged for donation {0} ({1})'.format(
                donation.id, ipnObj.txn_id
            ),
            event=donation.event,
        )

    # Automatically approve anonymous, no-comment donations if an auto-approve
    # threshold is set.
    auto_min = donation.event.auto_approve_threshold
    if auto_min:
        donation.approve_if_anonymous_and_no_comment(auto_min)

    donation.save()
    # I think we only care if the _donation_ was freshly created
    return donation
Exemplo n.º 5
0
def sync_event_donations(event):
    """Sync donations from a Tiltify campaign with an event in our system.

    :param event: Event record to merge.
    :type event: tracker.models.Event
    :return: Number of donations updated.
    :rtype: int
    """
    if not event.tiltify_api_key:
        raise ValidationError("API key not set")

    # Get campaign data to update start date for event.
    user = get_user_data()
    t_campaign = get_campaign_data(event.tiltify_api_key, user)

    start = datetime.datetime.fromtimestamp(t_campaign['startsAt'] / 1000,
                                            datetime.timezone.utc)
    if start:
        event.datetime = start

    event.save()

    # Get donations from Tiltify API.
    t_donations = get_donation_data(t_campaign)
    num_donations = 0

    for t_donation in t_donations:
        # Get donor based on alias.
        donor = None
        if t_donation['name'] and t_donation['name'] != 'Anonymous':
            try:
                donor = Donor.objects.get(alias__iexact=t_donation['name'])
            except Donor.DoesNotExist:
                donor = Donor(email=t_donation['name'],
                              alias=t_donation['name'])
                donor.save()

        # Get donation based on payment reference.
        try:
            donation = Donation.objects.select_for_update().get(
                domain='TILTIFY', domainId=t_donation['id'])
        except Donation.DoesNotExist:
            donation = Donation(event=event,
                                domain='TILTIFY',
                                domainId=t_donation['id'],
                                readstate='PENDING',
                                commentstate='PENDING',
                                donor=donor)

        # Make sure this donation wasn't already imported for a different event.
        if donation.event != event:
            raise ValidationError(
                "Donation {!r} already exists for a different event".format(
                    donation.domainId))

        donation.transactionstate = 'COMPLETED'
        donation.amount = t_donation['amount']
        donation.currency = event.paypalcurrency
        donation.timereceived = datetime.datetime.fromtimestamp(
            t_donation['completedAt'] / 1000, datetime.timezone.utc)
        donation.testdonation = event.usepaypalsandbox

        # Comment might be null from Tiltify, but can't be null on our end.
        if t_donation['comment']:
            donation.comment = t_donation['comment']
        else:
            donation.comment = ''

        donation.save()
        num_donations += 1

    return num_donations
    def commit_scraptf_donation(self, donation):
        '''
        From a ScrapTF donation, create a Donor and Donation. If
        incentives are present, create all DonationBids and
        BidSuggestions as well. We try very hard to ensure everything
        is valid for insertion. If anything doesn't look right, we
        fail the entire transaction.
        '''

        # Create the Donor
        user = donation['user']
        email = donation.get('email')
        if not email:
            raise Exception('Donation is missing an email.')
        validate_email(email)
        alias = user.get('name', '')[:DONOR_ALIAS_MAX_LENGTH]

        donor = Donor(email=email,
                      alias=alias,
                      visibility='ALIAS')

        # The only unique constraint on Donors is paypalemail, which is
        # not a concern here, so forget about IntegrityError.
        donor.full_clean()
        donor.save()

        # Create the Donation
        domain_id = donation['id']
        amount = Decimal(str(donation['cash_value']))
        time = datetime.fromtimestamp(donation['confirmed_time'], tz=pytz.utc)
        comment = donation.get('message')
        commentstate = 'PENDING' if comment else 'ABSENT'
        steamid = user.get('steamid')

        d = Donation(donor=donor,
                     domain='SCRAPTF',
                     domainId=domain_id,
                     transactionstate='COMPLETED',
                     amount=amount,
                     timereceived=time,
                     currency='USD',
                     comment=comment,
                     commentstate=commentstate,
                     requestedalias=alias,
                     requestedemail=email,
                     steamid=steamid)

        try:
            d.full_clean()
            d.save()
        except IntegrityError:
            raise Exception('Donation already exists')

        # Only proceed if incentives are present
        incentives = donation.get('incentives')
        if not incentives:
            return
        log.info('Donation has incentives, creating them now.',
                 incentives_total=len(incentives))

        # Ensure the incentives don't exceed the total donation amount
        inc_amounts = [Decimal(inc['amount']) for inc in incentives]
        inc_total = sum(inc_amounts)
        if inc_total > amount:
            raise Exception('sum of incentive amounts exceeds donation amount')

        for inc in incentives:
            # First grab the bid to make sure it exists
            bid = Bid.objects.get(pk=inc['incentive'])

            dbid = DonationBid(donation=d,
                               bid=bid,
                               amount=Decimal(inc['amount']))
            dbid.full_clean()
            dbid.save()

            # If the incentive has a custom value, create a BidSuggestion
            suggestion = inc.get('custom')
            if suggestion:
                s = BidSuggestion(bid=bid,
                                  name=suggestion)
                s.full_clean()
                s.save()

        eventutil.post_donation_to_postbacks(d)