def test_cancel_handler_clears_paid_status(self): self.user.is_paid = 1 self.user.stripe_customer_id = "fake" self.user.save() pl = PaymentLog(processor=PaymentLog.STRIPE, user_id=self.user.id, status="payment", subscription_id="fake-sub", transaction_serial_number=1) pl.save() # stub out the underlying stripe things fake_plan = Mock() fake_plan.id = options.stripe_annual_plan_id fake_subscription = Mock() fake_subscription.id = "fake-sub" fake_subscription.status = "active" fake_subscription.plan = fake_plan fake_subscription.current_period_start = 1489464525 fake_subscription.current_period_end = 1521000525 fake_subscription.delete.return_value = None customer = stripe.Customer(id=self.user.stripe_customer_id) customer.subscriptions = Mock() customer.subscriptions.data = [fake_subscription] customer.subscriptions.retrieve.return_value = fake_subscription with patch("stripe.Customer") as CustomerMock: CustomerMock.retrieve.return_value = customer response = self.post_url("/account/payment/cancel", {}) user = User.get(self.user.id) self.assertEqual(user.is_paid, 0)
def test_redeem_voucher_with_good_voucher(self): self.sign_out() user = test.factories.user(name="free_user", email="*****@*****.**") user.is_paid = 0 user.save() self.sign_in(user.name, "password") response = self.fetch_url("/account/settings") # verify this account is currently free self.assertTrue( response.body.find("You are currently using a free account.") > -1) arguments = {"key": "unclaimed"} # this will post and redirect to the settings page which should # then reflect that we are a paid user with 5 years of credit response = self.post_url("/account/redeem", arguments) self.assertTrue(response.body.find("5 Years") > -1) payments = PaymentLog.where("user_id=%s", user.id) self.assertEquals(len(payments), 1) self.assertEquals(payments[0].operation, "redeem") self.assertEquals(payments[0].status, "credit") self.assertEquals(payments[0].reference_id, str(self.promotion.id)) self.assertEquals(payments[0].transaction_id, arguments['key']) self.assertEquals(payments[0].buyer_email, user.email) self.assertEquals(payments[0].buyer_name, user.name) # self.assertEquals(payments[0].next_transaction_date, ) voucher = Voucher.get("claimed_by_user_id=%s", user.id) self.assertEquals(voucher.promotion_id, self.promotion.id) self.assertEquals(voucher.claimed_by_user_id, user.id) self.assertEquals(voucher.offered_by_user_id, self.admin.id)
def get(self): user = self.get_current_user_object() payments = [] if user.is_paid: payments = PaymentLog.last_payments(count=3, user_id=user.id) already_requested = self.get_secure_cookie("image_request") promotions = Promotion.active() return self.render("account/settings.html", user=user, payments=payments, already_requested=already_requested, promotions=promotions)
def get(self): user = self.get_current_user_object() payments = [] if user.is_paid: payments = PaymentLog.last_payments(count=3, user_id=user.id) already_requested = self.get_secure_cookie("image_request") cancel_flag = "canceled" in (user.stripe_plan_id or "") migrated_flag = self.get_argument('migrated', 0) promotions = Promotion.active() has_data_to_migrate = not MigrationState.has_migrated(user.id) return self.render("account/settings.html", user=user, payments=payments, already_requested=already_requested, promotions=promotions, plan_name=plan_name(user.stripe_plan_id), has_data_to_migrate=has_data_to_migrate, migrated_flag=migrated_flag, cancel_flag=cancel_flag)
def apply_to_user(self, user_): from models import PaymentLog promotion = self.get_promotion() self.offered_by_user_id = 0 if not self.offered_by_user_id and promotion is not None: shake = promotion.shake() if shake is not None: # if this promotion is related to a shake # associate the voucher with that shake's owner self.offered_by_user_id = shake.user_id now = datetime.datetime.utcnow() # if the user has a voucher, then we need # to apply a credit to their account using # payment_log in addition to creating the # voucher record and claiming it. self.claimed_by_user_id = user_.id self.claimed_at = now.strftime("%Y-%m-%d %H:%M:%S") self.save() # now record to payment_log that this # user has claimed a voucher. next_date = None amount = None promotion = self.get_promotion() # make a sensible "transaction amount" description # that we use for the settings screen if promotion is not None \ and promotion.membership_months > 0: months = promotion.membership_months days = int((365 * (months/12.0)) + 0.5) next_date = now + datetime.timedelta(days=days) if months >= 12 and months % 12 == 0: years = months / 12 if years == 1: amount = '1 Year' elif years > 1: amount = '%d Years' % years elif months == 1: amount = '1 Month' elif months > 1: amount = '%d Months' % months pl = PaymentLog( user_id = user_.id, status = "credit", reference_id = promotion.id, transaction_id = self.voucher_key, operation = "redeem", transaction_date = now.strftime("%Y-%m-%d %H:%M:%S"), next_transaction_date = next_date.strftime("%Y-%m-%d %H:%M:%S"), buyer_email = user_.email, buyer_name = (user_.full_name or user_.name), transaction_amount = amount, payment_reason = "MLTSHP Paid Account", payment_method = "voucher", processor = PaymentLog.VOUCHER, transaction_serial_number = 0 ) pl.save() # update user paid status if necessary if user_.is_paid != 1: user_.is_paid = 1 user_.save() payment_notifications(user_, "redeemed", amount)
def post(self): current_user = self.get_current_user_object() token_id = None if current_user.stripe_customer_id is None: token_id = self.get_argument("token") if token_id is None: # invalid request raise Exception("Invalid request") plan_id = self.get_argument("plan_id") if plan_id not in ("mltshp-single", "mltshp-double"): raise Exception("Invalid request") quantity = 1 if plan_id == "mltshp-double": quantity = int(float(self.get_argument("quantity"))) if quantity < 24 or quantity > 500: raise "Invalid request" customer = None sub = None stripe_customer_id = current_user.stripe_customer_id if stripe_customer_id is not None: try: customer = stripe.Customer.retrieve(stripe_customer_id) except stripe.error.InvalidRequestError: pass if customer and getattr(customer, "deleted", False): customer = None try: if customer is None: if token_id is None: # FIXME: handle this more gracefully... raise "Invalid request" # create a new customer object for this subscription customer = stripe.Customer.create( description="MLTSHP user %s" % current_user.name, email=current_user.email, metadata={"mltshp_user": current_user.name}, source=token_id, ) # if this works, we should have a customer with 1 subscription, this one if customer.subscriptions.total_count > 0: sub = customer.subscriptions.data[0] if sub.plan != plan_id: sub.plan = plan_id if plan_id == "mltshp-double": sub.quantity = quantity else: sub.quantity = 1 sub.save() else: if plan_id == "mltshp-double": sub = customer.subscriptions.create(plan=plan_id, quantity=quantity) else: sub = customer.subscriptions.create(plan=plan_id) except stripe.error.CardError as ex: return self.render("account/return-subscription-completed.html", error=unicode(ex), has_data_to_migrate=False) if not sub: raise Exception("Error issuing subscription") amount = "USD %0.2f" % ((sub.plan.amount / 100.0) * quantity) payment_log = PaymentLog( processor=PaymentLog.STRIPE, user_id=current_user.id, status="subscribed", reference_id=sub.id, # ?? transaction_id=sub.id, # ?? operation="order", transaction_date=datetime.datetime.fromtimestamp( sub.current_period_start).strftime("%Y-%m-%d %H:%M:%S"), next_transaction_date=datetime.datetime.fromtimestamp( sub.current_period_end).strftime("%Y-%m-%d %H:%M:%S"), buyer_email=current_user.email, buyer_name=current_user.display_name(), recipient_email="*****@*****.**", recipient_name="MLTSHP, Inc.", payment_reason="MLTSHP Membership", transaction_serial_number=1, subscription_id=sub.id, payment_method="CC", transaction_amount=amount, ) payment_log.save() current_user.is_paid = 1 current_user.stripe_plan_id = plan_id if plan_id == "mltshp-double": current_user.stripe_plan_rate = quantity else: current_user.stripe_plan_rate = None if current_user.stripe_customer_id != customer.id: current_user.stripe_customer_id = customer.id current_user.save() if options.postmark_api_key: pm = postmark.PMMail( api_key=options.postmark_api_key, sender="*****@*****.**", to="*****@*****.**", subject="%s has created a subscription" % (payment_log.buyer_name), text_body= "Subscription ID: %s\nBuyer Name:%s\nBuyer Email:%s\nUser ID:%s\n" % (payment_log.subscription_id, payment_log.buyer_name, payment_log.buyer_email, current_user.id)) pm.send() payment_notifications(current_user, "subscription", amount) has_data_to_migrate = not MigrationState.has_migrated(current_user.id) return self.render("account/return-subscription-completed.html", has_data_to_migrate=has_data_to_migrate)
def post(self): current_user = self.get_current_user_object() # an existing, paying user can't subscribe again... # we also won't allow users with unconfirmed emails to subscribe # should we message here? if current_user.is_paid == 1 or current_user.email_confirmed != 1: return self.redirect("/account/settings") token_id = self.get_argument("token") if token_id is None: # invalid request raise "Invalid request" customer = None sub = None stripe_customer_id = current_user.stripe_customer_id if stripe_customer_id is not None: try: customer = stripe.Customer.retrieve(stripe_customer_id) except stripe.error.InvalidRequestError: pass if customer and getattr(customer, "deleted", False): customer = None try: if customer is None: # create a new customer object for this subscription customer = stripe.Customer.create( description="MLTSHP user %s" % current_user.name, email=current_user.email, plan=options.stripe_annual_plan_id, card=token_id, # obtained with Checkout.js metadata={"mltshp_user": current_user.name}, ) # if this works, we should have a customer with 1 subscription, this one if customer.subscriptions.total_count > 0: sub = customer.subscriptions.data[0] else: sub = customer.subscriptions.create( plan=options.stripe_annual_plan_id, card=token_id) except stripe.error.CardError as ex: return self.render("account/return-subscription-completed.html", error=unicode(ex)) if not sub: raise Exception("Error issuing subscription") amount = "USD %0.2f" % (sub.plan.amount / 100.0) payment_log = PaymentLog( processor=PaymentLog.STRIPE, user_id=current_user.id, status="subscribed", reference_id=sub.id, # ?? transaction_id=sub.id, # ?? operation="order", transaction_date=datetime.datetime.fromtimestamp( sub.current_period_start).strftime("%Y-%m-%d %H:%M:%S"), next_transaction_date=datetime.datetime.fromtimestamp( sub.current_period_end).strftime("%Y-%m-%d %H:%M:%S"), buyer_email=current_user.email, buyer_name=current_user.display_name(), recipient_email="*****@*****.**", recipient_name="MLTSHP, LLC", payment_reason="MLTSHP Paid Account", transaction_serial_number=1, subscription_id=sub.id, payment_method="CC", transaction_amount=amount, ) payment_log.save() current_user.is_paid = 1 if current_user.stripe_customer_id != customer.id: current_user.stripe_customer_id = customer.id current_user.save() if options.postmark_api_key: pm = postmark.PMMail( api_key=options.postmark_api_key, sender="*****@*****.**", to="*****@*****.**", subject="%s has created a subscription" % (payment_log.buyer_name), text_body= "Subscription ID: %s\nBuyer Name:%s\nBuyer Email:%s\nUser ID:%s\n" % (payment_log.subscription_id, payment_log.buyer_name, payment_log.buyer_email, current_user.id)) pm.send() payment_notifications(current_user, "subscription", amount) return self.render("account/return-subscription-completed.html")
def post(self): # type of message is passed through "type" parameter json_response = json.loads(self.request.body) body_str = json.dumps(json_response).replace("\n", "\\n") stripe_customer_id = None period_start = None period_end = None checkout_id = None status = None subscription_id = None charge_id = None amount = 0 operation = None evt = stripe.convert_to_stripe_object(json_response, options.stripe_secret_key, None) if evt.type == 'invoice.payment_failed': # subscription failed to be paid due to a problem # with the member's credit card or something # for now, just email [email protected] about this stripe_customer_id = evt.data.object.customer subscriber = User.get("stripe_customer_id=%s and deleted=0", stripe_customer_id) if subscriber and options.postmark_api_key: pm = postmark.PMMail( api_key=options.postmark_api_key, sender="*****@*****.**", to="*****@*****.**", subject="%s has a subscription failure" % (subscriber.display_name()), text_body= "Subscription ID: %s\nBuyer Name:%s\nBuyer Email:%s\nUser ID:%s\n" % (subscription_id, subscriber.display_name(), subscriber.email, subscriber.id)) pm.send() return self.finish("OK") elif evt.type == 'customer.subscription.created': # important properties # customer - should be recorded already in account.stripe_customer_id # current_period_start # current_period_end # plan.id (mltshp-annual) stripe_customer_id = evt.data.object.customer period_start = evt.data.object.current_period_start period_end = evt.data.object.current_period_end checkout_id = evt.data.object.id status = "subscribed" # evt.type operation = "subscribe" subscription_id = evt.data.object.id amount = evt.data.object.plan.amount elif evt.type == "customer.subscription.deleted": stripe_customer_id = evt.data.object.customer period_start = evt.data.object.current_period_start period_end = evt.data.object.current_period_end status = "canceled" # evt.type operation = "cancel" subscription_id = evt.data.object.id elif evt.type == 'invoice.payment_succeeded': # customer # date # lines.subscriptions[0].plan.id # period.start # period.end # total line_items = [ item for item in evt.data.object.lines.data if item.type == "subscription" and item.plan.id in (options.stripe_annual_plan_id, options.stripe_monthly_plan_id) ] if line_items: line_item = line_items[0] stripe_customer_id = evt.data.object.customer period_start = line_item.period.start period_end = line_item.period.end checkout_id = evt.data.object.id status = "payment" # evt.type operation = "pay" subscription_id = evt.data.object.subscription charge_id = evt.data.object.charge amount = evt.data.object.total else: # unsupported event type; just ignore it return self.finish("OK") subscriber = None if stripe_customer_id: subscriber = User.get("stripe_customer_id=%s and deleted=0", stripe_customer_id) if subscriber is None: # raise an exception for this... #raise Exception("failed to locate user for stripe_customer_id %s" # % stripe_customer_id) return self.finish("OK") #create a payment log record amount = "USD %0.2f" % (amount / 100.0) pl = PaymentLog(user_id=subscriber.id, status=status, reference_id=checkout_id, transaction_id=charge_id, operation=operation, transaction_date=datetime.datetime.fromtimestamp( period_start).strftime("%Y-%m-%d %H:%M:%S"), next_transaction_date=datetime.datetime.fromtimestamp( period_end).strftime("%Y-%m-%d %H:%M:%S"), buyer_email=subscriber.email, buyer_name=subscriber.display_name(), recipient_email="*****@*****.**", recipient_name="MLTSHP, LLC", payment_reason="MLTSHP Paid Account", transaction_serial_number=1, subscription_id=subscription_id, payment_method='CC', transaction_amount=amount, processor=PaymentLog.STRIPE) pl.save() if evt.type == "customer.subscription.deleted": subscriber.is_paid = 0 else: subscriber.is_paid = 1 subscriber.save() return self.finish("OK")
def get(self): user = self.get_current_user_object() payments = [] if user.is_paid: payments = [] if user.stripe_customer_id: customer_id = user.stripe_customer_id charges = stripe.Charge.list(limit=5, customer=customer_id) for charge in charges.data: payments.append({ "transaction_amount": "USD %0.2f" % (charge.amount / 100.0, ), "refund_amount": charge.refunded and "USD %0.2f" % (charge.amount_refunded / 100.0, ) or "", "created_at": datetime.datetime.fromtimestamp(charge.created), "status": "charged", "is_pending": charge.status == "pending", "is_failed": charge.status == "failed", "is_success": charge.status == "succeeded", "is_refund": charge.refunded, }) else: log = PaymentLog.last_payments(count=5, user_id=user.id) for payment in log: payments.append({ "transaction_amount": payment.transaction_amount, "refund_amount": "", "created_at": payment.created_at, "status": payment.status, "is_pending": False, "is_success": True, "is_failed": False, "is_refund": False, }) already_requested = self.get_secure_cookie("image_request") cancel_flag = "canceled" in (user.stripe_plan_id or "") updated_flag = self.get_argument("update", "") == "1" migrated_flag = self.get_argument("migrated", 0) past_due = False source_card_type = None source_last_4 = None source_expiration = None promotions = Promotion.active() has_data_to_migrate = not MigrationState.has_migrated(user.id) if user.stripe_customer_id: customer = None try: customer = stripe.Customer.retrieve(user.stripe_customer_id) except stripe.error.InvalidRequestError: pass if customer and not hasattr(customer, 'deleted'): if customer.subscriptions.total_count >= 1: subscriptions = [ sub for sub in customer.subscriptions.data if sub.plan.id == user.stripe_plan_id ] if subscriptions: subscription = subscriptions[0] past_due = subscription.status == "past_due" if customer.sources.total_count > 0: if customer.sources.data[0].object == "card": card = customer.sources.data[0] elif customer.sources.data[0].object == "source": card = customer.sources.data[0].card source_card_type = card.brand source_last_4 = card.last4 source_expiration = "%d/%d" % (card.exp_month, card.exp_year) return self.render("account/settings.html", user=user, payments=payments, already_requested=already_requested, promotions=promotions, plan_name=plan_name(user.stripe_plan_id), past_due=past_due, stripe_public_key=options.stripe_public_key, source_card_type=source_card_type, source_last_4=source_last_4, source_expiration=source_expiration, has_data_to_migrate=has_data_to_migrate, updated_flag=updated_flag, migrated_flag=migrated_flag, cancel_flag=cancel_flag)