Ejemplo n.º 1
0
def email_tickets():
    """Email tickets to those who haven't received them"""
    users_purchase_counts = (Purchase.query.filter_by(
        is_paid_for=True,
        state="paid").join(PriceTier, Product, ProductGroup).filter(
            ProductGroup.type.in_(RECEIPT_TYPES)).join(
                Purchase.owner).with_entities(User, func.count(
                    Purchase.id)).group_by(User).order_by(User.id))

    for user, purchase_count in users_purchase_counts:
        plural = purchase_count != 1 and "s" or ""

        msg = EmailMessage(
            "Your Electromagnetic Field Ticket%s" % plural,
            from_email=from_email("TICKETS_EMAIL"),
            to=[user.email],
        )

        already_emailed = set_tickets_emailed(user)
        msg.body = render_template("emails/receipt.txt",
                                   user=user,
                                   already_emailed=already_emailed)

        attach_tickets(msg, user)

        app.logger.info("Emailing %s receipt for %s tickets", user.email,
                        purchase_count)
        msg.send()

        db.session.commit()
Ejemplo n.º 2
0
def users():
    form = NewUserForm()

    if form.validate_on_submit():
        email, name = form.email.data, form.name.data
        user = User(email, name)

        db.session.add(user)
        db.session.commit()
        app.logger.info(
            "%s manually created new user with email %s and id: %s",
            current_user.id,
            email,
            user.id,
        )

        code = user.login_code(app.config["SECRET_KEY"])
        msg = EmailMessage(
            "Welcome to the EMF website",
            from_email=from_email("CONTACT_EMAIL"),
            to=[email],
        )
        msg.body = render_template("emails/manually-added-user.txt",
                                   user=user,
                                   code=code)
        msg.send()

        flash("Created account for: %s" % name)
        return redirect(url_for(".users"))

    users = User.query.order_by(User.id).options(joinedload(
        User.permissions)).all()
    return render_template("admin/users/users.html", users=users, form=form)
Ejemplo n.º 3
0
def stripe_payment_paid(payment: StripePayment):
    if payment.state == "paid":
        logger.info("Payment is already paid, ignoring")
        return

    if payment.state == "partrefunded":
        logger.info("Payment is already partially refunded, ignoring")
        return

    logger.info("Setting payment %s to paid", payment.id)
    payment.paid()
    db.session.commit()

    msg = EmailMessage(
        "Your EMF payment has been confirmed",
        from_email=from_email("TICKETS_EMAIL"),
        to=[payment.user.email],
    )

    already_emailed = set_tickets_emailed(payment.user)
    msg.body = render_template(
        "emails/tickets-paid-email-stripe.txt",
        user=payment.user,
        payment=payment,
        already_emailed=already_emailed,
    )

    if feature_enabled("ISSUE_TICKETS"):
        attach_tickets(msg, payment.user)

    msg.send()
    db.session.commit()
Ejemplo n.º 4
0
def transaction_reconcile(txn_id, payment_id):
    txn = BankTransaction.query.get_or_404(txn_id)
    payment = BankPayment.query.get_or_404(payment_id)

    form = ManualReconcilePaymentForm()
    if form.validate_on_submit():
        if form.reconcile.data:
            app.logger.info(
                "%s manually reconciling against payment %s (%s) by %s",
                current_user.name,
                payment.id,
                payment.bankref,
                payment.user.email,
            )

            if txn.payment:
                app.logger.error("Transaction already reconciled")
                flash("Transaction %s already reconciled" % txn.id)
                return redirect(url_for("admin.transactions"))

            payment.lock()

            if payment.state == "paid":
                app.logger.error("Payment has already been paid")
                flash("Payment %s already paid" % payment.id)
                return redirect(url_for("admin.transactions"))

            txn.payment = payment
            payment.paid()
            db.session.commit()

            msg = EmailMessage(
                "Electromagnetic Field ticket purchase update",
                from_email=from_email("TICKETS_EMAIL"),
                to=[payment.user.email],
            )

            already_emailed = set_tickets_emailed(payment.user)
            msg.body = render_template(
                "emails/tickets-paid-email-banktransfer.txt",
                user=payment.user,
                payment=payment,
                already_emailed=already_emailed,
            )

            if feature_enabled("ISSUE_TICKETS"):
                attach_tickets(msg, payment.user)

            msg.send()

            flash("Payment ID %s marked as paid" % payment.id)
            return redirect(url_for("admin.transactions"))

    return render_template("admin/accounts/txn-reconcile.html",
                           txn=txn,
                           payment=payment,
                           form=form)
Ejemplo n.º 5
0
def handle_free_tickets(flow: str, view: ProductView, basket: Basket):
    """The user is trying to "buy" only free tickets.

    This is effectively a payment stage, handled differently
    from the rest of the flow.
    """
    # They must be authenticated for this.
    if not current_user.is_authenticated:
        app.logger.warn("User is not authenticated, sending to login")
        flash("You must be logged in to buy additional free tickets")
        return redirect(
            url_for("users.login", next=url_for("tickets.main", flow=flow)))

    # We sell under-12 tickets to non-CfP users, to enforce capacity.
    # We don't let people order an under-12 ticket on its own.
    # However, CfP users need to be able to buy day and parking tickets.
    admissions_tickets = current_user.get_owned_tickets(
        type="admission_ticket")
    if not any(admissions_tickets) and not view.cfp_accepted_only:
        app.logger.warn(
            "User trying to buy free add-ons without an admission ticket")
        flash(
            "You must have an admissions ticket to buy additional free tickets"
        )
        return redirect(url_for("tickets.main", flow=flow))

    basket.user = current_user
    basket.check_out_free()
    db.session.commit()

    Basket.clear_from_session()

    msg = EmailMessage(
        "Your EMF ticket order",
        from_email=from_email("TICKETS_EMAIL"),
        to=[current_user.email],
    )

    already_emailed = set_tickets_emailed(current_user)
    msg.body = render_template(
        "emails/tickets-ordered-email-free.txt",
        user=current_user,
        basket=basket,
        already_emailed=already_emailed,
    )
    if feature_enabled("ISSUE_TICKETS"):
        attach_tickets(msg, current_user)

    msg.send()

    if len(basket.purchases) == 1:
        flash("Your ticket has been confirmed")
    else:
        flash("Your tickets have been confirmed")

    return redirect(url_for("users.purchases"))
Ejemplo n.º 6
0
def ticket_admin_email(title, template, **kwargs):
    if not app.config.get("TICKETS_NOTICE_EMAIL"):
        app.logger.warning("No tickets notice email configured, not sending")
        return

    msg = EmailMessage(
        title,
        from_email=from_email("TICKETS_EMAIL"),
        to=[app.config["TICKETS_NOTICE_EMAIL"][1]],
    )
    msg.body = render_template(template, **kwargs)
    msg.send()
Ejemplo n.º 7
0
def signup():
    if not request.args.get("code"):
        abort(404)

    uid = verify_signup_code(app.config["SECRET_KEY"], time.time(),
                             request.args.get("code"))
    if uid is None:
        flash(
            "Your signup link was invalid. Please note that they expire after 6 hours."
        )
        abort(404)

    user = User.query.get_or_404(uid)
    if not user.has_permission("admin"):
        app.logger.warn("Signup link resolves to non-admin user %s", user)
        abort(404)

    form = SignupForm()

    if current_user.is_authenticated:
        return redirect(url_for(".account"))

    if form.validate_on_submit():
        email, name = form.email.data, form.name.data
        user = User(email, name)

        if form.allow_promo.data:
            user.promo_opt_in = True

        db.session.add(user)
        db.session.commit()
        app.logger.info("Signed up new user with email %s and id %s", email,
                        user.id)

        msg = EmailMessage(
            "Welcome to the EMF website",
            from_email=from_email("CONTACT_EMAIL"),
            to=[email],
        )
        msg.body = render_template("emails/signup-user.txt", user=user)
        msg.send()

        login_user(user)

        return redirect(url_for(".account"))

    if request.args.get("email"):
        form.email.data = request.args.get("email")

    return render_template("account/signup.html", form=form)
Ejemplo n.º 8
0
def send_refund_email(request: RefundRequest, amount: Decimal) -> None:
    payment = request.payment
    msg = EmailMessage(
        "Your refund request has been processed",
        from_email=from_email("TICKETS_EMAIL"),
        to=[payment.user.email],
    )
    # TODO: handle situation where currency of refund is different from currency of payment.
    msg.body = render_template(
        "emails/refund-sent.txt",
        user=payment.user,
        amount=amount,
        request=request,
        currency=payment.currency,
    )
    msg.send()
Ejemplo n.º 9
0
def send_reminder(payment_id):
    payment = BankPayment.query.get_or_404(payment_id)

    form = SendReminderForm()
    if form.validate_on_submit():
        if form.remind.data:
            app.logger.info(
                "%s sending reminder email to %s <%s> for payment %s",
                current_user.name,
                payment.user.name,
                payment.user.email,
                payment.id,
            )

            payment.lock()

            if payment.reminder_sent_at:
                app.logger.error("Reminder for payment %s already sent",
                                 payment.id)
                flash("Cannot send duplicate reminder email for payment %s" %
                      payment.id)
                return redirect(url_for("admin.expiring"))

            msg = EmailMessage(
                "Electromagnetic Field: Your tickets will expire in five days",
                from_email=from_email("TICKETS_EMAIL"),
                to=[payment.user.email],
            )
            msg.body = render_template(
                "emails/tickets-reminder.txt",
                payment=payment,
                account=payment.recommended_destination,
            )
            msg.send()

            payment.reminder_sent_at = datetime.utcnow()
            db.session.commit()

            flash("Reminder email for payment %s sent" % payment.id)
            return redirect(url_for("admin.expiring"))

    return render_template(
        "admin/payments/payment-send-reminder.html",
        payment=payment,
        account=payment.recommended_destination,
        form=form,
    )
Ejemplo n.º 10
0
def login():
    if current_user.is_authenticated:
        return redirect(get_next_url())

    if request.args.get("code"):
        user = User.get_by_code(app.config["SECRET_KEY"],
                                request.args.get("code"))
        if user is not None:
            login_user(user)
            session.permanent = True
            return redirect(get_next_url())
        else:
            flash(
                "Your login link was invalid. Please enter your email address below to receive a new link."
            )

    form = LoginForm(request.form)
    if form.validate_on_submit():
        code = form._user.login_code(app.config["SECRET_KEY"])

        msg = EmailMessage(
            "Electromagnetic Field: Login details",
            from_email=from_email("TICKETS_EMAIL"),
            to=[form._user.email],
        )
        msg.body = render_template(
            "emails/login-code.txt",
            user=form._user,
            code=code,
            next_url=get_next_url(),
        )
        msg.send()

        flash("We've sent you an email with your login link")

    if request.args.get("email"):
        form.email.data = request.args.get("email")

    return render_template(
        "account/login.html",
        form=form,
        next=get_next_url(),
    )
Ejemplo n.º 11
0
def send_confirmation(payment: BankPayment):
    msg = EmailMessage(
        "Electromagnetic Field ticket purchase update",
        from_email=from_email("TICKETS_EMAIL"),
        to=[payment.user.email],
    )

    already_emailed = set_tickets_emailed(payment.user)
    msg.body = render_template(
        "emails/tickets-paid-email-banktransfer.txt",
        user=payment.user,
        payment=payment,
        already_emailed=already_emailed,
    )

    if feature_enabled("ISSUE_TICKETS"):
        attach_tickets(msg, payment.user)

    msg.send()
    db.session.commit()
Ejemplo n.º 12
0
def transfer_start(payment: BankPayment):
    if not feature_enabled("BANK_TRANSFER"):
        return redirect(url_for("tickets.pay"))

    if get_user_currency() == "EUR" and not feature_enabled("BANK_TRANSFER_EURO"):
        return redirect(url_for("tickets.pay"))

    logger.info("Created bank payment %s (%s)", payment.id, payment.bankref)

    # No extra preparation required for bank transfer. We can go straight to inprogress.

    if payment.currency == "GBP":
        days = app.config.get("EXPIRY_DAYS_TRANSFER")
    elif payment.currency == "EUR":
        days = app.config.get("EXPIRY_DAYS_TRANSFER_EURO")

    if days is None:
        raise Exception("EXPIRY_DAYS_TRANSFER(_EURO) not set")

    payment.expires = datetime.utcnow() + timedelta(days=days)
    payment.state = "inprogress"

    for purchase in payment.purchases:
        purchase.set_state("payment-pending")

    db.session.commit()

    msg = EmailMessage(
        "Your EMF ticket purchase",
        from_email=from_email("TICKETS_EMAIL"),
        to=[current_user.email],
    )
    msg.body = render_template(
        "emails/tickets-purchased-email-banktransfer.txt",
        user=current_user,
        payment=payment,
    )
    msg.send()

    return redirect(url_for("payments.transfer_waiting", payment_id=payment.id))
Ejemplo n.º 13
0
def send_email_for_proposal(proposal, reason="still-considered", from_address=None):
    if reason == "accepted":
        subject = 'Your EMF proposal "%s" has been accepted!' % proposal.title
        template = "cfp_review/email/accepted_msg.txt"

    elif reason == "still-considered":
        subject = 'We\'re still considering your EMF proposal "%s"' % proposal.title
        template = "cfp_review/email/not_accepted_msg.txt"

    elif reason == "rejected":
        proposal.has_rejected_email = True
        subject = 'Your EMF proposal "%s" was not accepted.' % proposal.title
        template = "emails/cfp-rejected.txt"

    elif reason == "check-your-slot":
        subject = (
            "Your EMF proposal '%s' has been scheduled, please check your slot"
            % proposal.title
        )
        template = "emails/cfp-check-your-slot.txt"

    elif reason == "please-finalise":
        subject = "We need information about your EMF proposal '%s'" % proposal.title
        template = "emails/cfp-please-finalise.txt"

    elif reason == "reserve-list":
        subject = "Your EMF proposal '%s', and EMF tickets" % proposal.title
        template = "emails/cfp-reserve-list.txt"

    elif reason == "scheduled":
        subject = "Your EMF %s has been scheduled ('%s')" % (
            proposal.human_type,
            proposal.title,
        )
        template = "emails/cfp-slot-scheduled.txt"

    elif reason == "moved":
        subject = "Your EMF %s slot has been moved ('%s')" % (
            proposal.human_type,
            proposal.title,
        )
        template = "emails/cfp-slot-moved.txt"

    else:
        raise Exception("Unknown cfp proposal email type %s" % reason)

    app.logger.info("Sending %s email for proposal %s", reason, proposal.id)

    send_from = from_email("CONTENT_EMAIL")
    if from_address:
        send_from = from_address

    msg = EmailMessage(subject, from_email=send_from, to=[proposal.user.email])
    msg.body = render_template(
        template,
        user=proposal.user,
        proposal=proposal,
        reserve_ticket_link=app.config["RESERVE_LIST_TICKET_LINK"],
    )

    msg.send()
Ejemplo n.º 14
0
def transfer(ticket_id):
    try:
        ticket = current_user.owned_purchases.filter_by(id=ticket_id).one()
    except NoResultFound:
        abort(404)

    if not ticket.is_paid_for:
        flash("Unpaid tickets cannot be transferred")
        return redirect(url_for("users.purchases"))

    if not ticket.product.get_attribute("is_transferable"):
        flash("This purchase cannot be transferred")
        return redirect(url_for("users.purchases"))

    form = TicketTransferForm()

    if form.validate_on_submit():
        email = form.email.data

        if not User.does_user_exist(email):
            new_user = True

            # Create a new user to transfer the ticket to
            to_user = User(email, form.name.data)
            db.session.add(to_user)
            db.session.commit()

        else:
            new_user = False
            to_user = User.get_by_email(email)

        ticket = Ticket.query.with_for_update().get(ticket_id)
        assert ticket.owner_id == current_user.id

        ticket.transfer(from_user=current_user, to_user=to_user)
        db.session.commit()

        app.logger.info("Ticket %s transferred from %s to %s", ticket,
                        current_user, to_user)

        # Alert the users via email
        code = to_user.login_code(app.config["SECRET_KEY"])

        msg = EmailMessage(
            "You've been sent a ticket to EMF!",
            from_email=from_email("TICKETS_EMAIL"),
            to=[to_user.email],
        )

        already_emailed = set_tickets_emailed(to_user)
        msg.body = render_template(
            "emails/ticket-transfer-new-owner.txt",
            to_user=to_user,
            from_user=current_user,
            new_user=new_user,
            code=code,
            already_emailed=already_emailed,
        )

        if feature_enabled("ISSUE_TICKETS"):
            attach_tickets(msg, to_user)

        msg.send()
        db.session.commit()

        msg = EmailMessage(
            "You sent someone an EMF ticket",
            from_email=from_email("TICKETS_EMAIL"),
            to=[current_user.email],
        )
        msg.body = render_template(
            "emails/ticket-transfer-original-owner.txt",
            to_user=to_user,
            from_user=current_user,
        )

        msg.send()

        flash("Your ticket was transferred.")
        return redirect(url_for("users.purchases"))

    return render_template("tickets/transfer.html", ticket=ticket, form=form)
Ejemplo n.º 15
0
def tickets_issue_free(email):
    user = User.get_by_email(email)

    if user is None:
        form = IssueFreeTicketsNewUserForm()
        new_user = True
    else:
        form = IssueTicketsForm()
        new_user = False

    free_pts = (PriceTier.query.join(Product).filter(~PriceTier.prices.any(
        Price.price_int > 0)).order_by(Product.name).all())

    form.add_price_tiers(free_pts)

    if form.validate_on_submit():
        if not user:
            app.logger.info("Creating new user with email %s and name %s",
                            email, form.name.data)
            user = User(email, form.name.data)
            db.session.add(user)
            flash("Created account for %s" % email)

        basket = form.create_basket(user)
        app.logger.info("Admin basket for %s %s", user.email, basket)

        try:
            basket.create_purchases()
            basket.ensure_purchase_capacity()
            assert basket.total == 0

        except CapacityException as e:
            db.session.rollback()
            app.logger.warn("Limit exceeded creating admin tickets: %s", e)
            return redirect(url_for(".tickets_issue_free", email=email))

        for p in basket.purchases:
            p.set_state("paid")

        app.logger.info("Allocated %s tickets to user", len(basket.purchases))
        db.session.commit()

        code = user.login_code(app.config["SECRET_KEY"])
        ticket_noun = "tickets" if len(basket.purchases) > 1 else "ticket"
        msg = EmailMessage(
            f"Your complimentary {ticket_noun} to Electromagnetic Field",
            from_email=from_email("TICKETS_EMAIL"),
            to=[user.email],
        )

        already_emailed = set_tickets_emailed(user)
        msg.body = render_template(
            "emails/tickets-free.txt",
            user=user,
            code=code,
            tickets=basket.purchases,
            new_user=new_user,
            already_emailed=already_emailed,
        )

        if feature_enabled("ISSUE_TICKETS"):
            attach_tickets(msg, user)

        msg.send()
        db.session.commit()

        flash(f"Allocated {len(basket.purchases)} {ticket_noun}")
        return redirect(url_for(".tickets_issue"))
    return render_template("admin/tickets/tickets-issue-free.html",
                           form=form,
                           user=user,
                           email=email)
Ejemplo n.º 16
0
def tickets_reserve(email):
    user = User.get_by_email(email)

    if user is None:
        form = ReserveTicketsNewUserForm()
        new_user = True
    else:
        form = ReserveTicketsForm()
        new_user = False

    pts = (PriceTier.query.join(Product,
                                ProductGroup).order_by(ProductGroup.name,
                                                       Product.display_name,
                                                       Product.id).all())

    form.add_price_tiers(pts)

    if form.validate_on_submit():
        if not user:
            name = form.name.data

            app.logger.info("Creating new user with email %s and name %s",
                            email, name)
            user = User(email, name)
            flash("Created account for %s" % name)
            db.session.add(user)

        basket = form.create_basket(user)

        app.logger.info("Admin basket for %s %s", user.email, basket)

        try:
            basket.create_purchases()
            basket.ensure_purchase_capacity()

            db.session.commit()

        except CapacityException as e:
            db.session.rollback()
            app.logger.warn("Limit exceeded creating admin tickets: %s", e)
            return redirect(url_for(".tickets_reserve", email=email))

        code = user.login_code(app.config["SECRET_KEY"])
        ticket_noun = "tickets" if len(basket.purchases) > 1 else "ticket"
        msg = EmailMessage(
            f"Your reserved {ticket_noun} to EMF",
            from_email=from_email("TICKETS_EMAIL"),
            to=[user.email],
        )

        msg.body = render_template(
            "emails/tickets-reserved.txt",
            user=user,
            code=code,
            tickets=basket.purchases,
            new_user=new_user,
            currency=form.currency.data,
        )

        msg.send()
        db.session.commit()

        flash(f"Reserved {ticket_noun} and emailed {user.email}")
        return redirect(url_for(".tickets_issue"))

    return render_template("admin/tickets/tickets-reserve.html",
                           form=form,
                           pts=pts,
                           user=user)