Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add billing #329

Merged
merged 41 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
dafd449
Add trial period
riggraz Apr 19, 2024
bff353a
fix specs
riggraz Apr 19, 2024
20b53bd
fix specs
riggraz Apr 19, 2024
d748fe4
fix specs
riggraz Apr 19, 2024
b64bc74
fix specs
riggraz Apr 19, 2024
f7936bf
fix specs
riggraz Apr 19, 2024
a183ab7
fix application_controller check_tenant_subscription
riggraz Apr 19, 2024
f72bba1
fix
riggraz Apr 19, 2024
e3b223a
default to 7 days if TRIAL_PERIOD_DAYS env var not defined
riggraz Apr 20, 2024
e2bb684
refactor tenant_billings factory
riggraz Apr 20, 2024
16c0fc3
Add basic billing page
riggraz Apr 20, 2024
f819633
add basic payment workflow
riggraz Apr 20, 2024
07270de
Add webhook endpoint for fulfilling completed subscriptions
riggraz Apr 20, 2024
3d428b1
init subscription_ends_at
riggraz Apr 21, 2024
4cdc79f
fix specs
riggraz Apr 21, 2024
9c85a18
add webhook for subscription update + various improvements
riggraz Apr 21, 2024
bf1e85c
fix codeql alert
riggraz Apr 21, 2024
872d106
add tos and pp
riggraz Apr 21, 2024
ddb067f
Improve billing page style
riggraz Apr 24, 2024
5e78e58
Add authentication to billing pages
riggraz Apr 24, 2024
f5e5440
Show "choose another plan" link after 5 seconds
riggraz Apr 24, 2024
4463da4
Move billing page to billing subdomain
riggraz Apr 27, 2024
2132dd2
move everything related to billing to billing subdomain
riggraz Apr 29, 2024
9cc2257
move stripe public key to env variable
riggraz Apr 29, 2024
7bc74c1
disable user sign out on billing pages
riggraz Apr 29, 2024
ae835ba
refactor billing_controller
riggraz Apr 29, 2024
d6850f9
add trial period and tos acceptance in tenant signup
riggraz Apr 29, 2024
0f3e2be
refactor
riggraz Apr 30, 2024
8e89470
Add smooth scrolling to checkout form
riggraz Apr 30, 2024
542b2d8
add some tenant subscription confirmation emails
riggraz Apr 30, 2024
426575f
update some mailer settings
riggraz Apr 30, 2024
37087b7
add some mailers
riggraz May 1, 2024
e9a0db2
add 'owner' method to tenant
riggraz May 1, 2024
99e7b05
add cron jobs to notify tenants in trial period
riggraz May 2, 2024
dbf52bb
increase scrollto checkout delay
riggraz May 2, 2024
24749fd
improve emails
riggraz May 3, 2024
fabb9a1
remove whenever gem and cron from web container
riggraz May 3, 2024
d2d1e0f
minor fixes
riggraz May 3, 2024
836c6c8
add alert near billing when trial ended
riggraz May 3, 2024
5874313
update emails
riggraz May 3, 2024
5f7a814
update rake task
riggraz May 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor
  • Loading branch information
riggraz committed Apr 30, 2024
commit 0f3e2be18186c7226053da3c6fd9d0eec743e196
16 changes: 13 additions & 3 deletions app/controllers/billing_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ def index
sign_in owner
tb.invalidate_auth_token

@prices = Stripe::Price.list({limit: 2}).data
@prices = Stripe::Price.list({
lookup_keys: [
Rails.application.stripe_monthly_lookup_key,
Rails.application.stripe_yearly_lookup_key
],
active: true
}).data
@prices = @prices.sort_by { |price| price.unit_amount }
else
redirect_to get_url_for(method(:root_url))
end
Expand Down Expand Up @@ -87,10 +94,13 @@ def webhook
if event['type'] == 'invoice.paid'
Current.tenant = get_tenant_from_customer_id(event.data.object.customer)

monthly_lookup_key = Rails.application.stripe_monthly_lookup_key
yearly_lookup_key = Rails.application.stripe_yearly_lookup_key

subscription_type = event.data.object.lines.data.last.price.lookup_key
return head :bad_request unless subscription_type == 'monthly' || subscription_type == 'yearly'
return head :bad_request unless subscription_type == monthly_lookup_key || subscription_type == yearly_lookup_key

subscription_duration = subscription_type == 'monthly' ? 1.month : 1.year
subscription_duration = subscription_type == monthly_lookup_key ? 1.month : 1.year
Current.tenant.tenant_billing.update!(
status: 'active',
subscription_ends_at: Time.current + subscription_duration
Expand Down
131 changes: 73 additions & 58 deletions app/javascript/components/Billing/PricingTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,71 +5,86 @@ interface Props {
currentPrice: string;
setCurrentPrice: (priceId: string) => void;
setChosenPrice: (priceId: string) => void;
stripeMonthlyLookupKey: string;
stripeYearlyLookupKey: string;
}

const PricingTable = ({ prices, currentPrice, setCurrentPrice, setChosenPrice }: Props) => (
<div className="pricingTable">
<h3>Choose your plan</h3>
const PricingTable = ({
prices,
currentPrice,
setCurrentPrice,
setChosenPrice,
stripeMonthlyLookupKey,
stripeYearlyLookupKey,
}: Props) => {
const monthlyPlanUnitAmount = prices.find(p => p.lookup_key === stripeMonthlyLookupKey).unit_amount;
const yearlyPlanUnitAmount = prices.find(p => p.lookup_key === stripeYearlyLookupKey).unit_amount;
const yearlyPlanDiscount = 1 - yearlyPlanUnitAmount / (monthlyPlanUnitAmount*12)

<ul className="pricingPlansNav">
{
prices && prices.map((price) => (
<li key={price.id} className="nav-item">
<a className={`nav-link${currentPrice === price.id ? ' active' : ''}`} onClick={() => setCurrentPrice(price.id)}>
{price.lookup_key}
{
price.lookup_key === 'yearly' &&
<span className="yearlyPlanDiscount">-{(1 - ((prices.find(p => p.lookup_key === 'yearly').unit_amount) / (prices.find(p => p.lookup_key === 'monthly').unit_amount*12))) * 100}%</span>
}
</a>
</li>
))
}
</ul>
{
prices && prices.filter(price => price.id === currentPrice).map((price) => (
<div key={price.id} className="pricingTableColumn">
<h4>{ price.lookup_key === 'monthly' ? 'Monthly subscription' : 'Yearly subscription' }</h4>
return (
<div className="pricingTable">
<h3>Choose your plan</h3>

<div className="priceContainer">
<p className="price">
<span className="amount">{price.unit_amount / 100.0}</span>
&nbsp;
<span className="currency">{price.currency}</span>
&nbsp;/&nbsp;
<span className="period">{price.recurring.interval}</span>
</p>
<ul className="pricingPlansNav">
{
prices && prices.map((price) => (
<li key={price.id} className="nav-item">
<a className={`nav-link${currentPrice === price.id ? ' active' : ''}`} onClick={() => setCurrentPrice(price.id)}>
{price.lookup_key}
{
price.lookup_key === stripeYearlyLookupKey &&
<span className="yearlyPlanDiscount">-{yearlyPlanDiscount * 100}%</span>
}
</a>
</li>
))
}
</ul>
{
prices && prices.filter(price => price.id === currentPrice).map((price) => (
<div key={price.id} className="pricingTableColumn">
<h4>{ price.lookup_key === stripeMonthlyLookupKey ? 'Monthly subscription' : 'Yearly subscription' }</h4>

{
price.lookup_key === 'yearly' &&
<p className="priceYearly">
(
<span className="amount">{price.unit_amount / 100.0 / 12}</span>
&nbsp;
<span className="currency">{price.currency}</span>
&nbsp;/&nbsp;
<span className="period">month</span>
)
</p>
}
</div>
<div className="priceContainer">
<p className="price">
<span className="amount">{price.unit_amount / 100.0}</span>
&nbsp;
<span className="currency">{price.currency}</span>
&nbsp;/&nbsp;
<span className="period">{price.recurring.interval}</span>
</p>

<p className="description">
For most small-medium organizations.<br />
Bigger organizations can <a className="link" href="mailto:info@astuto.io">contact us</a> for a custom plan.
</p>
{
price.lookup_key === stripeYearlyLookupKey &&
<p className="priceYearly">
(
<span className="amount">{price.unit_amount / 100.0 / 12}</span>
&nbsp;
<span className="currency">{price.currency}</span>
&nbsp;/&nbsp;
<span className="period">month</span>
)
</p>
}
</div>

<ul className="features">
<li>All features</li>
<li>Unlimited feedback</li>
<li>Unlimited boards</li>
</ul>
<p className="description">
For most small-medium organizations.<br />
Bigger organizations can <a className="link" href="mailto:info@astuto.io">contact us</a> for a custom plan.
</p>

<button onClick={() => setChosenPrice(price.id)} className="btnPrimary">Subscribe</button>
</div>
))
}
</div>
);
<ul className="features">
<li>All features</li>
<li>Unlimited feedback</li>
<li>Unlimited boards</li>
</ul>

<button onClick={() => setChosenPrice(price.id)} className="btnPrimary">Subscribe</button>
</div>
))
}
</div>
);
};

export default PricingTable;
7 changes: 6 additions & 1 deletion app/javascript/components/Billing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ interface Props {
createCheckoutSessionUrl: string;
billingUrl: string;
manageSubscriptionUrl: string;

stripeMonthlyLookupKey: string;
stripeYearlyLookupKey: string;
stripePublicKey: string;
authenticityToken: string;
}
Expand All @@ -28,6 +29,8 @@ const Billing = ({
createCheckoutSessionUrl,
billingUrl,
manageSubscriptionUrl,
stripeMonthlyLookupKey,
stripeYearlyLookupKey,
stripePublicKey,
authenticityToken,
}: Props) => {
Expand Down Expand Up @@ -122,6 +125,8 @@ const Billing = ({
currentPrice={currentPrice}
setCurrentPrice={setCurrentPrice}
setChosenPrice={setChosenPrice}
stripeMonthlyLookupKey={stripeMonthlyLookupKey}
stripeYearlyLookupKey={stripeYearlyLookupKey}
/>
}

Expand Down
3 changes: 2 additions & 1 deletion app/views/billing/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
createCheckoutSessionUrl: create_checkout_session_url,
billingUrl: get_url_for(method(:request_billing_page_url)),
manageSubscriptionUrl: Rails.application.stripe_manage_subscription_url,

stripeMonthlyLookupKey: Rails.application.stripe_monthly_lookup_key,
stripeYearlyLookupKey: Rails.application.stripe_yearly_lookup_key,
stripePublicKey: Rails.application.stripe_public_key,
authenticityToken: form_authenticity_token
}
Expand Down
8 changes: 8 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,13 @@ def stripe_endpoint_secret
def stripe_manage_subscription_url
ENV["STRIPE_MANAGE_SUBSCRIPTION_URL"]
end

def stripe_monthly_lookup_key
ENV["STRIPE_MONTHLY_LOOKUP_KEY"]
end

def stripe_yearly_lookup_key
ENV["STRIPE_YEARLY_LOOKUP_KEY"]
end
end
end