def _handle_failed_tip_sub(body): sub = _get_tip_subscription(body) if not sub: return {'warning': 'subscription unrecognized'} closed = body['data']['object']['closed'] pod = sub.podcast if closed: sub.deactivated = True sub.save() send_notification_email( None, ugettext('Your subscription to %s was cancelled') % pod.name, ugettext('We attempted to charge your card for your ' 'subscription to %s, but the payment failed multiple ' 'times. If you wish to remain subscribed, please ' 'visit the link below to enter new payment ' 'information.\n\n%s') % (pod.name, BASE_URL + reverse('tip_jar', podcast_slug=pod.slug)), email=sub.tipper.email_address) return {'success': 'nastygram sent, subscription deactivated'} else: send_notification_email( None, ugettext('Your subscription to %s has problems') % pod.name, ugettext('We attempted to charge your card for your ' 'subscription to %s, but the payment failed. Please ' 'visit the tip jar and update your subscription with ' 'new card details as soon as possible. You can do that ' 'at the link below.\n\n%s') % (pod.name, BASE_URL + reverse('tip_jar', podcast_slug=pod.slug)), email=sub.tipper.email_address) return {'success': 'nastygram sent'}
def _handle_failed_tip_sub(body): sub = _get_tip_subscription(body) if not sub: return {'warning': 'subscription unrecognized'} closed = body['data']['object']['closed'] pod = sub.podcast if closed: sub.deactivated = True sub.save() send_notification_email( None, ugettext('Your subscription to %s was cancelled') % pod.name, ugettext('We attempted to charge your card for your ' 'subscription to %s, but the payment failed multiple ' 'times. If you wish to remain subscribed, please ' 'visit the link below to enter new payment ' 'information.\n\n%s') % (pod.name, BASE_URL + reverse('tip_jar', podcast_slug=pod.slug)), email=sub.tipper.email_address) return {'success': 'nastygram sent, subscription deactivated'} else: send_notification_email( None, ugettext('Your subscription to %s has problems') % pod.name, ugettext('We attempted to charge your card for your ' 'subscription to %s, but the payment failed. Please ' 'visit the tip jar and update your subscription with ' 'new card details as soon as possible. You can do that ' 'at the link below.\n\n%s') % (pod.name, BASE_URL + reverse('tip_jar', podcast_slug=pod.slug)), email=sub.tipper.email_address) return {'success': 'nastygram sent'}
def _send_one_time_tip(req, podcast, owner_us, amount): email = req.POST.get('email') token = req.POST.get('token') tip_user = TipUser.tip_user_from(email_address=email) application_fee = int(amount * 0.3) if owner_us.plan == PLAN_DEMO else 0 try: stripe_charge = stripe.Charge.create( amount=amount, application_fee=application_fee, currency='usd', description='Tip for %s' % podcast.name, destination=owner_us.stripe_payout_managed_account, source=token, ) except Exception as e: rollbar.report_message('Error when sending tip: %s' % str(e), 'error') return {'error': str(e)} podcast.total_tips += amount podcast.save() tip_event = TipEvent( tipper=tip_user, podcast=podcast, amount=amount, fee_amount=application_fee, stripe_charge=stripe_charge.id) tip_event.save() send_notification_email( None, ugettext('Thanks for leaving a tip!'), ugettext('Your tip was sent: %s received $%0.2f. Thanks for supporting your ' 'favorite content creators!') % (podcast.name, float(amount) / 100), email=email) send_notification_email( podcast.owner, ugettext('Your podcast was tipped!'), ugettext('%s received a tip of $%0.2f from %s. You should send them an email ' 'thanking them for their generosity.') % ( podcast.name, float(amount) / 100, tip_user.email_address)) NotificationHook.trigger_notification( podcast=podcast, trigger_type='tip', data={'tipper': tip_user.email_address, 'amount': amount}) return {'success': True}
def _send_one_time_tip(req, podcast, owner_us, amount): email = req.POST.get('email') token = req.POST.get('token') tip_user = TipUser.tip_user_from(email_address=email) application_fee = int(amount * 0.3) if owner_us.plan == PLAN_DEMO else 0 try: stripe_charge = stripe.Charge.create( amount=amount, application_fee=application_fee, currency='usd', description='Tip for %s' % podcast.name, destination=owner_us.stripe_payout_managed_account, source=token, ) except Exception as e: rollbar.report_message('Error when sending tip: %s' % str(e), 'error') return {'error': str(e)} Podcast.objects.filter(id=podcast.id).update(total_tips=F('total_tips') + amount) tip_event = TipEvent( tipper=tip_user, podcast=podcast, amount=amount, fee_amount=application_fee, stripe_charge=stripe_charge.id) tip_event.save() send_notification_email( None, ugettext('Thanks for leaving a tip!'), ugettext('Your tip was sent: %s received $%0.2f. Thanks for supporting your ' 'favorite content creators!') % (podcast.name, float(amount) / 100), email=email) send_notification_email( podcast.owner, ugettext('Your podcast was tipped!'), ugettext('%s received a tip of $%0.2f from %s. You should send them an email ' 'thanking them for their generosity.') % ( podcast.name, float(amount) / 100, tip_user.email_address)) NotificationHook.trigger_notification( podcast=podcast, trigger_type='tip', data={'tipper': tip_user.email_address, 'amount': amount}) return {'success': True}
def ep_comment_box(req, podcast_slug, episode_id): pod = get_object_or_404(Podcast, slug=podcast_slug) if not UserSettings.user_meets_plan(pod.owner, plans.FEATURE_MIN_COMMENT_BOX): raise Http404() ep = get_object_or_404(PodcastEpisode, podcast=pod, id=episode_id) if not req.POST: return _pmrender(req, 'feedback/comment_episode.html', { 'podcast': pod, 'episode': ep }) try: if not _validate_recaptcha(req): raise Exception('Invalid ReCAPTCHA') ip = analyze.get_request_ip(req) f = Feedback(podcast=pod, episode=ep, sender=req.POST.get('email'), message=req.POST.get('message'), sender_ip=ip) f.save() send_notification_email( pod.owner, ugettext('[Pinecast] You got some feedback!'), 'Go check the Feedback page of %s--an episode on %s--to see what was written.\n\n' 'https://pinecast.com%s' % (ep.title, pod.name, reverse('podcast_episode', podcast_slug=podcast_slug, episode_id=str(ep.id)) + '#tab-feedback')) NotificationHook.trigger_notification(podcast=pod, trigger_type='feedback', data={ 'episode': ep, 'content': req.POST.get('message'), 'sender': req.POST.get('email') }) except Exception: return _pmrender(req, 'feedback/comment_episode.html', { 'podcast': pod, 'episode': ep, 'error': True, 'default': req.POST }) return _pmrender(req, 'feedback/thanks.html', {'podcast': pod})
def ep_comment_box(req, podcast_slug, episode_id): pod = get_object_or_404(Podcast, slug=podcast_slug) if not UserSettings.user_meets_plan(pod.owner, plans.FEATURE_MIN_COMMENT_BOX): raise Http404() ep = get_object_or_404(PodcastEpisode, podcast=pod, id=episode_id) if not req.POST: return _pmrender(req, 'feedback/comment_episode.html', {'podcast': pod, 'episode': ep}) try: if not _validate_recaptcha(req): raise Exception('Invalid ReCAPTCHA') ip = analyze.get_request_ip(req) f = Feedback( podcast=pod, episode=ep, sender=req.POST.get('email'), message=req.POST.get('message'), sender_ip=ip ) f.save() analytics_log.write('feedback', { 'podcast': unicode(pod.id), 'episode': unicode(ep.id), 'profile': { 'email': req.POST.get('email'), 'email_host': req.POST.get('email').split('@')[1], 'ip': ip, 'ua': req.META.get('HTTP_USER_AGENT'), }, }, req=req) send_notification_email( pod.owner, ugettext('[Pinecast] You got some feedback!'), 'Go check the Feedback page of %s--an episode on %s--to see what was written.\n\n' 'https://pinecast.com%s' % (ep.title, pod.name, reverse('podcast_episode', podcast_slug=podcast_slug, episode_id=str(ep.id)) + '#tab-feedback') ) except Exception: return _pmrender(req, 'feedback/comment_episode.html', {'podcast': pod, 'episode': ep, 'error': True, 'default': req.POST}) return _pmrender(req, 'feedback/thanks.html', {'podcast': pod})
def user_settings_page_changepassword(req): if req.POST.get('new_password') != req.POST.get('confirm_password'): return redirect(reverse('dashboard') + '?error=pwc#settings') if not req.user.check_password(req.POST.get('old_password')): return redirect(reverse('dashboard') + '?error=pwo#settings') if len(req.POST.get('new_password')) < 8: return redirect(reverse('dashboard') + '?error=pwl#settings') req.user.set_password(req.POST.get('new_password')) req.user.save() send_notification_email( req.user, ugettext('[Pinecast] Password changed'), ugettext(''' Your Pinecast password has been updated. If you did not request this change, please contact Pinecast support as soon as possible at [email protected]. ''')) return redirect(reverse('login'))
def _handle_failed_subscription(body): customer = body['data']['object']['customer'] try: us = UserSettings.objects.get(stripe_customer_id=customer) except UserSettings.DoesNotExist: rollbar.report_message('Unknown customer: %s' % customer, 'warn') return {'warning': 'customer unrecognized'} closed = body['data']['object']['closed'] user = us.user if closed: us.set_plan(payment_plans.PLAN_DEMO) send_notification_email( user, ugettext('Your Pinecast subscription was cancelled.'), ugettext('Pinecast attempted to charge your payment card multiple ' 'times, but was unable to collect payment. Your ' 'account has been downgraded to a free Demo plan. Only ' 'the ten most recent episodes from each of your podcasts ' 'will be shown to your listeners. All recurring tip ' 'subscriptions to your podcasts have also been ' 'cancelled.\n\nNo content or settings have been deleted ' 'from your account. If you wish to re-subscribe, you may ' 'do so at any time at the URL below.\n\n%s') % (BASE_URL + reverse('upgrade'))) return {'success': 'nastygram sent, account downgraded'} else: send_notification_email( user, ugettext('Payment failed for Pinecast subscription'), ugettext('Pinecast attempted to charge your payment card for your ' 'current subscription, but was unable to collect payment. ' 'If we fail to process your card three times, your ' 'account will automatically be downgraded to a free Demo ' 'plan.\n\n' 'No changes have currently been made to your account or ' 'plan. Please update your payment information at the URL ' 'below.\n\n%s') % (BASE_URL + reverse('dashboard') + '#settings,subscription')) return {'success': 'nastygram sent'}
def user_settings_page_changepassword(req): if req.POST.get('new_password') != req.POST.get('confirm_password'): return redirect(reverse('user_settings') + '?error=pwc') if not req.user.check_password(req.POST.get('old_password')): return redirect(reverse('user_settings') + '?error=pwo') if len(req.POST.get('new_password')) < 8: return redirect(reverse('user_settings') + '?error=pwl') req.user.set_password(req.POST.get('new_password')) req.user.save() send_notification_email( req.user, ugettext('[Pinecast] Password changed'), ugettext(''' Your Pinecast password has been updated. If you did not request this change, please contact Pinecast support as soon as possible at [email protected]. ''') ) return redirect(reverse('login'))
def _handle_failed_subscription(body): customer = body['data']['object']['customer'] try: us = UserSettings.objects.get(stripe_customer_id=customer) except UserSettings.DoesNotExist: rollbar.report_message('Unknown customer: %s' % customer, 'warn') return {'warning': 'customer unrecognized'} closed = body['data']['object']['closed'] user = us.user if closed: us.set_plan(payment_plans.PLAN_DEMO) send_notification_email( user, ugettext('Your Pinecast subscription was cancelled.'), ugettext('Pinecast attempted to charge your payment card multiple ' 'times, but was unable to collect payment. Your ' 'account has been downgraded to a free Demo plan. Only ' 'the ten most recent episodes from each of your podcasts ' 'will be shown to your listeners. All recurring tip ' 'subscriptions to your podcasts have also been ' 'cancelled.\n\nNo content or settings have been deleted ' 'from your account. If you wish to re-subscribe, you may ' 'do so at any time at the URL below.\n\n%s') % (BASE_URL + reverse('upgrade'))) return {'success': 'nastygram sent, account downgraded'} else: send_notification_email( user, ugettext('Payment failed for Pinecast subscription'), ugettext( 'Pinecast attempted to charge your payment card for your ' 'current subscription, but was unable to collect payment. ' 'If we fail to process your card three times, your ' 'account will automatically be downgraded to a free Demo ' 'plan.\n\n' 'No changes have currently been made to your account or ' 'plan. Please update your payment information at the URL ' 'below.\n\n%s') % (BASE_URL + reverse('dashboard') + '#settings,subscription')) return {'success': 'nastygram sent'}
def podcast_comment_box(req, podcast_slug): pod = get_object_or_404(Podcast, slug=podcast_slug) if not UserSettings.user_meets_plan(pod.owner, plans.FEATURE_MIN_COMMENT_BOX): raise Http404() if not req.POST: return _pmrender(req, 'feedback/comment_podcast.html', {'podcast': pod}) try: if not _validate_recaptcha(req): raise Exception('Invalid ReCAPTCHA') ip = analyze.get_request_ip(req) f = Feedback( podcast=pod, sender=req.POST.get('email'), message=req.POST.get('message'), sender_ip=ip ) f.save() send_notification_email( pod.owner, ugettext('[Pinecast] You got some feedback!'), 'Go check the Feedback page of your podcast, %s, to see what was written.\n\n' 'https://pinecast.com%s' % (pod.name, reverse('podcast_dashboard', podcast_slug=podcast_slug) + '#tab-feedback') ) NotificationHook.trigger_notification( podcast=pod, trigger_type='feedback', data={'content': req.POST.get('message'), 'sender': req.POST.get('email')}) except Exception: return _pmrender(req, 'feedback/comment_podcast.html', {'podcast': pod, 'error': True, 'default': req.POST}) return _pmrender(req, 'feedback/thanks.html', {'podcast': pod})
def set_plan(self, new_plan_val, coupon=None): orig_plan = self.plan customer = self.get_stripe_customer() if not customer: return False existing_subs = customer.subscriptions.all(limit=1)['data'] # Handle downgrades to free if new_plan_val == payment_plans.PLAN_DEMO: if existing_subs: existing_sub = existing_subs[0] existing_sub.delete() for podcast in self.user.podcast_set.all(): for tip in podcast.recurring_tips.all(): tip.cancel() self.plan = payment_plans.PLAN_DEMO if self.coupon_code: stripe.Coupon.retrieve(self.coupon_code).delete() self.coupon_code = None self.save() return True plan_stripe_id = payment_plans.STRIPE_PLANS[new_plan_val] was_upgrade = ( payment_plans.PLAN_RANKS[orig_plan] <= payment_plans.PLAN_RANKS[new_plan_val]) if existing_subs: existing_sub = existing_subs[0] existing_sub.plan = plan_stripe_id if was_upgrade and coupon: existing_sub.coupon = coupon try: existing_sub.save() except Exception as e: rollbar.report_message(str(e), 'error') return 'card_error' else: try: customer.subscriptions.create( coupon=coupon, plan=plan_stripe_id) except stripe.error.CardError: return 'card_error' except Exception as e: rollbar.report_message(str(e), 'error') return 'card_error' self.plan = new_plan_val self.save() send_notification_email( self.user, ugettext('Your account has been %s') % (ugettext('upgraded') if was_upgrade else ugettext('downgraded')), ugettext('Your Pinecast account has been updated successfully. ' 'Your account is now marked as "%s".\n\n' 'Please contact Pinecast support if you have any ' 'questions.') % payment_plans.PLANS_MAP[new_plan_val]) return True
def set_plan(self, new_plan_val, coupon=None): orig_plan = self.plan user = self.user customer = self.get_stripe_customer() if not customer: return False was_upgrade = (payment_plans.PLAN_RANKS[orig_plan] <= payment_plans.PLAN_RANKS[new_plan_val]) # Handle pro downgrades if orig_plan == payment_plans.PLAN_PRO and not was_upgrade: from dashboard.models import Collaborator Collaborator.objects.filter( podcast__in=user.podcast_set.all()).delete() existing_subs = customer.subscriptions.all(limit=1)['data'] # Handle downgrades to free if new_plan_val == payment_plans.PLAN_DEMO: if existing_subs: existing_sub = existing_subs[0] existing_sub.delete() for podcast in self.user.podcast_set.all(): for tip in podcast.recurring_tips.all(): tip.cancel() self.plan = payment_plans.PLAN_DEMO if self.coupon_code: stripe.Coupon.retrieve(self.coupon_code).delete() self.coupon_code = None self.save() return True plan_stripe_id = payment_plans.STRIPE_PLANS[new_plan_val] if existing_subs: existing_sub = existing_subs[0] existing_sub.plan = plan_stripe_id if was_upgrade and coupon: existing_sub.coupon = coupon try: existing_sub.save() except Exception as e: rollbar.report_exc_info(sys.exc_info()) return 'card_error' else: try: customer.subscriptions.create(coupon=coupon, plan=plan_stripe_id) except stripe.error.CardError: return 'card_error' except Exception as e: rollbar.report_exc_info(sys.exc_info()) return 'card_error' self.plan = new_plan_val self.save() send_notification_email( user, ugettext('Your account has been %s') % (ugettext('upgraded') if was_upgrade else ugettext('downgraded')), ugettext('Your Pinecast account has been updated successfully. ' 'Your account is now marked as "%s".\n\n' 'Please contact Pinecast support if you have any ' 'questions.') % payment_plans.PLANS_MAP[new_plan_val]) return True
def _finish_sub(req, pod, amount, email, token): owner_us = UserSettings.get_from_user(pod.owner) if (not owner_us.stripe_payout_managed_account or owner_us.plan == PLAN_DEMO): raise Exception('invalid plan') tip_user = TipUser.tip_user_from(email_address=email, auto_save=False) if not tip_user.verified: tip_user.verified = True tip_user.save() req.session['pay_session'] = tip_user.id # If the user already has a subscription, update it instead of billing them # with a new one. try: sub = RecurringTip.objects.get(tipper=tip_user, podcast=pod, deactivated=False) if sub.amount == amount: return True # Update Stripe with the new amount sub_obj = sub.get_subscription() sub_obj.quantity = int(amount / 100) sub_obj.source = token sub_obj.save() # Update the DB with the new amount sub.amount = amount sub.save() # Updating total_tips is done with the web hook. send_notification_email( None, ugettext('Your subscription was updated.'), ugettext('Your subscription to %s was updated to $%0.2f. Thanks ' 'for supporting your favorite content creators!') % (pod.name, float(amount) / 100), email=email) return True except RecurringTip.DoesNotExist: pass managed_account = owner_us.stripe_payout_managed_account # Check that the tip sub plan exists plans = stripe.Plan.list(stripe_account=managed_account) if not plans.data: stripe.Plan.create( amount=100, currency='usd', id='tipsub', interval='month', name='Podcast Tip Jar', statement_descriptor='PODCAST TIP JAR', stripe_account=managed_account) # Create the customer associated with the managed account sub = RecurringTip(tipper=tip_user, podcast=pod, amount=amount) try: customer = stripe.Customer.create( email=email, plan='tipsub', quantity=amount / 100, source=token, stripe_account=managed_account) except stripe.error.InvalidRequestError: return True sub.stripe_customer_id = customer.id sub.stripe_subscription_id = customer.subscriptions.data[0].id sub.save() # We don't update total_tips or create a tip event here. That happens when # the web hook from Stripe tells us that the payment succeeded. send_notification_email( pod.owner, ugettext('Someone subscribed to your podcast!'), ugettext('%s will receive a tip of $%0.2f from %s. Their subscription ' 'will pay out once every month. You should send them an email ' 'thanking them for their generosity.') % ( pod.name, float(amount) / 100, email)) return True
def hook(req): try: body = json.loads(req.body.decode('utf-8')) except Exception as e: rollbar.report_message('Error parsing Stripe hook JSON: %s' % str(e), 'warn') return HttpResponse(status=400) if not settings.DEBUG: try: # Validate the event stripe.Event.retrieve(body['id'], stripe_account=body.get('user_id')) except Exception as e: rollbar.report_message('Error fetching Stripe event: %s' % str(e), 'warn') return HttpResponse(status=400) if body['type'] == 'invoice.payment_succeeded' and body.get('user_id'): sub = _get_tip_subscription(body) if not sub: return {'warning': 'subscription unrecognized'} amount = int(body['data']['object']['total']) pod = sub.podcast tip_event = TipEvent(tipper=sub.tipper, podcast=pod, amount=amount, recurring_tip=sub) tip_event.save() Podcast.objects.filter(id=pod.id).update(total_tips=F('total_tips') + amount) email = sub.tipper.email_address send_notification_email( None, ugettext('Thanks for leaving a tip!'), ugettext( 'Your tip was sent: %s received $%0.2f. Thanks for supporting your ' 'favorite content creators!') % (pod.name, float(amount) / 100), email=email) send_notification_email( pod.owner, ugettext('%s received a tip of $%0.2f') % (pod.name, float(amount) / 100), ugettext( '%s received a tip of $%0.2f from %s as part of a monthly ' 'subscription to the show. You should send them an email ' 'thanking them for their generosity.') % (pod.name, float(amount) / 100, email)) NotificationHook.trigger_notification(podcast=pod, trigger_type='tip', data={ 'tipper': sub.tipper.email_address, 'amount': amount }) return {'success': 'emails sent, tip event processed'} elif body['type'] == 'invoice.payment_succeeded': sub = stripe.Subscription.retrieve( body['data']['object']['subscription']) if not sub.discount: return {'success': 'ignoring undiscounted subscription'} coupon_code = sub.discount.coupon.id try: coupon_owner = UserSettings.objects.get(coupon_code=coupon_code) except UserSettings.DoesNotExist: return {'success': 'coupon not owned by referrer'} if coupon_owner.plan == payment_plans.PLAN_DEMO or coupon_owner.plan == payment_plans.PLAN_COMMUNITY: return {'success': 'coupon owned by free user'} min_charge = float('inf') valid_charges = 0 # We limit this to three. If it goes over or under, we can ignore the charge. invoices = stripe.Invoice.list(customer=sub.customer, limit=3) if len(invoices.data) < 2: return {'success': 'did not reach two invoices yet'} for invoice in invoices.data: if not invoice.paid: continue valid_charges += 1 invoice_amount = 0 for line in invoice.lines.data: invoice_amount += line.amount if invoice_amount < min_charge: min_charge = invoice_amount if valid_charges != 2: return {'success': 'did not have two successful invoices'} try: owner_cust = coupon_owner.get_stripe_customer() except Exception as e: rollbar.report_message( 'Error fetching coupon owner stripe customer: %s' % str(e), 'error') return {'success': 'coupon owner does not exist'} else: if not owner_cust: rollbar.report_message( 'Coupon owner did not have valid stripe customer', 'error') return {'success': 'coupon owner does not exist'} amount = min_charge * 2 # Negative amounts are credits owner_cust.account_balance -= amount owner_cust.save() send_notification_email( coupon_owner.user, ugettext('You have referral credit!'), ugettext('One of your referrals has crossed their two-month mark ' 'as a paying customer. Your account has been credited ' '$%.2f.') % (float(amount) / 100)) return {'success': 'user credited'} elif body['type'] == 'invoice.payment_failed': if body.get('user_id'): return _handle_failed_tip_sub(body) else: return _handle_failed_subscription(body) return {'success': 'ignored'}
def hook(req): try: body = json.loads(req.body.decode('utf-8')) except Exception as e: rollbar.report_message( 'Error parsing Stripe hook JSON: %s' % str(e), 'warn') return HttpResponse(status=400) if not settings.DEBUG: try: # Validate the event stripe.Event.retrieve( body['id'], stripe_account=body.get('user_id')) except Exception as e: rollbar.report_message( 'Error fetching Stripe event: %s' % str(e), 'warn') return HttpResponse(status=400) if body['type'] == 'invoice.payment_succeeded' and body.get('user_id'): sub = _get_tip_subscription(body) if not sub: return {'warning': 'subscription unrecognized'} amount = int(body['data']['object']['total']) pod = sub.podcast tip_event = TipEvent( tipper=sub.tipper, podcast=pod, amount=amount, recurring_tip=sub) tip_event.save() pod.total_tips += amount pod.save() email = sub.tipper.email_address send_notification_email( None, ugettext('Thanks for leaving a tip!'), ugettext('Your tip was sent: %s received $%0.2f. Thanks for supporting your ' 'favorite content creators!') % (pod.name, float(amount) / 100), email=email) send_notification_email( pod.owner, ugettext('%s received a tip of $%0.2f') % (pod.name, float(amount) / 100), ugettext('%s received a tip of $%0.2f from %s as part of a monthly ' 'subscription to the show. You should send them an email ' 'thanking them for their generosity.') % (pod.name, float(amount) / 100, email)) NotificationHook.trigger_notification( podcast=pod, trigger_type='tip', data={'tipper': sub.tipper.email_address, 'amount': amount}) return {'success': 'emails sent, tip event processed'} elif body['type'] == 'invoice.payment_succeeded': sub = stripe.Subscription.retrieve(body['data']['object']['subscription']) if not sub.discount: return {'success': 'ignoring undiscounted subscription'} coupon_code = sub.discount.coupon.id try: coupon_owner = UserSettings.objects.get(coupon_code=coupon_code) except UserSettings.DoesNotExist: return {'success': 'coupon not owned by referrer'} if coupon_owner.plan == payment_plans.PLAN_DEMO or coupon_owner.plan == payment_plans.PLAN_COMMUNITY: return {'success': 'coupon owned by free user'} min_charge = float('inf') valid_charges = 0 # We limit this to three. If it goes over or under, we can ignore the charge. invoices = stripe.Invoice.list(customer=sub.customer, limit=3) if len(invoices.data) < 2: return {'success': 'did not reach two invoices yet'} for invoice in invoices.data: if not invoice.paid: continue valid_charges += 1 invoice_amount = 0 for line in invoice.lines.data: invoice_amount += line.amount if invoice_amount < min_charge: min_charge = invoice_amount if valid_charges != 2: return {'success': 'did not have two successful invoices'} try: owner_cust = coupon_owner.get_stripe_customer() except Exception as e: rollbar.report_message('Error fetching coupon owner stripe customer: %s' % str(e), 'error') return {'success': 'coupon owner does not exist'} else: if not owner_cust: rollbar.report_message('Coupon owner did not have valid stripe customer', 'error') return {'success': 'coupon owner does not exist'} amount = min_charge * 2 # Negative amounts are credits owner_cust.account_balance -= amount owner_cust.save() send_notification_email( coupon_owner.user, ugettext('You have referral credit!'), ugettext('One of your referrals has crossed their two-month mark ' 'as a paying customer. Your account has been credited ' '$%.2f.') % (float(amount) / 100)) return {'success': 'user credited'} elif body['type'] == 'invoice.payment_failed': if body.get('user_id'): return _handle_failed_tip_sub(body) else: return _handle_failed_subscription(body) return {'success': 'ignored'}
def _finish_sub(req, pod, amount, email, token): owner_us = UserSettings.get_from_user(pod.owner) if (not owner_us.stripe_payout_managed_account or owner_us.plan == PLAN_DEMO): raise Exception('invalid plan') tip_user = TipUser.tip_user_from(email_address=email, auto_save=False) if not tip_user.verified: tip_user.verified = True tip_user.save() req.session['pay_session'] = tip_user.id # If the user already has a subscription, update it instead of billing them # with a new one. try: sub = RecurringTip.objects.get(tipper=tip_user, podcast=pod, deactivated=False) if sub.amount == amount: return True # Update Stripe with the new amount sub_obj = sub.get_subscription() sub_obj.quantity = int(amount / 100) sub_obj.source = token sub_obj.save() # Update the DB with the new amount old_amount = sub.amount sub.amount = amount sub.save() # Updating total_tips is done with the web hook. send_notification_email( None, ugettext('Your subscription was updated.'), ugettext('Your subscription to %s was updated to $%0.2f. Thanks ' 'for supporting your favorite content creators!') % (pod.name, float(amount) / 100), email=email) return True except RecurringTip.DoesNotExist: pass managed_account = owner_us.stripe_payout_managed_account # Check that the tip sub plan exists plans = stripe.Plan.list(stripe_account=managed_account) if not plans.data: stripe.Plan.create( amount=100, currency='usd', id='tipsub', interval='month', name='Podcast Tip Jar', statement_descriptor='PODCAST TIP JAR', stripe_account=managed_account) # Create the customer associated with the managed account sub = RecurringTip(tipper=tip_user, podcast=pod, amount=amount) try: customer = stripe.Customer.create( email=email, plan='tipsub', quantity=amount / 100, source=token, stripe_account=managed_account) except stripe.error.InvalidRequestError: return True sub.stripe_customer_id = customer.id sub.stripe_subscription_id = customer.subscriptions.data[0].id sub.save() # We don't update total_tips or create a tip event here. That happens when # the web hook from Stripe tells us that the payment succeeded. send_notification_email( pod.owner, ugettext('Someone subscribed to your podcast!'), ugettext('%s will receive a tip of $%0.2f from %s. Their subscription ' 'will pay out once every month. You should send them an email ' 'thanking them for their generosity.') % ( pod.name, float(amount) / 100, email)) return True