def member_customer_portal(account: Account, base_url: str): """ Access stripe customer portal :param account: :param base_url: :return: customer portalUrl """ stripe_customer_id = get_stripe_customer(account) if stripe_customer_id is None: raise P2k16UserException( 'No billing information available. Create a subscription first.') return_url = base_url + '/#!/' try: session = stripe.billing_portal.Session.create( customer=stripe_customer_id, return_url=return_url) return {'portalUrl': session.url} except stripe.error.StripeError as e: logger.error("Stripe error: " + repr(e.json_body)) raise P2k16UserException( "Stripe error. Contact [email protected] if the " "problem persists.")
def member_create_checkout_session(account: Account, base_url: str, price_id: int): """ Create a new subscription using Stripe Checkout / Billing :param account: :param base_url: :param price_id: :return: checkout sessionId """ stripe_customer_id = get_stripe_customer(account) # Existing customers should only use this flow if they have no subscriptions. if stripe_customer_id is not None: # Get customer object cu = stripe.Customer.retrieve(stripe_customer_id.stripe_id, expand=['subscriptions']) if len(cu.subscriptions.data) > 0: raise P2k16UserException("User is already subscribed.") else: # Create a new customer object stripe_customer_id = stripe.Customer.create(name=account.name, email=account.email) # Store stripe customer in case checkout fails. stripe_customer = StripeCustomer(stripe_customer_id.id) db.session.add(stripe_customer) db.session.commit() success_url = base_url + '/#!/?session_id={CHECKOUT_SESSION_ID}' cancel_url = base_url + '/#!/' try: checkout_session = stripe.checkout.Session.create( success_url=success_url, cancel_url=cancel_url, payment_method_types=["card"], mode="subscription", line_items=[{ "price": price_id, "quantity": 1 }], metadata={"accountId": account.id}, customer=stripe_customer_id, ) session_id = checkout_session['id'] return {'sessionId': session_id} except stripe.error.StripeError as e: logger.error("Stripe error: " + repr(e.json_body)) raise P2k16UserException( "Stripe error. Contact [email protected] if the problem persists." )
def _assert_can_admin_circle(admin: Account, circle: Circle): if can_admin_circle(admin, circle): return if circle.management_style == CircleManagementStyle.ADMIN_CIRCLE: raise P2k16UserException( "{} is not in the admin circle ({}) for circle {}".format( admin.username, circle.admin_circle.name, circle.name)) elif circle.management_style == CircleManagementStyle.SELF_ADMIN: raise P2k16UserException("{} is not an admin of {}".format( admin.username, circle.name))
def _check_is_circle_admin(circle: Circle, admin: Account): admin_circle = Circle.find_by_name(circle.name + '-admin') if admin_circle is None: raise P2k16UserException( 'There is no admin circle (%s-admin") for circle "%s"'.format( circle.name, circle.name)) if not is_account_in_circle(admin, admin_circle): raise P2k16UserException('Account %s is not an administrator of %s' % (admin.username, circle.description))
def register_account(username: str, email: str, name: str, password: str, phone: str) -> Account: account = Account.find_account_by_username(username) if account: raise P2k16UserException("Username is taken") account = Account.find_account_by_email(email) if account: raise P2k16UserException("Email is already registered") account = Account(username, email, name, phone, password) db.session.add(account) return account
def open_doors(self, account: Account, doors: List[Door]): door_circle = Circle.get_by_name('door') can_open_door = False if account_management.is_account_in_circle( account, door_circle) and membership_management.active_member(account): can_open_door = True if len(Company.find_active_companies_with_account(account.id)) > 0: can_open_door = True if not can_open_door: # Only non-office users may fail here if not membership_management.active_member(account): raise P2k16UserException( '{} does not have an active membership'.format( account.display_name())) if not account_management.is_account_in_circle( account, door_circle): raise P2k16UserException('{} is not in the door circle'.format( account.display_name())) publishes = [] if not event_management.has_opened_door(account): system = Account.find_account_by_username("system") logger.info("First door opening for {}".format(account)) badge_management.create_badge(account, system, "first-door-opening") for door in doors: logger.info( 'Opening door. username={}, door={}, open_time={}'.format( account.username, door.key, door.open_time)) event_management.save_event(OpenDoorEvent(door.key)) publishes.append((self.prefix + door.topic, str(door.open_time))) # Make sure everything has been written to the database before actually opening the door. db.session.flush() # TODO: move this to a handler that runs after the transaction is done # TODO: we can look at the responses and see if they where successfully sent/received. for topic, open_time in publishes: logger.info("Sending message: {}: {}".format(topic, open_time)) self._client.publish(topic, open_time)
def open_doors(self, account: Account, doors: List[Door]): can_open_door = authz_management.can_haz_door_access(account) if not can_open_door: raise P2k16UserException( '{} does not have an active membership, or lack door circle membership' .format(account.display_name())) publishes = [] if not event_management.has_opened_door(account): system = Account.find_account_by_username("system") logger.info("First door opening for {}".format(account)) badge_management.create_badge(account, system, "first-door-opening") for door in doors: logger.info( 'Opening door. username={}, door={}, open_time={}'.format( account.username, door.key, door.open_time)) event_management.save_event(OpenDoorEvent(door.key)) publishes.append((self.prefix + door.topic, str(door.open_time))) # Make sure everything has been written to the database before actually opening the door. db.session.flush() # TODO: move this to a handler that runs after the transaction is done # TODO: we can look at the responses and see if they where successfully sent/received. for topic, open_time in publishes: logger.info("Sending message: {}: {}".format(topic, open_time)) self._client.publish(topic, open_time)
def _data_tool_save(): circle_name = request.json["circle"] circle = Circle.find_by_name(circle_name) if not circle: raise P2k16UserException("No such circle: {}".format(circle_name)) _id = request.json.get("id", None) if _id: tool = ToolDescription.find_by_id(_id) if tool is None: abort(404) logger.info("Updating tool: {}".format(tool)) tool.name = request.json["name"] tool.circle = circle tool.description = request.json["description"] else: logger.info("Creating new tooldescription: {}".format( request.json["name"])) tool = ToolDescription(request.json["name"], request.json["description"], circle) db.session.add(tool) db.session.commit() db.session.flush() logger.info("Update tool: {}".format(tool.name)) return jsonify(tool_to_json(tool))
def _data_company_save(): contact_id = request.json["contact"] contact = Account.find_account_by_id(contact_id) if not contact: raise P2k16UserException("No such account: {}".format(contact_id)) _id = request.json.get("id", None) if _id: company = Company.find_by_id(_id) if company is None: abort(404) logger.info("Updating company: {}".format(company)) company.name = request.json["name"] company.contact_id = contact.id company.active = request.json["active"] else: name = request.json["name"] active = request.json["active"] logger.info("Registering new company: {}".format(name)) company = Company(name, contact, active) db.session.add(company) db.session.commit() return jsonify(company_to_json(company, include_employees=True))
def open_doors(self, account: Account, doors): can_open_door = authz_management.can_haz_door_access(account, doors) if not can_open_door: f = "{} does not have an active membership, or lacks door circle membership" raise P2k16UserException(f.format(account.display_name())) if not event_management.has_opened_door(account): system = Account.find_account_by_username("system") logger.info("First door opening for {}".format(account)) badge_management.create_badge(account, system, "first-door-opening") for door in doors: lf = "Opening door. username={}, door={}, open_time={}" logger.info(lf.format(account.username, door.key, door.open_time)) event_management.save_event(OpenDoorEvent(door.key)) # Make sure everything has been written to the database before actually opening the door. db.session.flush() # TODO: move this to a handler that runs after the transaction is done # TODO: we can look at the responses and see if they where successfully sent/received. for door in doors: if isinstance(door, DlockDoor): self.dlock.open(door) elif isinstance(door, MqttDoor): self.mqtt.open(door) else: P2k16TechnicalException("Unknown kind of door")
def member_get_details(account): # Get mapping from account to stripe_id stripe_customer_id = get_stripe_customer(account) details = {} details['stripe_pubkey'] = os.environ.get('STRIPE_PUBLIC_KEY') try: # Get payment details details['card'] = "N / A" details['card_exp'] = "" details['stripe_price'] = "0" details['stripe_subscription_status'] = "none" if stripe_customer_id is not None: # Get customer object cu = stripe.Customer.retrieve(stripe_customer_id.stripe_id) if len(cu.sources.data) > 0: card = cu.sources.data[0] details['card'] = "**** **** **** " + card.last4 details['card_exp'] = "%r/%r" % (card.exp_month, card.exp_year) # Get stripe subscription to make sure it matches local database assert len(cu.subscriptions.data) <= 1 for sub in cu.subscriptions.data: details['stripe_subscription_status'] = sub.status details['stripe_price'] = sub.plan.amount / 100 # Get current membership membership = get_membership(account) if membership is not None: details['fee'] = membership.fee details['first_membership'] = membership.first_membership details['start_membership'] = membership.start_membership else: details['fee'] = 0 # Export payments payments = [] for pay in get_membetship_payments(account): payments.append({ 'id': pay.id, 'start_date': pay.start_date, 'end_date': pay.end_date, 'amount': float(pay.amount), 'payment_date': pay.payment_date }) details['payments'] = payments except stripe.error.StripeError as e: raise P2k16UserException( "Error reading data from Stripe. Contact [email protected] if the problem persists." ) return details
def checkout_tool(self, account: Account, tool: ToolDescription): # Check that user has correct circle and is paying member if not account_management.is_account_in_circle(account, tool.circle): raise P2k16UserException('{} is not in the {} circle'.format( account.display_name(), tool.circle.name)) if not membership_management.active_member(account) \ and len(Company.find_active_companies_with_account(account.id)) == 0: raise P2k16UserException( '{} does not have an active membership and is not employed in an active company' .format(account.display_name())) logger.info('Checking out tool. username={}, tool={}'.format( account.username, tool.name)) # Verify that tool is not checked out by someone else. Check in first if it is. checkout = ToolCheckout.find_by_tool(tool) if checkout: if checkout.account is account: raise P2k16UserException('Tools can only be checked out once.') logger.info( 'Tool checked out by someone else. Assuming control: username={}, tool={}, old_username={}' .format(account.username, tool.name, checkout.account.name)) self.checkin_tool(checkout.account, checkout.tool_description) # Make a new checkout reservation event_management.save_event( ToolCheckoutEvent(tool.name, datetime.now(), account)) checkout = ToolCheckout(tool, account, datetime.now()) db.session.add(checkout) # Make sure everything has been written to the database before actually opening the door. db.session.flush() # TODO: move this to a handler that runs after the transaction is done # TODO: we can look at the responses and see if they where successfully sent/received. # for topic, open_time in publishes: # logger.info("Sending message: {}: {}".format(topic, open_time)) # self._client.publish(topic, open_time) topic = self._mqtt_topic(tool=tool.name, action='unlock') payload = 'true' logger.info("Sending message: {}: {}".format(topic, payload)) self._client.publish(topic, payload)
def _load_circle_admin(account_id, circle_id, admin_id): account = Account.find_account_by_id(account_id) admin = Account.find_account_by_id(admin_id) circle = Circle.find_by_id(circle_id) if account is None or admin is None or circle is None: raise P2k16UserException('Bad values') return account, admin, circle
def add_member(self, account: Account, comment: Optional[str]): if comment: comment = comment.strip() if self.comment_required_for_membership and len(comment) == 0: raise P2k16UserException( "This circle requires memberships to have a comment") self.members.append(CircleMember(self, account, comment))
def service_authz_login(): username = request.json["username"] account = Account.find_account_by_username(username) password = request.json["password"] if not account: logger.info("Login: Bad login attempt, no such user: {}".format(username)) raise P2k16UserException("Invalid credentials") if not account.valid_password(password): logger.info("Login: Bad login attempt, wrong password: {}".format(username)) raise P2k16UserException("Invalid credentials") circles = account_management.get_circles_for_account(account.id) badges = badge_management.badges_for_account(account.id) logger.info("Login: username={}, circles={}".format(username, circles)) authenticated_account = auth.AuthenticatedAccount(account, circles) flask_login.login_user(authenticated_account) return jsonify(account_to_json(account, circles, badges))
def remove_account_from_circle(account: Account, circle: Circle, admin: Account): logger.info("Removing %s from circle %s, admin=%s" % (account.username, circle.name, admin.username)) _assert_can_admin_circle(admin, circle) if not is_account_in_circle(account, circle): raise P2k16UserException( "Account isn't a member of the circle, cannot be removed") circle.remove_member(account)
def add_account_to_circle(account: Account, circle: Circle, admin: Account, comment: str): logger.info("Adding %s to circle %s, admin=%s" % (account.username, circle.name, admin.username)) _assert_can_admin_circle(admin, circle) if is_account_in_circle(account, circle): raise P2k16UserException( "Account is already a member of the cirlce, cannot be added again") circle.add_member(account, comment)
def create_circle(name: str, description: str, comment_required_for_membership, management_style: CircleManagementStyle, admin_circle_name: Optional[str] = None, username: Optional[str] = None, comment: Optional[str] = None) -> Circle: c = Circle(name, description, comment_required_for_membership, management_style) if management_style == CircleManagementStyle.ADMIN_CIRCLE: if admin_circle_name is None: raise P2k16UserException( "An admin circle is required when management style is set to ADMIN_CIRCLE" ) admin_circle = Circle.find_by_name(admin_circle_name) if admin_circle is None: raise P2k16UserException( "No such circle: {}".format(admin_circle_name)) c.admin_circle = admin_circle elif management_style == CircleManagementStyle.SELF_ADMIN: if username is None: raise P2k16UserException( "An initial member's username is required when management style is set to " "SELF_ADMIN") account = Account.find_account_by_username(username) if account is None: raise P2k16UserException("No such account: {}".format(username)) c.add_member(account, comment or "") db.session.add(c) return c
def service_set_password(): old_password = flask.request.json["oldPassword"] new_password = flask.request.json["newPassword"] a = flask_login.current_user.account # type: Account if not a.valid_password(old_password): raise P2k16UserException("Bad password") else: account_management.set_password(a, new_password, old_password=old_password) db.session.commit() return jsonify({})
def remove_circle(admin: Account, circle: Circle): _assert_can_admin_circle(admin, circle) logger.info("Removing circle, id={}, admin={}".format(circle.id, admin.id)) c = Circle.get_by_id(circle.id) if c.management_style == CircleManagementStyle.SELF_ADMIN: ok = len(c.members) == 1 and c.members[0].account == admin if not ok: raise P2k16UserException( "A circle which is self-administrated must only contain the remover." ) elif c.management_style == CircleManagementStyle.ADMIN_CIRCLE: if len(c.members) != 0: raise P2k16UserException( "The circle has to be empty to be removed.") else: raise P2k16UserException("Unknown management style") Circle.delete_by_id(circle.id)
def register_account(username: str, email: str, name: str, password: str, phone: str) -> Account: account = Account.find_account_by_username(username) if account: raise P2k16UserException("Username is taken") account = Account.find_account_by_email(email) if account: raise P2k16UserException("Email is already registered") if name is None: raise P2k16UserException("Name cannot be empty.") if " " in username: raise P2k16UserException("Username cannot contain spaces") if not re.match(r"^[a-zA-Z0-9@._+-]+", username): raise P2k16UserException( "Username can only contain a-z, 0-9, @, ., _, + and -.") account = Account(username, email, name, phone, password) db.session.add(account) return account
def open_door(): a = flask_login.current_user.account dc = flask.current_app.config.door_client # type: DoorClient doors = [] for key in request.json["doors"]: if key in p2k16.core.door.doors: doors.append(p2k16.core.door.doors[key]) else: raise P2k16UserException("Unknown door: {}".format(key)) dc.open_doors(a, doors) db.session.commit() return jsonify(dict())
def member_cancel_membership(account): try: # Update local db membership = get_membership(account) db.session.delete(membership) # Update stripe stripe_customer_id = get_stripe_customer(account) for sub in stripe.Subscription.list(customer=stripe_customer_id): sub.delete(at_period_end=True) db.session.commit() except stripe.error.StripeError as e: logger.error("Stripe error: " + repr(e.json_body)) raise P2k16UserException( "Stripe error. Contact [email protected] if the problem persists." )
def create(): account = flask_login.current_user.account # type: Account title = request.json["title"] recipient_username = request.json.get("recipient", None) if recipient_username: recipient = Account.find_account_by_username(recipient_username) if not recipient: raise P2k16UserException( "No such username: {}".format(recipient_username)) else: recipient = account badge_management.create_badge(recipient, account, title) circles = account_management.get_circles_for_account(account.id) badges = badge_management.badges_for_account(account.id) db.session.commit() return jsonify(account_to_json(account, circles, badges))
def create_badge(receiver: Account, awarder: Account, title: str) -> AccountBadge: desc = _load_description(title) logger.info("Creating badge: title={}, receiver={}, awarder={}".format(title, receiver.username, awarder.username)) if desc: logger.info("desc.certification_circle={}".format(desc.certification_circle)) if desc: if desc.certification_circle: if not account_management.is_account_in_circle(awarder, desc.certification_circle): raise P2k16UserException("The awarder {} is not a valid certifier".format(awarder.username)) else: desc = BadgeDescription(title) db.session.add(desc) db.session.flush([desc]) ab = AccountBadge(receiver, awarder, desc) db.session.add(ab) db.session.flush() event_management.save_event(BadgeAwardedEvent(ab, desc)) return ab
def wrapper(*args, **kw): try: js.validate(flask.request.json, schema) return f(*args, **kw) except js.ValidationError as e: raise P2k16UserException(e.message)
def member_set_membership(account, membership_plan, membership_price): # TODO: Remove membership_price and look up price from model try: membership = get_membership(account) if membership_plan == 'none': member_cancel_membership(account) return True # --- Update membership in local db --- if membership is not None: if membership.fee is membership_price: # Nothing's changed. logger.info( "No membership change for user=%r, type=%r, amount=%r" % (account.username, membership_plan, membership_price)) return else: membership.fee = membership_price membership.start_membership = datetime.now() else: # New membership membership = Membership(membership_price) # --- Update membership in stripe --- # Get customer object stripe_customer_id = get_stripe_customer(account) if stripe_customer_id is None: raise P2k16UserException( "You must set a credit card before changing plan.") # Check for active subscription subscriptions = stripe.Subscription.list(customer=stripe_customer_id) if subscriptions is None or len(subscriptions) == 0: sub = stripe.Subscription.create(customer=stripe_customer_id, items=[{ "plan": membership_plan }]) else: sub = next(iter(subscriptions), None) stripe.Subscription.modify(sub.id, cancel_at_period_end=False, items=[{ 'id': sub['items']['data'][0].id, 'plan': membership_plan }], prorate=False) # Commit to db db.session.add(membership) db.session.commit() logger.info( "Successfully updated membership type for user=%r, type=%r, amount=%r" % (account.username, membership_plan, membership_price)) mail.send_new_member(account) return True except stripe.error.CardError as e: err = e.json_body.get('error', {}) msg = err.get('message') logger.info("Card processing failed for user=%r, error=%r" % (account.username, err)) raise P2k16UserException("Error charging credit card: %r" % msg) except stripe.error.StripeError as e: logger.error("Stripe error: " + repr(e.json_body)) raise P2k16UserException( "Stripe error. Contact [email protected] if the problem persists." )
def member_set_credit_card(account, stripe_token): # Get mapping from account to stripe_id stripe_customer_id = get_stripe_customer(account) try: if stripe_customer_id is None: # Create a new stripe customer and set source cu = stripe.Customer.create(description="Customer for %r" % account.name, email=account.email, source=stripe_token) stripe_customer_id = StripeCustomer(cu.stripe_id) logger.info("Created customer for user=%r" % account.username) else: # Get customer object cu = stripe.Customer.retrieve(stripe_customer_id.stripe_id) if cu is None or (hasattr(cu, 'deleted') and cu.deleted): logger.error( "Stripe customer does not exist. This should not happen! account=%r, stripe_id=%r" % (account.username, stripe_token)) raise P2k16UserException( "Set credit card invalid state. Contact [email protected]" ) # Create a new default card new_card = cu.sources.create(source=stripe_token) cu.default_source = new_card.id cu.save() # Delete any old cards for card in cu.sources.list(): if card.id != new_card.id: card.delete() # Commit to db db.session.add(stripe_customer_id) db.session.commit() # Check if there are any outstanding invoices on this account that needs billing for invoice in stripe.Invoice.list(customer=cu.stripe_id): if invoice.paid is False and invoice.closed is False and invoice.forgiven is False: invoice.pay() logger.info("Successfully updated credit card for user=%r" % account.username) return True except stripe.error.CardError as e: err = e.json_body.get('error', {}) msg = err.get('message') logger.info("Card processing failed for user=%r, error=%r" % (account.username, err)) raise P2k16UserException("Error updating credit card: %r" % msg) except stripe.error.StripeError as e: logger.error("Stripe error: " + repr(e.json_body)) raise P2k16UserException( "Error updating credit card due to stripe error. Contact [email protected] if the " "problem persists.")
def on_before_update(self): if self.management_style == CircleManagementStyle.SELF_ADMIN and len( self.members) == 0: raise P2k16UserException( "A circle which is self-administrated must have at least one member" )
def member_set_membership(account, membership_plan, membership_price): # TODO: Remove membership_price and look up price from model try: membership = get_membership(account) if membership_plan == 'none': member_cancel_membership(account) return True # --- Update membership in local db --- if membership is not None: if membership.fee is membership_price: # Nothing's changed. logger.info( "No membership change for user=%r, type=%r, amount=%r" % (account.username, membership_plan, membership_price)) return else: membership.fee = membership_price membership.start_membership = datetime.now() else: # New membership membership = Membership(membership_price) # --- Update membership in stripe --- # Get customer object stripe_customer_id = get_stripe_customer(account) new_sub = stripe.Subscription.create(customer=stripe_customer_id, items=[{ "plan": membership_plan }]) # Remove existing subscriptions for sub in stripe.Subscription.list(customer=stripe_customer_id): if new_sub.id != sub.id: sub.delete(at_period_end=False) # Commit to db db.session.add(membership) db.session.commit() logger.info( "Successfully updated membership type for user=%r, type=%r, amount=%r" % (account.username, membership_plan, membership_price)) return True except stripe.error.CardError as e: err = e.json_body.get('error', {}) msg = err.get('message') logger.info("Card processing failed for user=%r, error=%r" % (account.username, err)) raise P2k16UserException("Error charging credit card: %r" % msg) except stripe.error.StripeError as e: logger.error("Stripe error: " + repr(e.json_body)) raise P2k16UserException( "Stripe error. Contact [email protected] if the problem persists." )