remove pricing2
This commit is contained in:
parent
263f68ecec
commit
6f7c99963b
|
@ -1,167 +0,0 @@
|
|||
{% extends 'default.html' %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
|
||||
{% block title %}
|
||||
Pricing
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style type="text/css">
|
||||
/**
|
||||
* The CSS shown here will not be introduced in the Quickstart guide, but shows
|
||||
* how you can use CSS to style your Element's container.
|
||||
*/
|
||||
.StripeElement {
|
||||
box-sizing: border-box;
|
||||
|
||||
height: 40px;
|
||||
|
||||
padding: 10px 12px;
|
||||
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
|
||||
box-shadow: 0 1px 3px 0 #e6ebf1;
|
||||
-webkit-transition: box-shadow 150ms ease;
|
||||
transition: box-shadow 150ms ease;
|
||||
}
|
||||
|
||||
.StripeElement--focus {
|
||||
box-shadow: 0 1px 3px 0 #cfd7df;
|
||||
}
|
||||
|
||||
.StripeElement--invalid {
|
||||
border-color: #fa755a;
|
||||
}
|
||||
|
||||
.StripeElement--webkit-autofill {
|
||||
background-color: #fefde5 !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block default_content %}
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="card-category">Premium</div>
|
||||
<div class="display-4 my-6">$20/year</div>
|
||||
<ul class="list-unstyled leading-loose">
|
||||
<li><i class="fe fe-check text-success mr-2" aria-hidden="true"></i> Infinite Emails</li>
|
||||
<li><i class="fe fe-check text-success mr-2" aria-hidden="true"></i> Custom Alias</li>
|
||||
<li><i class="fe fe-check text-success mr-2" aria-hidden="true"></i> Privacy protected</li>
|
||||
<li><i class="fe fe-check text-success mr-2" aria-hidden="true"></i> Infinite Login</li>
|
||||
<li><i class="fe fe-check text-success mr-2" aria-hidden="true"></i>
|
||||
Support us and our application partners
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-lg-6">
|
||||
<div class="display-6">
|
||||
The payment is processed by <a href="https://stripe.com" target="_blank">Stripe</a>. <br>
|
||||
Your card number is never stored on our server.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<form method="post" id="payment-form">
|
||||
<div class="form-group">
|
||||
<label for="card-element" class="form-label">
|
||||
Credit or debit card
|
||||
</label>
|
||||
<div id="card-element">
|
||||
<!-- A Stripe Element will be inserted here. -->
|
||||
</div>
|
||||
|
||||
<!-- Used to display form errors. -->
|
||||
<div id="card-errors" role="alert" class="text-danger"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success">Upgrade</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Create a Stripe client.
|
||||
var stripe = Stripe('{{ stripe_api }}');
|
||||
|
||||
// Create an instance of Elements.
|
||||
var elements = stripe.elements();
|
||||
|
||||
// Custom styling can be passed to options when creating an Element.
|
||||
// (Note that this demo uses a wider set of styles than the guide below.)
|
||||
var style = {
|
||||
base: {
|
||||
color: '#32325d',
|
||||
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
|
||||
fontSmoothing: 'antialiased',
|
||||
fontSize: '16px',
|
||||
'::placeholder': {
|
||||
color: '#aab7c4'
|
||||
}
|
||||
},
|
||||
invalid: {
|
||||
color: '#fa755a',
|
||||
iconColor: '#fa755a'
|
||||
}
|
||||
};
|
||||
|
||||
// Create an instance of the card Element.
|
||||
// the postal code is asked on Safari but not on other browsers ...
|
||||
// Disable it explicitly
|
||||
var card = elements.create('card', {hidePostalCode: true, style: style});
|
||||
|
||||
// Add an instance of the card Element into the `card-element` <div>.
|
||||
card.mount('#card-element');
|
||||
|
||||
// Handle real-time validation errors from the card Element.
|
||||
card.addEventListener('change', function (event) {
|
||||
var displayError = document.getElementById('card-errors');
|
||||
if (event.error) {
|
||||
displayError.textContent = event.error.message;
|
||||
} else {
|
||||
displayError.textContent = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle form submission.
|
||||
var form = document.getElementById('payment-form');
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
stripe.createToken(card).then(function (result) {
|
||||
if (result.error) {
|
||||
// Inform the user if there was an error.
|
||||
var errorElement = document.getElementById('card-errors');
|
||||
errorElement.textContent = result.error.message;
|
||||
} else {
|
||||
// Send the token to your server.
|
||||
stripeTokenHandler(result.token);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Submit the form with the token ID.
|
||||
function stripeTokenHandler(token) {
|
||||
// Insert the token ID into the form so it gets submitted to the server
|
||||
var form = document.getElementById('payment-form');
|
||||
var hiddenInput = document.createElement('input');
|
||||
hiddenInput.setAttribute('type', 'hidden');
|
||||
hiddenInput.setAttribute('name', 'stripeToken');
|
||||
hiddenInput.setAttribute('value', token.id);
|
||||
form.appendChild(hiddenInput);
|
||||
|
||||
// Submit the form
|
||||
form.submit();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,81 +0,0 @@
|
|||
import stripe
|
||||
from flask import render_template, request, flash, redirect, url_for
|
||||
from flask_login import login_required, current_user
|
||||
from stripe.error import CardError
|
||||
|
||||
from app.config import STRIPE_API, STRIPE_YEARLY_PLAN
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.email_utils import notify_admin
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import PlanEnum
|
||||
|
||||
|
||||
@dashboard_bp.route("/pricing", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def pricing():
|
||||
# sanity check: make sure this page is only for free user that has never subscribed before
|
||||
# case user unsubscribe and re-subscribe will be handled later
|
||||
if current_user.is_premium():
|
||||
flash("You are already a premium user", "warning")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
if (
|
||||
current_user.stripe_customer_id
|
||||
or current_user.stripe_card_token
|
||||
or current_user.stripe_subscription_id
|
||||
):
|
||||
raise Exception("only user not exist on stripe can view this page")
|
||||
|
||||
if stripe.Customer.list(email=current_user.email):
|
||||
raise Exception("user email is already used on stripe!")
|
||||
|
||||
if request.method == "POST":
|
||||
plan = PlanEnum.yearly
|
||||
|
||||
stripe_token = request.form.get("stripeToken")
|
||||
LOG.d("stripe card token %s for plan %s", stripe_token, plan)
|
||||
current_user.stripe_card_token = stripe_token
|
||||
|
||||
try:
|
||||
customer = stripe.Customer.create(
|
||||
source=stripe_token,
|
||||
email=current_user.email,
|
||||
metadata={"id": current_user.id},
|
||||
name=current_user.name,
|
||||
)
|
||||
except CardError as e:
|
||||
LOG.exception("payment problem, code:%s", e.code)
|
||||
flash(
|
||||
"Payment refused with error {e.message}. Could you re-try with another card please?",
|
||||
"danger",
|
||||
)
|
||||
else:
|
||||
LOG.d("stripe customer %s", customer)
|
||||
current_user.stripe_customer_id = customer.id
|
||||
|
||||
stripe_plan = STRIPE_YEARLY_PLAN
|
||||
subscription = stripe.Subscription.create(
|
||||
customer=current_user.stripe_customer_id,
|
||||
items=[{"plan": stripe_plan}],
|
||||
expand=["latest_invoice.payment_intent"],
|
||||
)
|
||||
|
||||
LOG.d("stripe subscription %s", subscription)
|
||||
|
||||
current_user.stripe_subscription_id = subscription.id
|
||||
|
||||
db.session.commit()
|
||||
|
||||
if subscription.latest_invoice.payment_intent.status == "succeeded":
|
||||
LOG.d("payment successful for user %s", current_user)
|
||||
current_user.plan = plan
|
||||
current_user.plan_expiration = None
|
||||
db.session.commit()
|
||||
flash("Thanks for your subscription!", "success")
|
||||
notify_admin(
|
||||
f"user {current_user.email} has finished subscription {plan}"
|
||||
)
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
return render_template("dashboard/pricing.html", stripe_api=STRIPE_API)
|
|
@ -522,3 +522,22 @@ class ForwardEmail(db.Model, ModelMixin):
|
|||
reply_email = db.Column(db.String(128), nullable=False)
|
||||
|
||||
gen_email = db.relationship(GenEmail)
|
||||
|
||||
|
||||
class Subscription(db.Model, ModelMixin):
|
||||
# Come from Paddle
|
||||
cancel_url = db.Column(db.String(1024), nullable=False)
|
||||
update_url = db.Column(db.String(1024), nullable=False)
|
||||
subscription_id = db.Column(db.String(1024), nullable=False, unique=True)
|
||||
event_time = db.Column(ArrowType, nullable=False)
|
||||
next_bill_date = db.Column(db.Date, nullable=False)
|
||||
|
||||
cancelled = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
plan = db.Column(db.Enum(PlanEnum), nullable=False)
|
||||
|
||||
user_id = db.Column(
|
||||
db.ForeignKey(User.id, ondelete="cascade"), nullable=False, unique=True
|
||||
)
|
||||
|
||||
user = db.relationship(User)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import arrow
|
||||
|
||||
from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN
|
||||
from app.extensions import db
|
||||
from app.models import generate_email, User, GenEmail, PlanEnum
|
||||
from app.models import generate_email, User, GenEmail
|
||||
|
||||
|
||||
def test_generate_email(flask_client):
|
||||
|
@ -38,6 +40,7 @@ def test_suggested_emails_for_user_who_cannot_create_new_email(flask_client):
|
|||
user = User.create(
|
||||
email="a@b.c", password="password", name="Test User", activated=True
|
||||
)
|
||||
user.trial_expiration = arrow.now().shift(days=-1)
|
||||
db.session.commit()
|
||||
|
||||
# make sure user runs out of quota to create new email
|
||||
|
|
Loading…
Reference in New Issue