def complete_gold_purchase(secret, transaction_id, payer_email, payer_id, subscription_id, pennies, months, goldtype, buyer, recipient, signed, giftmessage, comment): """After receiving a message from a payment processor, apply gold. Shared endpoint for all payment processing systems. Validation of gold purchase (sender, recipient, etc.) should happen before hitting this. """ gold_recipient = recipient or buyer with gold_lock(gold_recipient): gold_recipient._sync_latest() days = days_from_months(months) if goldtype in ('onetime', 'autorenew'): admintools.engolden(buyer, days) if goldtype == 'onetime': subject = "thanks for buying reddit gold!" if g.lounge_reddit: message = strings.lounge_msg else: message = ":)" else: subject = "your reddit gold has been renewed!" message = ("see the details of your subscription on " "[your userpage](/u/%s)" % buyer.name) elif goldtype == 'creddits': buyer._incr('gold_creddits', months) subject = "thanks for buying creddits!" message = ("To spend them, visit http://%s/gold or your favorite " "person's userpage." % (g.domain)) elif goldtype == 'gift': send_gift(buyer, recipient, months, days, signed, giftmessage, comment) subject = "thanks for giving reddit gold!" message = "Your gift to %s has been delivered." % recipient.name status = 'processed' secret_pieces = [goldtype] if goldtype == 'gift': secret_pieces.append(recipient.name) secret_pieces.append(secret) secret = '-'.join(secret_pieces) try: create_claimed_gold(transaction_id, payer_email, payer_id, pennies, days, secret, buyer._id, c.start_time, subscr_id=subscription_id, status=status) except IntegrityError: g.log.error('gold: got duplicate gold transaction') try: message = append_random_bottlecap_phrase(message) send_system_message(buyer, subject, message, distinguished='gold-auto') except MessageError: g.log.error('complete_gold_purchase: could not send system message')
def send_gold_code(buyer, months, days, trans_id=None, payer_email='', pennies=0): code = create_gold_code(trans_id, payer_email, buyer._id, pennies, days, c.start_time) # format the code so it's easier to read (XXXXX-XXXXX) split_at = len(code) / 2 code = code[:split_at] + '-' + code[split_at:] if months == 1: amount = "a month" else: amount = "%d months" % months subject = _('Your gold gift code has been generated!') message = _('Here is your gift code for %(amount)s of reddit gold:\n\n' '**%(code)s**\n\nThe recipient (or you!) can enter it at ' 'http://www.reddit.com/gold or go directly to ' 'http://www.reddit.com/thanks/%(code)s to claim it.' ) % {'amount': amount, 'code': code} message = append_random_bottlecap_phrase(message) send_system_message(buyer, subject, message, distinguished='gold-auto') g.log.info("%s bought a gold code for %s" % (buyer.name, amount)) return code
def POST_goldcharge(self, form, jquery, token, passthrough, pennies, months, period): """ Submit charge to stripe. Called by GoldPayment form. This submits the charge to stripe, and gold will be applied once we receive a webhook from stripe. """ try: payment_blob = validate_blob(passthrough) except GoldException as e: # This should never happen. All fields in the payment_blob # are validated on creation form.set_html('.status', _('something bad happened, try again later')) g.log.debug('POST_goldcharge: %s' % e.message) return if period: plan_id = (g.STRIPE_MONTHLY_GOLD_PLAN if period == 'monthly' else g.STRIPE_YEARLY_GOLD_PLAN) if c.user.has_gold_subscription: form.set_html('.status', _('your account already has a gold subscription')) return else: plan_id = None penny_months, days = months_and_days_from_pennies(pennies) if not months or months != penny_months: form.set_html('.status', _('stop trying to trick the form')) return customer = self.create_customer(form, token, plan=plan_id) if not customer: return if period: c.user.gold_subscr_id = customer.id c.user._commit() status = _('subscription created') subject = _('reddit gold subscription') body = _('Your subscription is being processed and reddit gold ' 'will be delivered shortly.') else: charge = self.charge_customer(form, customer, pennies, passthrough) if not charge: return status = _('payment submitted') subject = _('reddit gold payment') body = _('Your payment is being processed and reddit gold ' 'will be delivered shortly.') form.set_html('.status', status) body = append_random_bottlecap_phrase(body) send_system_message(c.user, subject, body, distinguished='gold-auto')
def complete_gold_purchase(secret, transaction_id, payer_email, payer_id, subscription_id, pennies, months, goldtype, buyer, recipient, signed, giftmessage, comment): """After receiving a message from a payment processor, apply gold. Shared endpoint for all payment processing systems. Validation of gold purchase (sender, recipient, etc.) should happen before hitting this. """ gold_recipient = recipient or buyer with gold_lock(gold_recipient): gold_recipient._sync_latest() days = days_from_months(months) if goldtype in ('onetime', 'autorenew'): admintools.engolden(buyer, days) if goldtype == 'onetime': subject = "thanks for buying reddit gold!" if g.lounge_reddit: lounge_url = "/r/" + g.lounge_reddit message = strings.lounge_msg % dict(link=lounge_url) else: message = ":)" else: subject = "your reddit gold has been renewed!" message = ("see the details of your subscription on " "[your userpage](/u/%s)" % buyer.name) elif goldtype == 'creddits': buyer._incr('gold_creddits', months) subject = "thanks for buying creddits!" message = ("To spend them, visit http://%s/gold or your favorite " "person's userpage." % (g.domain)) elif goldtype == 'gift': send_gift(buyer, recipient, months, days, signed, giftmessage, comment) subject = "thanks for giving reddit gold!" message = "Your gift to %s has been delivered." % recipient.name status = 'processed' secret_pieces = [goldtype] if goldtype == 'gift': secret_pieces.append(recipient.name) secret_pieces.append(secret) secret = '-'.join(secret_pieces) try: create_claimed_gold(transaction_id, payer_email, payer_id, pennies, days, secret, buyer._id, c.start_time, subscr_id=subscription_id, status=status) except IntegrityError: g.log.error('gold: got duplicate gold transaction') try: send_system_message(buyer, subject, message) except MessageError: g.log.error('complete_gold_purchase: could not send system message')
def process_webhook(self, event_type, webhook): if event_type == "noop": return existing = retrieve_gold_transaction(webhook.transaction_id) if not existing and webhook.passthrough: try: webhook.load_blob() except GoldException as e: g.log.error("%s: payment_blob %s", webhook.transaction_id, e) self.abort403() msg = None if event_type == "cancelled": subject = _("reddit gold payment cancelled") msg = _("Your reddit gold payment has been cancelled, contact " "%(gold_email)s for details") % { "gold_email": g.goldthanks_email } if existing: # note that we don't check status on existing, probably # should update gold_table when a cancellation happens reverse_gold_purchase(webhook.transaction_id) elif event_type == "succeeded": if existing and existing.status == "processed": g.log.info("POST_goldwebhook skipping %s" % webhook.transaction_id) return self.complete_gold_purchase(webhook) elif event_type == "failed": subject = _("reddit gold payment failed") msg = _("Your reddit gold payment has failed, contact " "%(gold_email)s for details") % { "gold_email": g.goldthanks_email } elif event_type == "failed_subscription": subject = _("reddit gold subscription payment failed") msg = _( "Your reddit gold subscription payment has failed. " "Please go to http://www.reddit.com/subscription to " "make sure your information is correct, or contact " "%(gold_email)s for details" ) % {"gold_email": g.goldthanks_email} elif event_type == "refunded": if not (existing and existing.status == "processed"): return subject = _("reddit gold refund") msg = _("Your reddit gold payment has been refunded, contact " "%(gold_email)s for details") % { "gold_email": g.goldthanks_email } reverse_gold_purchase(webhook.transaction_id) if msg: if existing: buyer = Account._byID(int(existing.account_id), data=True) elif webhook.buyer: buyer = webhook.buyer else: return send_system_message(buyer, subject, msg)
def POST_authorize(self, authorize, client, redirect_uri, scope, state, duration, response_type): """Endpoint for OAuth2 authorization.""" self._check_employee_grants(client, scope) self._check_redirect_uri(client, redirect_uri) self._check_response_type_and_scope(response_type, scope) self._check_client_type_and_duration(response_type, client, duration) if c.errors: return self._error_response(state, redirect_uri, as_fragment=(response_type == "token")) if response_type == "code": code = OAuth2AuthorizationCode._new(client._id, redirect_uri, c.user._id36, scope, duration == "permanent") resp = {"code": code._id, "state": state} final_redirect = _update_redirect_uri(redirect_uri, resp) g.stats.simple_event('oauth2.POST_authorize.authorization_code_create') elif response_type == "token": token = OAuth2AccessToken._new(client._id, c.user._id36, scope) token_data = OAuth2AccessController._make_token_dict(token) token_data["state"] = state final_redirect = _update_redirect_uri(redirect_uri, token_data, as_fragment=True) g.stats.simple_event('oauth2.POST_authorize.access_token_create') # If this is the first time the user is logging in with an official # mobile app, gild them if (g.live_config.get('mobile_gild_first_login') and not c.user.has_used_mobile_app and client._id in g.mobile_auth_gild_clients): buyer = Account.system_user() admintools.adjust_gold_expiration( c.user, days=g.mobile_auth_gild_time) create_gift_gold( buyer._id, c.user._id, g.mobile_auth_gild_time, datetime.now(g.tz), signed=True, note='first_mobile_auth') subject = 'Let there be gold! Reddit just sent you Reddit gold!' message = ( "Thank you for using the Reddit mobile app! As a thank you " "for logging in during launch week, you've been gifted %s of " "Reddit Gold.\n\n" "Reddit Gold is Reddit's premium membership program, which " "grants you: \n" "An ads-free experience in Reddit's mobile apps, and\n" "Extra site features on desktop\n\n" "Discuss and get help on the features and perks at " "r/goldbenefits." ) % g.mobile_auth_gild_message message += '\n\n' + strings.gold_benefits_msg send_system_message(c.user, subject, message, add_to_sent=False) c.user.has_used_mobile_app = True c.user._commit() return self.redirect(final_redirect, code=302)
def send_gift(buyer, recipient, months, days, signed, giftmessage, thing_fullname): admintools.engolden(recipient, days) if thing_fullname: thing = Thing._by_fullname(thing_fullname, data=True) thing._gild(buyer) else: thing = None if signed: sender = buyer.name md_sender = "[%s](/user/%s)" % (sender, sender) else: sender = _("An anonymous redditor") md_sender = _("An anonymous redditor") create_gift_gold(buyer._id, recipient._id, days, c.start_time, signed) if months == 1: amount = "a month" else: amount = "%d months" % months if not thing: subject = _( 'Let there be gold! %s just sent you reddit gold!') % sender message = strings.youve_got_gold % dict(sender=md_sender, amount=amount) if giftmessage and giftmessage.strip(): message += "\n\n" + strings.giftgold_note + giftmessage + '\n\n----' else: url = thing.make_permalink_slow() if isinstance(thing, Comment): subject = _('Your comment has been gilded!') message = strings.youve_been_gilded_comment % {'url': url} else: subject = _('Your submission has been gilded!') message = strings.youve_been_gilded_link % {'url': url} message += '\n\n' + strings.gold_benefits_msg if g.lounge_reddit: message += '\n* ' + strings.lounge_msg message = append_random_bottlecap_phrase(message) try: send_system_message(recipient, subject, message, distinguished='gold-auto') except MessageError: g.log.error('send_gift: could not send system message') g.log.info("%s gifted %s to %s" % (buyer.name, amount, recipient.name)) return thing
def send_gift(buyer, recipient, months, days, signed, giftmessage, thing_fullname): admintools.engolden(recipient, days) if thing_fullname: thing = Thing._by_fullname(thing_fullname, data=True) thing._gild(buyer) else: thing = None if signed: sender = buyer.name md_sender = "[%s](/user/%s)" % (sender, sender) else: sender = _("An anonymous redditor") md_sender = _("An anonymous redditor") create_gift_gold(buyer._id, recipient._id, days, c.start_time, signed) if months == 1: amount = "a month" else: amount = "%d months" % months if not thing: subject = _( 'Let there be gold! %s just sent you reddit gold!') % sender message = strings.youve_got_gold % dict( sender=md_sender, amount=amount) if giftmessage and giftmessage.strip(): message += "\n\n" + strings.giftgold_note + giftmessage + '\n\n----' else: url = thing.make_permalink_slow() if isinstance(thing, Comment): subject = _('Your comment has been gilded!') message = strings.youve_been_gilded_comment % {'url': url} else: subject = _('Your submission has been gilded!') message = strings.youve_been_gilded_link % {'url': url} message += '\n\n' + strings.gold_benefits_msg if g.lounge_reddit: message += '\n* ' + strings.lounge_msg message = append_random_bottlecap_phrase(message) try: send_system_message( recipient, subject, message, distinguished='gold-auto') except MessageError: g.log.error('send_gift: could not send system message') g.log.info("%s gifted %s to %s" % (buyer.name, amount, recipient.name)) return thing
def POST_authorize(self, authorize, client, redirect_uri, scope, state, duration, response_type): """Endpoint for OAuth2 authorization.""" self._check_employee_grants(client, scope) if response_type == "token" and client.is_confidential(): # Prevent "confidential" clients from distributing tokens # in a non-confidential manner c.errors.add((errors.OAUTH2_INVALID_CLIENT, "client_id")) if response_type == "token" and duration != "temporary": # implicit grant -> No refresh tokens allowed c.errors.add((errors.INVALID_OPTION, "duration")) self._check_redirect_uri(client, redirect_uri) if c.errors: return self._error_response(state, redirect_uri, as_fragment=(response_type == "token")) if response_type == "code": code = OAuth2AuthorizationCode._new(client._id, redirect_uri, c.user._id36, scope, duration == "permanent") resp = {"code": code._id, "state": state} final_redirect = _update_redirect_uri(redirect_uri, resp) elif response_type == "token": token = OAuth2AccessToken._new(client._id, c.user._id36, scope) token_data = OAuth2AccessController._make_token_dict(token) token_data["state"] = state final_redirect = _update_redirect_uri(redirect_uri, token_data, as_fragment=True) # If this is the first time the user is logging in with an official # mobile app, gild them if (g.live_config.get('mobile_gild_first_login') and not c.user.has_used_mobile_app and client._id in g.mobile_auth_gild_clients): buyer = Account.system_user() admintools.adjust_gold_expiration( c.user, days=g.mobile_auth_gild_time) create_gift_gold( buyer._id, c.user._id, g.mobile_auth_gild_time, datetime.now(g.tz), signed=True, note='first_mobile_auth') subject = 'Let there be gold! %s just sent you reddit gold!' % ( buyer.name) message = "Thank you for using the reddit mobile app! For your "\ "participation, you've been gifted %s of reddit gold." % ( g.mobile_auth_gild_message) message += '\n\n' + strings.gold_benefits_msg send_system_message(c.user, subject, message, add_to_sent=False) c.user.has_used_mobile_app = True c.user._commit() return self.redirect(final_redirect, code=302)
def cancel_subscription(cls, form, user): if not user.has_stripe_subscription: return customer = stripe.Customer.retrieve(user.gold_subscr_id) customer.delete() user.gold_subscr_id = None user._commit() subject = _('your gold subscription has been cancelled') message = _('if you have any questions please email %(email)s') message %= {'email': g.goldthanks_email} send_system_message(user, subject, message) return customer
def cancel_subscription(user): if not getattr(user, "stripe_customer_id", None): return customer = stripe.Customer.retrieve(user.stripe_customer_id) customer.delete() user.stripe_customer_id = None user._commit() subject = _("your gold subscription has been cancelled") message = _("if you have any questions please email %(email)s") message %= {"email": g.goldthanks_email} send_system_message(user, subject, message) return customer
def send_claim_message(username, postcard_url): # this is ripped off from the gold-record.py code in reddit-private timestamp = int(time.time()) now = datetime.datetime.now(g.tz) transaction_id = "M%d" % timestamp secret = "p_%d%s" % (timestamp, randstr(5)) create_unclaimed_gold(transaction_id, "", "manual-unclaimed", 0, REWARD, secret, now, None) claim_url = "http://www.reddit.com/thanks/" + secret user = Account._by_name(username) message = TEMPLATE % dict(postcard_url=postcard_url, claim_url=claim_url, gold_support_email=g.goldthanks_email) send_system_message(user, "we got that postcard you sent us!", message)
def send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id): admintools.engolden(recipient, days) if comment_id: comment = Thing._by_fullname(comment_id, data=True) comment._gild(buyer) else: comment = None if signed: sender = buyer.name md_sender = "[%s](/user/%s)" % (sender, sender) else: sender = _("An anonymous redditor") md_sender = _("An anonymous redditor") create_gift_gold(buyer._id, recipient._id, days, c.start_time, signed) if months == 1: amount = "a month" else: amount = "%d months" % months if not comment: subject = _( 'Let there be gold! %s just sent you reddit gold!') % sender message = strings.youve_got_gold % dict(sender=md_sender, amount=amount) if giftmessage and giftmessage.strip(): message += "\n\n" + strings.giftgold_note + giftmessage + '\n\n----' else: subject = _('Your comment has been gilded.') message = strings.youve_got_comment_gold % dict( url=comment.make_permalink_slow(), ) message += '\n\n' + strings.gold_benefits_msg message += '\n\n' + strings.lounge_msg try: send_system_message(recipient, subject, message) except MessageError: g.log.error('send_gift: could not send system message') g.log.info("%s gifted %s to %s" % (buyer.name, amount, recipient.name)) return comment
def send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id): admintools.engolden(recipient, days) if comment_id: comment = Thing._by_fullname(comment_id, data=True) comment._gild(buyer) else: comment = None if signed: sender = buyer.name md_sender = "[%s](/user/%s)" % (sender, sender) else: sender = _("An anonymous redditor") md_sender = _("An anonymous redditor") create_gift_gold (buyer._id, recipient._id, days, c.start_time, signed) if months == 1: amount = "a month" else: amount = "%d months" % months if not comment: subject = _('Let there be gold! %s just sent you reddit gold!') % sender message = strings.youve_got_gold % dict(sender=md_sender, amount=amount) if giftmessage and giftmessage.strip(): message += "\n\n" + strings.giftgold_note + giftmessage + '\n\n----' else: subject = _('Your comment has been gilded.') message = strings.youve_got_comment_gold % dict( url=comment.make_permalink_slow(), ) message += '\n\n' + strings.gold_benefits_msg message += '\n\n' + strings.lounge_msg try: send_system_message(recipient, subject, message) except MessageError: g.log.error('send_gift: could not send system message') g.log.info("%s gifted %s to %s" % (buyer.name, amount, recipient.name)) return comment
def send_gold_code(buyer, months, days, trans_id=None, payer_email='', pennies=0, buyer_email=None): if buyer: paying_id = buyer._id buyer_name = buyer.name else: paying_id = buyer_email buyer_name = buyer_email code = create_gold_code(trans_id, payer_email, paying_id, pennies, days, c.start_time) # format the code so it's easier to read (XXXXX-XXXXX) split_at = len(code) / 2 code = code[:split_at] + '-' + code[split_at:] if months == 1: amount = "a month" else: amount = "%d months" % months subject = _('Your gold gift code has been generated!') message = _('Here is your gift code for %(amount)s of reddit gold:\n\n' '%(code)s\n\nThe recipient (or you!) can enter it at ' 'http://www.reddit.com/gold or go directly to ' 'http://www.reddit.com/thanks/%(code)s to claim it.') % { 'amount': amount, 'code': code } if buyer: # bought by a logged-in user, send a reddit PM message = append_random_bottlecap_phrase(message) send_system_message(buyer, subject, message, distinguished='gold-auto') else: # bought by a logged-out user, send an email contents = GoldGiftCodeEmail(message=message).render(style='email') _system_email(buyer_email, contents, Email.Kind.GOLD_GIFT_CODE) g.log.info("%s bought a gold code for %s", buyer_name, amount) return code
def send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id): admintools.engolden(recipient, days) if comment_id: comment = Thing._by_fullname(comment_id, data=True) comment._gild(buyer) else: comment = None if signed: sender = buyer.name md_sender = "[%s](/user/%s)" % (sender, sender) else: sender = "someone" md_sender = "An anonymous lightnet user" create_gift_gold(buyer._id, recipient._id, days, c.start_time, signed) if months == 1: amount = "a month" else: amount = "%d months" % months if not comment: message = strings.youve_got_gold % dict(sender=md_sender, amount=amount) if giftmessage and giftmessage.strip(): message += "\n\n" + strings.giftgold_note + giftmessage + "\n\n----" else: message = strings.youve_got_comment_gold % dict(url=comment.make_permalink_slow()) message += "\n\n" + strings.gold_benefits_msg message += "\n\n" + strings.lounge_msg % {"link": "/space/" + g.lounge_reddit} subject = sender + " just sent you reddit gold!" try: send_system_message(recipient, subject, message) except MessageError: g.log.error("send_gift: could not send system message") g.log.info("%s gifted %s to %s" % (buyer.name, amount, recipient.name)) return comment
def reverse_gold_purchase(transaction_id, goldtype, buyer, pennies, recipient=None): gold_recipient = recipient or buyer with gold_lock(gold_recipient): gold_recipient._sync_latest() months, days = months_and_days_from_pennies(pennies) if goldtype in ('onetime', 'autorenew'): subtract_gold_days(buyer, days) elif goldtype == 'creddits': subtract_gold_creddits(buyer, months) elif goldtype == 'gift': subtract_gold_days(recipient, days) subject = 'your gifted gold has been reversed' message = 'sorry, but the payment was reversed' send_system_message(recipient, subject, message) update_gold_transaction(transaction_id, 'reversed')
def process_webhook(self, event_type, passthrough, transaction_id, pennies, months): if event_type == 'noop': return try: payment_blob = validate_blob(passthrough) except GoldError as e: g.log.error('%s %s: bad payment_blob %s' % (self.name, transaction_id, e)) self.abort403() goldtype = payment_blob['goldtype'] buyer = payment_blob['buyer'] recipient = payment_blob.get('recipient', None) signed = payment_blob.get('signed', False) giftmessage = payment_blob.get('giftmessage', None) comment = payment_blob.get('comment', None) comment = comment._fullname if comment else None existing = retrieve_gold_transaction(transaction_id) if event_type == 'cancelled': subject = 'gold payment cancelled' msg = ('your gold payment has been cancelled, contact ' '%(gold_email)s for details' % {'gold_email': g.goldthanks_email}) send_system_message(buyer, subject, msg) if existing: # note that we don't check status on existing, probably # should update gold_table when a cancellation happens reverse_gold_purchase(transaction_id) elif event_type == 'succeeded': if existing and existing.status == 'processed': g.log.info('POST_goldwebhook skipping %s' % transaction_id) return payer_email = '' payer_id = '' subscription_id = None complete_gold_purchase(passthrough, transaction_id, payer_email, payer_id, subscription_id, pennies, months, goldtype, buyer, recipient, signed, giftmessage, comment) elif event_type == 'failed': subject = 'gold payment failed' msg = ('your gold payment has failed, contact %(gold_email)s for ' 'details' % {'gold_email': g.goldthanks_email}) send_system_message(buyer, subject, msg) # probably want to update gold_table here elif event_type == 'refunded': if not (existing and existing.status == 'processed'): return subject = 'gold refund' msg = ('your gold payment has been refunded, contact ' '%(gold_email)s for details' % {'gold_email': g.goldthanks_email}) send_system_message(buyer, subject, msg) reverse_gold_purchase(transaction_id)
def process_webhook(self, event_type, passthrough, transaction_id, pennies, months): if event_type == 'noop': return try: payment_blob = validate_blob(passthrough) except GoldException as e: g.log.error('%s %s: bad payment_blob %s' % (self.name, transaction_id, e)) self.abort403() goldtype = payment_blob['goldtype'] buyer = payment_blob['buyer'] recipient = payment_blob.get('recipient', None) signed = payment_blob.get('signed', False) giftmessage = payment_blob.get('giftmessage', None) comment = payment_blob.get('comment', None) comment = comment._fullname if comment else None existing = retrieve_gold_transaction(transaction_id) if event_type == 'cancelled': subject = 'gold payment cancelled' msg = ('your gold payment has been cancelled, contact ' '%(gold_email)s for details' % {'gold_email': g.goldthanks_email}) send_system_message(buyer, subject, msg) if existing: # note that we don't check status on existing, probably # should update gold_table when a cancellation happens reverse_gold_purchase(transaction_id) elif event_type == 'succeeded': if existing and existing.status == 'processed': g.log.info('POST_goldwebhook skipping %s' % transaction_id) return payer_email = '' payer_id = '' subscription_id = None complete_gold_purchase(passthrough, transaction_id, payer_email, payer_id, subscription_id, pennies, months, goldtype, buyer, recipient, signed, giftmessage, comment) elif event_type == 'failed': subject = 'gold payment failed' msg = ('your gold payment has failed, contact %(gold_email)s for ' 'details' % {'gold_email': g.goldthanks_email}) send_system_message(buyer, subject, msg) # probably want to update gold_table here elif event_type == 'refunded': if not (existing and existing.status == 'processed'): return subject = 'gold refund' msg = ('your gold payment has been refunded, contact ' '%(gold_email)s for details' % {'gold_email': g.goldthanks_email}) send_system_message(buyer, subject, msg) reverse_gold_purchase(transaction_id)
def reverse_gold_purchase(transaction_id): transaction = retrieve_gold_transaction(transaction_id) if not transaction: raise GoldException('gold_table %s not found' % transaction_id) buyer = Account._byID(int(transaction.account_id), data=True) recipient = None days = transaction.days months = days / 31 secret = transaction.secret if '{' in secret: secret.strip('{}') # I goofed pieces = secret.split(',') else: pieces = secret.split('-') goldtype = pieces[0] if goldtype == 'gift': recipient_name, secret = pieces[1:] recipient = Account._by_name(recipient_name) gold_recipient = recipient or buyer with gold_lock(gold_recipient): gold_recipient._sync_latest() if goldtype in ('onetime', 'autorenew'): subtract_gold_days(buyer, days) elif goldtype == 'creddits': subtract_gold_creddits(buyer, months) elif goldtype == 'gift': subtract_gold_days(recipient, days) subject = 'your gifted gold has been reversed' message = 'sorry, but the payment was reversed' send_system_message(recipient, subject, message) update_gold_transaction(transaction_id, 'reversed')
def reverse_gold_purchase(transaction_id): transaction = retrieve_gold_transaction(transaction_id) if not transaction: raise GoldException("gold_table %s not found" % transaction_id) buyer = Account._byID(int(transaction.account_id), data=True) recipient = None days = transaction.days months = days / 31 secret = transaction.secret if "{" in secret: secret.strip("{}") # I goofed pieces = secret.split(",") else: pieces = secret.split("-") goldtype = pieces[0] if goldtype == "gift": recipient_name, secret = pieces[1:] recipient = Account._by_name(recipient_name) gold_recipient = recipient or buyer with gold_lock(gold_recipient): gold_recipient._sync_latest() if goldtype in ("onetime", "autorenew"): subtract_gold_days(buyer, days) elif goldtype == "creddits": subtract_gold_creddits(buyer, months) elif goldtype == "gift": subtract_gold_days(recipient, days) subject = "your gifted gold has been reversed" message = "sorry, but the payment was reversed" send_system_message(recipient, subject, message) update_gold_transaction(transaction_id, "reversed")
def send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id): admintools.engolden(recipient, days) if comment_id: comment = Thing._by_fullname(comment_id, data=True) comment._gild(buyer) else: comment = None if signed: sender = buyer.name md_sender = "[%s](/user/%s)" % (sender, sender) else: sender = "someone" md_sender = "An anonymous redditor" create_gift_gold (buyer._id, recipient._id, days, c.start_time, signed) if months == 1: amount = "a month" else: amount = "%d months" % months if not comment: message = strings.youve_got_gold % dict(sender=md_sender, amount=amount) if giftmessage and giftmessage.strip(): message += "\n\n" + strings.giftgold_note + giftmessage else: message = strings.youve_got_comment_gold % dict( url=comment.make_permalink_slow(), ) subject = sender + " just sent you reddit gold!" send_system_message(recipient, subject, message) g.log.info("%s gifted %s to %s" % (buyer.name, amount, recipient.name)) return comment
def send_gold_code(buyer, months, days, trans_id=None, payer_email='', pennies=0, buyer_email=None): if buyer: paying_id = buyer._id buyer_name = buyer.name else: paying_id = buyer_email buyer_name = buyer_email code = create_gold_code(trans_id, payer_email, paying_id, pennies, days, c.start_time) # format the code so it's easier to read (XXXXX-XXXXX) split_at = len(code) / 2 code = code[:split_at] + '-' + code[split_at:] if months == 1: amount = "a month" else: amount = "%d months" % months subject = _('Your gold gift code has been generated!') message = _('Here is your gift code for %(amount)s of reddit gold:\n\n' '%(code)s\n\nThe recipient (or you!) can enter it at ' 'http://www.reddit.com/gold or go directly to ' 'http://www.reddit.com/thanks/%(code)s to claim it.' ) % {'amount': amount, 'code': code} if buyer: # bought by a logged-in user, send a reddit PM message = append_random_bottlecap_phrase(message) send_system_message(buyer, subject, message, distinguished='gold-auto') else: # bought by a logged-out user, send an email contents = GoldGiftCodeEmail(message=message).render(style='email') _system_email(buyer_email, contents, Email.Kind.GOLD_GIFT_CODE) g.log.info("%s bought a gold code for %s", buyer_name, amount) return code
def process_webhook(self, event_type, webhook): if event_type == 'noop': return existing = retrieve_gold_transaction(webhook.transaction_id) if not existing and webhook.passthrough: try: webhook.load_blob() except GoldException as e: g.log.error('%s: payment_blob %s', webhook.transaction_id, e) self.abort403() msg = None if event_type == 'cancelled': subject = _('reddit gold payment cancelled') msg = _('Your reddit gold payment has been cancelled, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} if existing: # note that we don't check status on existing, probably # should update gold_table when a cancellation happens reverse_gold_purchase(webhook.transaction_id) elif event_type == 'succeeded': if (existing and existing.status in ('processed', 'unclaimed', 'claimed')): g.log.info('POST_goldwebhook skipping %s' % webhook.transaction_id) return self.complete_gold_purchase(webhook) elif event_type == 'failed': subject = _('reddit gold payment failed') msg = _('Your reddit gold payment has failed, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} elif event_type == 'deleted_subscription': # the subscription may have been deleted directly by the user using # POST_delete_subscription, in which case gold_subscr_id is already # unset and we don't need to message them if webhook.buyer and webhook.buyer.gold_subscr_id: subject = _('reddit gold subscription cancelled') msg = _('Your reddit gold subscription has been cancelled ' 'because your credit card could not be charged. ' 'Contact %(gold_email)s for details') msg %= {'gold_email': g.goldthanks_email} webhook.buyer.gold_subscr_id = None webhook.buyer._commit() elif event_type == 'refunded': if not (existing and existing.status == 'processed'): return subject = _('reddit gold refund') msg = _('Your reddit gold payment has been refunded, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} reverse_gold_purchase(webhook.transaction_id) if msg: if existing: buyer = Account._byID(int(existing.account_id), data=True) elif webhook.buyer: buyer = webhook.buyer else: return try: send_system_message(buyer, subject, msg) except MessageError: g.log.error('process_webhook: send_system_message error')
def finish(self, parameters, txn_id, payer_email, paying_id, subscr_id, custom, pennies, months, days): blob_key, payment_blob = get_blob(custom) buyer_id = payment_blob.get('account_id', None) if not buyer_id: dump_parameters(parameters) raise ValueError("No buyer_id in IPN/GC with custom='%s'" % custom) try: buyer = Account._byID(buyer_id) except NotFound: dump_parameters(parameters) raise ValueError("Invalid buyer_id %d in IPN/GC with custom='%s'" % (buyer_id, custom)) if subscr_id: buyer.gold_subscr_id = subscr_id instagift = False if payment_blob['goldtype'] in ('autorenew', 'onetime'): admintools.engolden(buyer, days) subject = _("thanks for buying reddit gold!") if g.lounge_reddit: lounge_url = "/r/" + g.lounge_reddit message = strings.lounge_msg % dict(link=lounge_url) else: message = ":)" elif payment_blob['goldtype'] == 'creddits': buyer._incr("gold_creddits", months) buyer._commit() subject = _("thanks for buying creddits!") message = _("To spend them, visit [/gold](/gold) or your favorite person's userpage.") elif payment_blob['goldtype'] == 'gift': recipient_name = payment_blob.get('recipient', None) try: recipient = Account._by_name(recipient_name) except NotFound: dump_parameters(parameters) raise ValueError("Invalid recipient_name %s in IPN/GC with custom='%s'" % (recipient_name, custom)) signed = payment_blob.get("signed", False) giftmessage = _force_unicode(payment_blob.get("giftmessage", "")) comment_id = payment_blob.get("comment") send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id) instagift = True subject = _("thanks for giving reddit gold!") message = _("Your gift to %s has been delivered." % recipient.name) else: dump_parameters(parameters) raise ValueError("Got status '%s' in IPN/GC" % payment_blob['status']) # Reuse the old "secret" column as a place to record the goldtype # and "custom", just in case we need to debug it later or something secret = payment_blob['goldtype'] + "-" + custom if instagift: status="instagift" else: status="processed" create_claimed_gold(txn_id, payer_email, paying_id, pennies, days, secret, buyer_id, c.start_time, subscr_id, status=status) send_system_message(buyer, subject, message) payment_blob["status"] = "processed" g.hardcache.set(blob_key, payment_blob, 86400 * 30)
def complete_gold_purchase(cls, webhook): """After receiving a message from a payment processor, apply gold. Shared endpoint for all payment processing systems. Validation of gold purchase (sender, recipient, etc.) should happen before hitting this. """ secret = webhook.passthrough transaction_id = webhook.transaction_id payer_email = webhook.payer_email payer_id = webhook.payer_id subscr_id = webhook.subscr_id pennies = webhook.pennies months = webhook.months goldtype = webhook.goldtype buyer = webhook.buyer recipient = webhook.recipient signed = webhook.signed giftmessage = webhook.giftmessage comment = webhook.comment gold_recipient = recipient or buyer with gold_lock(gold_recipient): gold_recipient._sync_latest() days = days_from_months(months) if goldtype in ('onetime', 'autorenew'): admintools.engolden(buyer, days) if goldtype == 'onetime': subject = "thanks for buying reddit gold!" if g.lounge_reddit: message = strings.lounge_msg else: message = ":)" else: subject = "your reddit gold has been renewed!" message = ("see the details of your subscription on " "[your userpage](/u/%s)" % buyer.name) elif goldtype == 'creddits': buyer._incr('gold_creddits', months) subject = "thanks for buying creddits!" message = ("To spend them, visit http://%s/gold or your " "favorite person's userpage." % (g.domain)) elif goldtype == 'gift': send_gift(buyer, recipient, months, days, signed, giftmessage, comment) subject = "thanks for giving reddit gold!" message = "Your gift to %s has been delivered." % recipient.name status = 'processed' secret_pieces = [goldtype] if goldtype == 'gift': secret_pieces.append(recipient.name) secret_pieces.append(secret or transaction_id) secret = '-'.join(secret_pieces) try: create_claimed_gold(transaction_id, payer_email, payer_id, pennies, days, secret, buyer._id, c.start_time, subscr_id=subscr_id, status=status) except IntegrityError: g.log.error('gold: got duplicate gold transaction') try: message = append_random_bottlecap_phrase(message) send_system_message(buyer, subject, message, distinguished='gold-auto') except MessageError: g.log.error( 'complete_gold_purchase: send_system_message error')
def process_webhook(self, event_type, webhook): if event_type == 'noop': return existing = retrieve_gold_transaction(webhook.transaction_id) if not existing and webhook.passthrough: try: webhook.load_blob() except GoldException as e: g.log.error('%s: payment_blob %s', webhook.transaction_id, e) if self.abort_on_error: self.abort403() else: return msg = None if event_type == 'cancelled': subject = _('reddit gold payment cancelled') msg = _('Your reddit gold payment has been cancelled, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} if existing: # note that we don't check status on existing, probably # should update gold_table when a cancellation happens reverse_gold_purchase(webhook.transaction_id) elif event_type == 'succeeded': if (existing and existing.status in ('processed', 'unclaimed', 'claimed')): g.log.info('POST_goldwebhook skipping %s' % webhook.transaction_id) return self.complete_gold_purchase(webhook) elif event_type == 'failed': subject = _('reddit gold payment failed') msg = _('Your reddit gold payment has failed, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} elif event_type == 'deleted_subscription': # the subscription may have been deleted directly by the user using # POST_delete_subscription, in which case gold_subscr_id is already # unset and we don't need to message them if webhook.buyer and webhook.buyer.gold_subscr_id: subject = _('reddit gold subscription cancelled') msg = _('Your reddit gold subscription has been cancelled ' 'because your credit card could not be charged. ' 'Contact %(gold_email)s for details') msg %= {'gold_email': g.goldthanks_email} webhook.buyer.gold_subscr_id = None webhook.buyer._commit() elif event_type == 'refunded': if not (existing and existing.status == 'processed'): return subject = _('reddit gold refund') msg = _('Your reddit gold payment has been refunded, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} reverse_gold_purchase(webhook.transaction_id) if msg: if existing: buyer = Account._byID(int(existing.account_id), data=True) elif webhook.buyer: buyer = webhook.buyer else: return try: send_system_message(buyer, subject, msg) except MessageError: g.log.error('process_webhook: send_system_message error')
def finish(self, parameters, txn_id, payer_email, paying_id, subscr_id, custom, pennies, months, days): blob_key, payment_blob = get_blob(custom) buyer_id = payment_blob.get('account_id', None) if not buyer_id: dump_parameters(parameters) raise ValueError("No buyer_id in IPN with custom='%s'" % custom) try: buyer = Account._byID(buyer_id) except NotFound: dump_parameters(parameters) raise ValueError("Invalid buyer_id %d in IPN with custom='%s'" % (buyer_id, custom)) if subscr_id: buyer.gold_subscr_id = subscr_id instagift = False if payment_blob['goldtype'] in ('autorenew', 'onetime'): admintools.engolden(buyer, days) subject = _("Eureka! Thank you for investing in reddit gold!") message = _("Thank you for buying reddit gold. Your patronage " "supports the site and makes future development " "possible. For example, one month of reddit gold " "pays for 5 instance hours of reddit's servers.") message += "\n\n" + strings.gold_benefits_msg if g.lounge_reddit: message += "\n* " + strings.lounge_msg elif payment_blob['goldtype'] == 'creddits': buyer._incr("gold_creddits", months) buyer._commit() subject = _("Eureka! Thank you for investing in reddit gold " "creddits!") message = _("Thank you for buying creddits. Your patronage " "supports the site and makes future development " "possible. To spend your creddits and spread reddit " "gold, visit [/gold](/gold) or your favorite " "person's user page.") message += "\n\n" + strings.gold_benefits_msg + "\n\n" message += _("Thank you again for your support, and have fun " "spreading gold!") elif payment_blob['goldtype'] == 'gift': recipient_name = payment_blob.get('recipient', None) try: recipient = Account._by_name(recipient_name) except NotFound: dump_parameters(parameters) raise ValueError( "Invalid recipient_name %s in IPN/GC with custom='%s'" % (recipient_name, custom)) signed = payment_blob.get("signed", False) giftmessage = _force_unicode(payment_blob.get("giftmessage", "")) comment_id = payment_blob.get("comment") send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id) instagift = True subject = _("Thanks for giving the gift of reddit gold!") message = _("Your classy gift to %s has been delivered.\n\n" "Thank you for gifting reddit gold. Your patronage " "supports the site and makes future development " "possible.") % recipient.name message += "\n\n" + strings.gold_benefits_msg + "\n\n" message += _("Thank you again for your support, and have fun " "spreading gold!") else: dump_parameters(parameters) raise ValueError("Got status '%s' in IPN/GC" % payment_blob['status']) # Reuse the old "secret" column as a place to record the goldtype # and "custom", just in case we need to debug it later or something secret = payment_blob['goldtype'] + "-" + custom if instagift: status = "instagift" else: status = "processed" create_claimed_gold(txn_id, payer_email, paying_id, pennies, days, secret, buyer_id, c.start_time, subscr_id, status=status) message = append_random_bottlecap_phrase(message) send_system_message(buyer, subject, message, distinguished='gold-auto') payment_blob["status"] = "processed" g.hardcache.set(blob_key, payment_blob, 86400 * 30)
def POST_gcheckout(self, full_sn): if full_sn: short_sn = full_sn.split('-')[0] g.log.error( "GOOGLE CHECKOUT: %s" % short_sn) trans = _google_ordernum_request(short_sn) # get the financial details auth = trans.find("authorization-amount-notification") custom = None cart = trans.find("shopping-cart") if cart: private_item_data = cart.find("merchant-private-item-data") if private_item_data: custom = str(private_item_data.contents[0]) if not auth: # see if the payment was declinded status = trans.findAll('financial-order-state') if 'PAYMENT_DECLINED' in [x.contents[0] for x in status]: g.log.error("google declined transaction found: '%s'" % short_sn) elif 'REVIEWING' not in [x.contents[0] for x in status]: g.log.error(("google transaction not found: " + "'%s', status: %s") % (short_sn, [x.contents[0] for x in status])) else: g.log.error(("google transaction status: " + "'%s', status: %s") % (short_sn, [x.contents[0] for x in status])) if custom: payment_blob = validate_blob(custom) buyer = payment_blob['buyer'] subject = _('Your gold order has been received') msg = _('Your order for reddit gold has been ' 'received, and will be delivered shortly. ' 'Please bear with us as Google Wallet ' 'payments can take up to an hour to ' 'complete.') try: send_system_message(buyer, subject, msg) except MessageError: g.log.error('gcheckout send_system_message failed') elif auth.find("financial-order-state" ).contents[0] == "CHARGEABLE": email = str(auth.find("email").contents[0]) payer_id = str(auth.find('buyer-id').contents[0]) if custom: days = None try: pennies = int(float(trans.find("order-total" ).contents[0])*100) months, days = months_and_days_from_pennies(pennies) if not months: raise ValueError("Bad pennies for %s" % short_sn) charged = trans.find("charge-amount-notification") if not charged: _google_charge_and_ship(short_sn) parameters = request.POST.copy() self.finish(parameters, "g%s" % short_sn, email, payer_id, None, custom, pennies, months, days) except ValueError, e: g.log.error(e) else: raise ValueError("Got no custom blob for %s" % short_sn) return (('<notification-acknowledgment ' + 'xmlns="http://checkout.google.com/schema/2" ' + 'serial-number="%s" />') % full_sn)
def POST_authorize(self, authorize, client, redirect_uri, scope, state, duration, response_type): """Endpoint for OAuth2 authorization.""" self._check_employee_grants(client, scope) self._check_redirect_uri(client, redirect_uri) self._check_response_type_and_scope(response_type, scope) self._check_client_type_and_duration(response_type, client, duration) if c.errors: return self._error_response(state, redirect_uri, as_fragment=(response_type == "token")) if response_type == "code": code = OAuth2AuthorizationCode._new(client._id, redirect_uri, c.user._id36, scope, duration == "permanent") resp = {"code": code._id, "state": state} final_redirect = _update_redirect_uri(redirect_uri, resp) g.stats.simple_event('oauth2.POST_authorize.authorization_code_create') elif response_type == "token": device_id = get_device_id(client) token = OAuth2AccessToken._new( client_id=client._id, user_id=c.user._id36, scope=scope, device_id=device_id, ) resp = OAuth2AccessController._make_new_token_response(token) resp["state"] = state final_redirect = _update_redirect_uri(redirect_uri, resp, as_fragment=True) g.stats.simple_event('oauth2.POST_authorize.access_token_create') # If this is the first time the user is logging in with an official # mobile app, gild them if (g.live_config.get('mobile_gild_first_login') and not c.user.has_used_mobile_app and client._id in g.mobile_auth_gild_clients): buyer = Account.system_user() admintools.adjust_gold_expiration( c.user, days=g.mobile_auth_gild_time) create_gift_gold( buyer._id, c.user._id, g.mobile_auth_gild_time, datetime.now(g.tz), signed=True, note='first_mobile_auth') subject = 'Let there be gold! Reddit just sent you Reddit gold!' message = ( "Thank you for using the Reddit mobile app! As a thank you " "for logging in during launch week, you've been gifted %s of " "Reddit Gold.\n\n" "Reddit Gold is Reddit's premium membership program, which " "grants you: \n" "An ads-free experience in Reddit's mobile apps, and\n" "Extra site features on desktop\n\n" "Discuss and get help on the features and perks at " "r/goldbenefits." ) % g.mobile_auth_gild_message message += '\n\n' + strings.gold_benefits_msg send_system_message(c.user, subject, message, add_to_sent=False) c.user.has_used_mobile_app = True c.user._commit() return self.redirect(final_redirect, code=302)
def complete_gold_purchase( secret, transaction_id, payer_email, payer_id, subscription_id, pennies, months, goldtype, buyer, recipient, signed, giftmessage, comment, ): """After receiving a message from a payment processor, apply gold. Shared endpoint for all payment processing systems. Validation of gold purchase (sender, recipient, etc.) should happen before hitting this. """ gold_recipient = recipient or buyer with gold_lock(gold_recipient): gold_recipient._sync_latest() days = days_from_months(months) if goldtype in ("onetime", "autorenew"): admintools.engolden(buyer, days) if goldtype == "onetime": subject = "thanks for buying reddit gold!" if g.lounge_reddit: lounge_url = "/space/" + g.lounge_reddit message = strings.lounge_msg % dict(link=lounge_url) else: message = ":)" else: subject = "your reddit gold has been renewed!" message = "see the details of your subscription on " "[your userpage](/u/%s)" % buyer.name elif goldtype == "creddits": buyer._incr("gold_creddits", months) subject = "thanks for buying creddits!" message = "To spend them, visit http://%s/gold or your favorite " "person's userpage." % (g.domain) elif goldtype == "gift": send_gift(buyer, recipient, months, days, signed, giftmessage, comment) subject = "thanks for giving reddit gold!" message = "Your gift to %s has been delivered." % recipient.name status = "processed" secret_pieces = [goldtype] if goldtype == "gift": secret_pieces.append(recipient.name) secret_pieces.append(secret) secret = "-".join(secret_pieces) try: create_claimed_gold( transaction_id, payer_email, payer_id, pennies, days, secret, buyer._id, c.start_time, subscr_id=subscription_id, status=status, ) except IntegrityError: g.log.error("gold: got duplicate gold transaction") try: send_system_message(buyer, subject, message) except MessageError: g.log.error("complete_gold_purchase: could not send system message")
def process_webhook(self, event_type, webhook): if event_type == 'noop': return existing = retrieve_gold_transaction(webhook.transaction_id) if not existing and webhook.passthrough: try: webhook.load_blob() except GoldException as e: g.log.error('%s: payment_blob %s', webhook.transaction_id, e) self.abort403() msg = None if event_type == 'cancelled': subject = _('reddit gold payment cancelled') msg = _('Your reddit gold payment has been cancelled, contact ' '%(gold_email)s for details') % { 'gold_email': g.goldthanks_email } if existing: # note that we don't check status on existing, probably # should update gold_table when a cancellation happens reverse_gold_purchase(webhook.transaction_id) elif event_type == 'succeeded': if existing and existing.status == 'processed': g.log.info('POST_goldwebhook skipping %s' % webhook.transaction_id) return self.complete_gold_purchase(webhook) elif event_type == 'failed': subject = _('reddit gold payment failed') msg = _('Your reddit gold payment has failed, contact ' '%(gold_email)s for details') % { 'gold_email': g.goldthanks_email } elif event_type == 'failed_subscription': subject = _('reddit gold subscription payment failed') msg = _('Your reddit gold subscription payment has failed. ' 'Please go to http://www.reddit.com/subscription to ' 'make sure your information is correct, or contact ' '%(gold_email)s for details') % { 'gold_email': g.goldthanks_email } elif event_type == 'refunded': if not (existing and existing.status == 'processed'): return subject = _('reddit gold refund') msg = _('Your reddit gold payment has been refunded, contact ' '%(gold_email)s for details') % { 'gold_email': g.goldthanks_email } reverse_gold_purchase(webhook.transaction_id) if msg: if existing: buyer = Account._byID(int(existing.account_id), data=True) elif webhook.buyer: buyer = webhook.buyer else: return send_system_message(buyer, subject, msg)
def finish(self, parameters, txn_id, payer_email, paying_id, subscr_id, custom, pennies, months, days): blob_key, payment_blob = get_blob(custom) buyer_id = payment_blob.get('account_id', None) if not buyer_id: dump_parameters(parameters) raise ValueError("No buyer_id in IPN with custom='%s'" % custom) try: buyer = Account._byID(buyer_id) except NotFound: dump_parameters(parameters) raise ValueError("Invalid buyer_id %d in IPN with custom='%s'" % (buyer_id, custom)) if subscr_id: buyer.gold_subscr_id = subscr_id instagift = False if payment_blob['goldtype'] in ('autorenew', 'onetime'): admintools.engolden(buyer, days) subject = _("Eureka! Thank you for investing in reddit gold!") message = _("Thank you for buying reddit gold. Your patronage " "supports the site and makes future development " "possible. For example, one month of reddit gold " "pays for 5 instance hours of reddit's servers.") message += "\n\n" + strings.gold_benefits_msg if g.lounge_reddit: message += "\n* " + strings.lounge_msg elif payment_blob['goldtype'] == 'creddits': buyer._incr("gold_creddits", months) buyer._commit() subject = _("Eureka! Thank you for investing in reddit gold " "creddits!") message = _("Thank you for buying creddits. Your patronage " "supports the site and makes future development " "possible. To spend your creddits and spread reddit " "gold, visit [/gold](/gold) or your favorite " "person's user page.") message += "\n\n" + strings.gold_benefits_msg + "\n\n" message += _("Thank you again for your support, and have fun " "spreading gold!") elif payment_blob['goldtype'] == 'gift': recipient_name = payment_blob.get('recipient', None) try: recipient = Account._by_name(recipient_name) except NotFound: dump_parameters(parameters) raise ValueError("Invalid recipient_name %s in IPN/GC with custom='%s'" % (recipient_name, custom)) signed = payment_blob.get("signed", False) giftmessage = _force_unicode(payment_blob.get("giftmessage", "")) comment_id = payment_blob.get("comment") send_gift(buyer, recipient, months, days, signed, giftmessage, comment_id) instagift = True subject = _("Thanks for giving the gift of reddit gold!") message = _("Your classy gift to %s has been delivered.\n\n" "Thank you for gifting reddit gold. Your patronage " "supports the site and makes future development " "possible.") % recipient.name message += "\n\n" + strings.gold_benefits_msg + "\n\n" message += _("Thank you again for your support, and have fun " "spreading gold!") else: dump_parameters(parameters) raise ValueError("Got status '%s' in IPN/GC" % payment_blob['status']) # Reuse the old "secret" column as a place to record the goldtype # and "custom", just in case we need to debug it later or something secret = payment_blob['goldtype'] + "-" + custom if instagift: status="instagift" else: status="processed" create_claimed_gold(txn_id, payer_email, paying_id, pennies, days, secret, buyer_id, c.start_time, subscr_id, status=status) message = append_random_bottlecap_phrase(message) send_system_message(buyer, subject, message, distinguished='gold-auto') payment_blob["status"] = "processed" g.hardcache.set(blob_key, payment_blob, 86400 * 30)
def POST_gcheckout(self, full_sn): if full_sn: short_sn = full_sn.split('-')[0] g.log.error( "GOOGLE CHECKOUT: %s" % short_sn) trans = _google_ordernum_request(short_sn) # get the financial details auth = trans.find("authorization-amount-notification") custom = None cart = trans.find("shopping-cart") if cart: private_item_data = cart.find("merchant-private-item-data") if private_item_data: custom = str(private_item_data.contents[0]) if not auth: # see if the payment was declinded status = trans.findAll('financial-order-state') if 'PAYMENT_DECLINED' in [x.contents[0] for x in status]: g.log.error("google declined transaction found: '%s'" % short_sn) elif 'REVIEWING' not in [x.contents[0] for x in status]: g.log.error(("google transaction not found: " + "'%s', status: %s") % (short_sn, [x.contents[0] for x in status])) else: g.log.error(("google transaction status: " + "'%s', status: %s") % (short_sn, [x.contents[0] for x in status])) if custom: payment_blob = validate_blob(custom) buyer = payment_blob['buyer'] subject = _('gold order') msg = _('your order has been received and gold will' ' be delivered shortly. please bear with us' ' as google wallet payments can take up to an' ' hour to complete') try: send_system_message(buyer, subject, msg) except MessageError: g.log.error('gcheckout send_system_message failed') elif auth.find("financial-order-state" ).contents[0] == "CHARGEABLE": email = str(auth.find("email").contents[0]) payer_id = str(auth.find('buyer-id').contents[0]) if custom: days = None try: pennies = int(float(trans.find("order-total" ).contents[0])*100) months, days = months_and_days_from_pennies(pennies) if not months: raise ValueError("Bad pennies for %s" % short_sn) charged = trans.find("charge-amount-notification") if not charged: _google_charge_and_ship(short_sn) parameters = request.POST.copy() self.finish(parameters, "g%s" % short_sn, email, payer_id, None, custom, pennies, months, days) except ValueError, e: g.log.error(e) else: raise ValueError("Got no custom blob for %s" % short_sn) return (('<notification-acknowledgment ' + 'xmlns="http://checkout.google.com/schema/2" ' + 'serial-number="%s" />') % full_sn)
def POST_goldcharge(self, form, jquery, token, passthrough, pennies, months): """ Submit charge to stripe. Called by GoldPayment form. This submits the charge to stripe, and gold will be applied once we receive a webhook from stripe. """ try: payment_blob = validate_blob(passthrough) except GoldException as e: # This should never happen. All fields in the payment_blob # are validated on creation form.set_html('.status', _('something bad happened, try again later')) g.log.debug('POST_goldcharge: %s' % e.message) return penny_months, days = months_and_days_from_pennies(pennies) if not months or months != penny_months: form.set_html('.status', _('stop trying to trick the form')) return stripe.api_key = g.STRIPE_SECRET_KEY try: customer = stripe.Customer.create(card=token) if (customer['active_card']['address_line1_check'] == 'fail' or customer['active_card']['address_zip_check'] == 'fail'): form.set_html('.status', _('error: address verification failed')) form.find('.stripe-submit').removeAttr('disabled').end() return if customer['active_card']['cvc_check'] == 'fail': form.set_html('.status', _('error: cvc check failed')) form.find('.stripe-submit').removeAttr('disabled').end() return charge = stripe.Charge.create( amount=pennies, currency="usd", customer=customer['id'], description='%s-%s' % (passthrough, c.user.name) ) except stripe.CardError as e: form.set_html('.status', 'error: %s' % e.message) form.find('.stripe-submit').removeAttr('disabled').end() except stripe.InvalidRequestError as e: form.set_html('.status', _('invalid request')) except stripe.APIConnectionError as e: form.set_html('.status', _('api error')) except stripe.AuthenticationError as e: form.set_html('.status', _('connection error')) except stripe.StripeError as e: form.set_html('.status', _('error')) g.log.error('stripe error: %s' % e) else: form.set_html('.status', _('payment submitted')) # webhook usually sends near instantly, send a message in case subject = _('reddit gold payment') msg = _('Your payment is being processed and reddit gold will be ' 'delivered shortly.') send_system_message(c.user, subject, msg)
def process_webhook(self, event_type, passthrough, transaction_id, pennies, months): if event_type == "noop": return try: payment_blob = validate_blob(passthrough) except GoldError as e: g.log.error("%s %s: bad payment_blob %s" % (self.name, transaction_id, e)) self.abort403() goldtype = payment_blob["goldtype"] buyer = payment_blob["buyer"] recipient = payment_blob.get("recipient", None) signed = payment_blob.get("signed", False) giftmessage = payment_blob.get("giftmessage", None) comment = payment_blob.get("comment", None) comment = comment._fullname if comment else None existing = retrieve_gold_transaction(transaction_id) if event_type == "cancelled": subject = "gold payment cancelled" msg = "your gold payment has been cancelled, contact " "%(gold_email)s for details" % { "gold_email": g.goldthanks_email } send_system_message(buyer, subject, msg) if existing: # note that we don't check status on existing, probably # should update gold_table when a cancellation happens reverse_gold_purchase(transaction_id) elif event_type == "succeeded": if existing and existing.status == "processed": g.log.info("POST_goldwebhook skipping %s" % transaction_id) return payer_email = "" payer_id = "" subscription_id = None complete_gold_purchase( passthrough, transaction_id, payer_email, payer_id, subscription_id, pennies, months, goldtype, buyer, recipient, signed, giftmessage, comment, ) elif event_type == "failed": subject = "gold payment failed" msg = "your gold payment has failed, contact %(gold_email)s for " "details" % { "gold_email": g.goldthanks_email } send_system_message(buyer, subject, msg) # probably want to update gold_table here elif event_type == "refunded": if not (existing and existing.status == "processed"): return subject = "gold refund" msg = "your gold payment has been refunded, contact " "%(gold_email)s for details" % { "gold_email": g.goldthanks_email } send_system_message(buyer, subject, msg) reverse_gold_purchase(transaction_id)
def complete_gold_purchase(cls, webhook): """After receiving a message from a payment processor, apply gold. Shared endpoint for all payment processing systems. Validation of gold purchase (sender, recipient, etc.) should happen before hitting this. """ secret = webhook.passthrough transaction_id = webhook.transaction_id payer_email = webhook.payer_email payer_id = webhook.payer_id subscr_id = webhook.subscr_id pennies = webhook.pennies months = webhook.months goldtype = webhook.goldtype buyer = webhook.buyer recipient = webhook.recipient signed = webhook.signed giftmessage = webhook.giftmessage comment = webhook.comment gold_recipient = recipient or buyer with gold_lock(gold_recipient): gold_recipient._sync_latest() days = days_from_months(months) if goldtype in ("onetime", "autorenew"): admintools.engolden(buyer, days) if goldtype == "onetime": subject = "thanks for buying reddit gold!" if g.lounge_reddit: message = strings.lounge_msg else: message = ":)" else: subject = "your reddit gold has been renewed!" message = "see the details of your subscription on " "[your userpage](/u/%s)" % buyer.name elif goldtype == "creddits": buyer._incr("gold_creddits", months) subject = "thanks for buying creddits!" message = "To spend them, visit http://%s/gold or your " "favorite person's userpage." % (g.domain) elif goldtype == "gift": send_gift(buyer, recipient, months, days, signed, giftmessage, comment) subject = "thanks for giving reddit gold!" message = "Your gift to %s has been delivered." % recipient.name status = "processed" secret_pieces = [goldtype] if goldtype == "gift": secret_pieces.append(recipient.name) secret_pieces.append(secret or "") secret = "-".join(secret_pieces) try: create_claimed_gold( transaction_id, payer_email, payer_id, pennies, days, secret, buyer._id, c.start_time, subscr_id=subscr_id, status=status, ) except IntegrityError: g.log.error("gold: got duplicate gold transaction") try: message = append_random_bottlecap_phrase(message) send_system_message(buyer, subject, message, distinguished="gold-auto") except MessageError: g.log.error("complete_gold_purchase: send_system_message error")
def POST_goldcharge(self, form, jquery, token, passthrough, pennies, months, period): """ Submit charge to stripe. Called by GoldPayment form. This submits the charge to stripe, and gold will be applied once we receive a webhook from stripe. """ try: payment_blob = validate_blob(passthrough) except GoldException as e: # This should never happen. All fields in the payment_blob # are validated on creation form.set_html('.status', _('something bad happened, try again later')) g.log.debug('POST_goldcharge: %s' % e.message) return if period: plan_id = (g.STRIPE_MONTHLY_GOLD_PLAN if period == 'monthly' else g.STRIPE_YEARLY_GOLD_PLAN) if c.user.has_gold_subscription: form.set_html( '.status', _('your account already has a gold subscription')) return else: plan_id = None penny_months, days = months_and_days_from_pennies(pennies) if not months or months != penny_months: form.set_html('.status', _('stop trying to trick the form')) return customer = self.create_customer(form, token) if not customer: return if period: subscription = self.set_subscription(form, customer, plan_id) if not subscription: return c.user.gold_subscr_id = customer.id c.user._commit() status = _('subscription created') subject = _('reddit gold subscription') body = _('Your subscription is being processed and reddit gold ' 'will be delivered shortly.') else: charge = self.charge_customer(form, customer, pennies, passthrough) if not charge: return status = _('payment submitted') subject = _('reddit gold payment') body = _('Your payment is being processed and reddit gold ' 'will be delivered shortly.') form.set_html('.status', status) body = append_random_bottlecap_phrase(body) send_system_message(c.user, subject, body, distinguished='gold-auto')
def process_webhook(self, event_type, webhook): if event_type == 'noop': return existing = retrieve_gold_transaction(webhook.transaction_id) if not existing and webhook.passthrough: try: webhook.load_blob() except GoldException as e: g.log.error('%s: payment_blob %s', webhook.transaction_id, e) self.abort403() msg = None if event_type == 'cancelled': subject = _('reddit gold payment cancelled') msg = _('Your reddit gold payment has been cancelled, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} if existing: # note that we don't check status on existing, probably # should update gold_table when a cancellation happens reverse_gold_purchase(webhook.transaction_id) elif event_type == 'succeeded': if (existing and existing.status in ('processed', 'unclaimed', 'claimed')): g.log.info('POST_goldwebhook skipping %s' % webhook.transaction_id) return self.complete_gold_purchase(webhook) elif event_type == 'failed': subject = _('reddit gold payment failed') msg = _('Your reddit gold payment has failed, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} elif event_type == 'failed_subscription': subject = _('reddit gold subscription payment failed') msg = _('Your reddit gold subscription payment has failed. Please ' 'go to [your gold subscription page](%(gold_subscription)s) ' 'to make sure your information is correct, or contact ' '%(gold_email)s for details') msg %= {'gold_subscription': '/gold/subscription', 'gold_email': g.goldthanks_email} elif event_type == 'refunded': if not (existing and existing.status == 'processed'): return subject = _('reddit gold refund') msg = _('Your reddit gold payment has been refunded, contact ' '%(gold_email)s for details') % {'gold_email': g.goldthanks_email} reverse_gold_purchase(webhook.transaction_id) if msg: if existing: buyer = Account._byID(int(existing.account_id), data=True) elif webhook.buyer: buyer = webhook.buyer else: return try: send_system_message(buyer, subject, msg) except MessageError: g.log.error('process_webhook: send_system_message error')
def POST_goldcharge(self, form, jquery, token, passthrough, pennies, months): """ Submit charge to stripe. Called by GoldPayment form. This submits the charge to stripe, and gold will be applied once we receive a webhook from stripe. """ try: payment_blob = validate_blob(passthrough) except GoldException as e: # This should never happen. All fields in the payment_blob # are validated on creation form.set_html('.status', _('something bad happened, try again later')) g.log.debug('POST_goldcharge: %s' % e.message) return penny_months, days = months_and_days_from_pennies(pennies) if not months or months != penny_months: form.set_html('.status', _('stop trying to trick the form')) return stripe.api_key = g.STRIPE_SECRET_KEY try: customer = stripe.Customer.create(card=token) if (customer['active_card']['address_line1_check'] == 'fail' or customer['active_card']['address_zip_check'] == 'fail'): form.set_html('.status', _('error: address verification failed')) form.find('.stripe-submit').removeAttr('disabled').end() return if customer['active_card']['cvc_check'] == 'fail': form.set_html('.status', _('error: cvc check failed')) form.find('.stripe-submit').removeAttr('disabled').end() return charge = stripe.Charge.create( amount=pennies, currency="usd", customer=customer['id'], description='%s-%s' % (passthrough, c.user.name) ) except stripe.CardError as e: form.set_html('.status', 'error: %s' % e.message) form.find('.stripe-submit').removeAttr('disabled').end() except stripe.InvalidRequestError as e: form.set_html('.status', _('invalid request')) except stripe.APIConnectionError as e: form.set_html('.status', _('api error')) except stripe.AuthenticationError as e: form.set_html('.status', _('connection error')) except stripe.StripeError as e: form.set_html('.status', _('error')) g.log.error('stripe error: %s' % e) else: form.set_html('.status', _('payment submitted')) # webhook usually sends near instantly, send a message in case subject = _('gold payment') msg = _('your payment is being processed and gold will be' ' delivered shortly') send_system_message(c.user, subject, msg)