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)
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
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
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
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