mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
Oh hey look it's paypal
This commit is contained in:
parent
786c8f5cfc
commit
4082bbae2b
2
Gemfile
2
Gemfile
@ -26,6 +26,8 @@ gem 'authority'
|
||||
# Billing
|
||||
gem 'stripe'
|
||||
gem 'stripe_event'
|
||||
gem 'paypal_client'
|
||||
gem 'paypal-checkout-sdk'
|
||||
|
||||
# Design
|
||||
gem 'material_icons'
|
||||
|
||||
14
Gemfile.lock
14
Gemfile.lock
@ -1105,6 +1105,10 @@ GEM
|
||||
factory_bot_rails (5.1.1)
|
||||
factory_bot (~> 5.1.0)
|
||||
railties (>= 4.2.0)
|
||||
faraday (0.17.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
faye-websocket (0.10.9)
|
||||
eventmachine (>= 0.12.0)
|
||||
websocket-driver (>= 0.5.1)
|
||||
@ -1233,6 +1237,7 @@ GEM
|
||||
mixpanel-ruby (2.2.2)
|
||||
multi_json (1.14.1)
|
||||
multi_test (0.1.2)
|
||||
multipart-post (2.1.1)
|
||||
mustache (1.1.1)
|
||||
nenv (0.3.0)
|
||||
nested_form (0.3.2)
|
||||
@ -1263,6 +1268,13 @@ GEM
|
||||
activerecord (>= 4.0, < 6.1)
|
||||
parser (2.6.5.0)
|
||||
ast (~> 2.4.0)
|
||||
paypal-checkout-sdk (1.0.3)
|
||||
paypalhttp (~> 1.0.0)
|
||||
paypal_client (0.3.1)
|
||||
activesupport (> 4.2.8)
|
||||
faraday (~> 0.15)
|
||||
faraday_middleware (~> 0.12)
|
||||
paypalhttp (1.0.0)
|
||||
pg (1.2.1)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
@ -1523,6 +1535,8 @@ DEPENDENCIES
|
||||
newrelic_rpm
|
||||
paperclip
|
||||
paranoia
|
||||
paypal-checkout-sdk
|
||||
paypal_client
|
||||
pg (~> 1.1)
|
||||
pry
|
||||
puma (~> 4.3)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
class SubscriptionsController < ApplicationController
|
||||
protect_from_forgery except: :stripe_webhook
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :authenticate_user!, except: [:redeem]
|
||||
|
||||
before_action :set_navbar_actions
|
||||
before_action :set_sidenav_expansion
|
||||
@ -32,6 +32,58 @@ class SubscriptionsController < ApplicationController
|
||||
def show
|
||||
end
|
||||
|
||||
def prepay
|
||||
@invoices = current_user.paypal_invoices
|
||||
.where.not(status: 'CREATED')
|
||||
.includes(:page_unlock_promo_code)
|
||||
.order('id desc')
|
||||
|
||||
promo_code_ids = @invoices.map(&:page_unlock_promo_code_id).flatten
|
||||
@promo_codes = PageUnlockPromoCode.where(id: promo_code_ids)
|
||||
end
|
||||
|
||||
def redeem
|
||||
@code = PageUnlockPromoCode.find_by(code: params[:code])
|
||||
end
|
||||
|
||||
def prepay_redirect_to_paypal
|
||||
months = params[:months].to_i
|
||||
|
||||
# Create an invoice on Paypal to be paid
|
||||
ppi = PaypalService.create_prepay_invoice(months)
|
||||
|
||||
# Create a mirrored invoice of our own to mark paid later
|
||||
invoice = PaypalInvoice.create!(
|
||||
user: current_user,
|
||||
paypal_id: ppi.id,
|
||||
status: ppi.status,
|
||||
months: months,
|
||||
amount_cents: 100 * PaypalService.months_price(months),
|
||||
approval_url: ppi.links.detect { |l| l.rel == "approve" }.href
|
||||
)
|
||||
|
||||
# Send the user off to pay!
|
||||
# redirect_to PaypalService.checkout_url(invoice, prepay_path)
|
||||
redirect_to invoice.approval_url
|
||||
end
|
||||
|
||||
def capture_paypal_prepay
|
||||
# request = PayPalCheckoutSdk::Orders::OrdersCaptureRequest::new("APPROVED-ORDER-ID")
|
||||
|
||||
# begin
|
||||
# # Call API with your client and get a response for your call
|
||||
# response = client.execute(request)
|
||||
|
||||
# # If call returns body in response, you can get the deserialized version from the result attribute of the response
|
||||
# order = response.result
|
||||
# puts order
|
||||
# rescue PayPalHttp::HttpError => ioe
|
||||
# # Something went wrong server-side
|
||||
# puts ioe.status_code
|
||||
# puts ioe.headers["debug_id"]
|
||||
# end
|
||||
end
|
||||
|
||||
def change
|
||||
new_plan_id = params[:stripe_plan_id]
|
||||
possible_plan_ids = SubscriptionService.available_plans.pluck(:stripe_plan_id)
|
||||
|
||||
43
app/jobs/paypal_acceptance_wait_job.rb
Normal file
43
app/jobs/paypal_acceptance_wait_job.rb
Normal file
@ -0,0 +1,43 @@
|
||||
class PaypalAcceptanceWaitJob < ApplicationJob
|
||||
queue_as :paypal
|
||||
|
||||
def perform(*args)
|
||||
invoice_id = args.shift
|
||||
invoice = PaypalInvoice.find_by(paypal_id: invoice_id)
|
||||
|
||||
info = PaypalService.order_info(invoice_id)
|
||||
if info[:status] == 'CREATED'
|
||||
# If we're still in a CREATED state, keep requeuing for up to 24 hours
|
||||
if DateTime.current <= invoice.created_at + 24.hours
|
||||
PaypalAcceptanceWaitJob
|
||||
.set(wait: 30.seconds)
|
||||
.perform_later(invoice.paypal_id)
|
||||
end
|
||||
|
||||
elsif info[:status] == 'APPROVED'
|
||||
# Once a user has approved a payment, we need to capture that payment
|
||||
unless invoice.status == 'APPROVED'
|
||||
invoice.update(status: 'APPROVED')
|
||||
invoice.capture_funds!
|
||||
end
|
||||
|
||||
PaypalAcceptanceWaitJob
|
||||
.set(wait: 30.seconds)
|
||||
.perform_later(invoice.paypal_id)
|
||||
|
||||
elsif info[:status] == 'COMPLETED'
|
||||
# Once a payment has been captured, generate the code for use!
|
||||
unless invoice.status == 'COMPLETED'
|
||||
invoice.update(status: 'COMPLETED')
|
||||
invoice.generate_promo_code!
|
||||
end
|
||||
|
||||
else
|
||||
# Something unexpected happened! Wow!
|
||||
invoice.update(status: info[:status])
|
||||
raise info.inspect
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
37
app/models/paypal_invoice.rb
Normal file
37
app/models/paypal_invoice.rb
Normal file
@ -0,0 +1,37 @@
|
||||
class PaypalInvoice < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :page_unlock_promo_code, optional: true
|
||||
|
||||
after_create :watch_for_approval
|
||||
|
||||
def next_step_url
|
||||
# switch to receipt after approving
|
||||
self.approval_url
|
||||
end
|
||||
|
||||
def watch_for_approval
|
||||
PaypalAcceptanceWaitJob.perform_later(self.paypal_id)
|
||||
end
|
||||
|
||||
def capture_funds!
|
||||
PaypalService.capture_invoice_funds(self.paypal_id)
|
||||
end
|
||||
|
||||
def generate_promo_code!
|
||||
self.page_unlock_promo_code = PageUnlockPromoCode.create(
|
||||
code: 'PP' + (0...12).map { (65 + rand(26)).chr }.join,
|
||||
page_types: Rails.application.config.content_types[:premium].map(&:name),
|
||||
uses_remaining: 1,
|
||||
days_active: 30 * self.months.to_i,
|
||||
internal_description: "Prepaid with PayPal",
|
||||
description: "Prepaid Premium subscription"
|
||||
)
|
||||
self.save!
|
||||
end
|
||||
|
||||
def activateable?
|
||||
self.status == 'COMPLETED' &&
|
||||
page_unlock_promo_code.present? &&
|
||||
page_unlock_promo_code.uses_remaining > 0
|
||||
end
|
||||
end
|
||||
@ -30,6 +30,7 @@ class User < ApplicationRecord
|
||||
BillingPlan::PREMIUM_IDS.include?(self.selected_billing_plan_id) || active_promo_codes.any?
|
||||
end
|
||||
has_many :promotions, dependent: :destroy
|
||||
has_many :paypal_invoices
|
||||
|
||||
has_many :image_uploads, dependent: :destroy
|
||||
|
||||
|
||||
138
app/services/paypal_service.rb
Normal file
138
app/services/paypal_service.rb
Normal file
@ -0,0 +1,138 @@
|
||||
class PaypalService < Service
|
||||
include PayPalCheckoutSdk::Orders
|
||||
|
||||
def self.create_prepay_invoice(n_months)
|
||||
# Create a Paypal invoice to redirect to for payment
|
||||
request = PayPalCheckoutSdk::Orders::OrdersCreateRequest::new
|
||||
request.request_body({
|
||||
intent: "CAPTURE",
|
||||
purchase_units: [{
|
||||
amount: {
|
||||
currency_code: "USD",
|
||||
value: PaypalService.months_price(n_months),
|
||||
description: "Notebook.ai Premium (#{n_months} month#{'s' unless n_months == 1})",
|
||||
payment_instruction: "some text for paymen instruction",
|
||||
soft_descriptor: "soft descriptor"
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
begin
|
||||
response = PaypalService.client.execute(request)
|
||||
order = response.result
|
||||
|
||||
order
|
||||
|
||||
rescue PayPalHttp::HttpError => ioe
|
||||
# Something went wrong server-side
|
||||
# puts ioe.status_code
|
||||
# puts ioe.headers["debug_id"]
|
||||
raise ioe.inspect
|
||||
end
|
||||
end
|
||||
|
||||
def self.capture_invoice_funds(invoice_id)
|
||||
request = PayPalCheckoutSdk::Orders::OrdersCaptureRequest::new(invoice_id)
|
||||
|
||||
begin
|
||||
# Call API with your client and get a response for your call
|
||||
response = client.execute(request)
|
||||
|
||||
# If call returns body in response, you can get the deserialized version from the result attribute of the response
|
||||
order = response.result
|
||||
puts order
|
||||
|
||||
rescue PayPalHttp::HttpError => ioe
|
||||
# Something went wrong server-side
|
||||
puts ioe.status_code
|
||||
puts ioe.headers["debug_id"]
|
||||
end
|
||||
end
|
||||
|
||||
def self.checkout_url(invoice, return_path)
|
||||
app_host = Rails.env.production? ? 'https://www.notebook.ai' : 'http://localhost:3000'
|
||||
paypal_host = Rails.env.production? ? 'https://www.paypal.com' : 'https://www.sandbox.paypal.com'
|
||||
|
||||
values = {
|
||||
business: 'sb-43r7at861878@business.example.com',
|
||||
cmd: "_xclick",
|
||||
# image_url: 150x50 url
|
||||
|
||||
upload: 1,
|
||||
|
||||
invoice: invoice.paypal_id,
|
||||
item_name: "Notebook.ai Premium",
|
||||
item_number: "#{invoice.months} month#{'s' unless invoice.months == 1} of Premium",
|
||||
quantity: '1',
|
||||
amount: months_price(invoice.months),
|
||||
|
||||
no_note: 1,
|
||||
no_shipping: 1,
|
||||
|
||||
return: "#{app_host}#{return_path}",
|
||||
cancel_return: "#{app_host}/my/billing/prepay",
|
||||
}
|
||||
|
||||
"#{paypal_host}/cgi-bin/webscr?" + values.to_query
|
||||
end
|
||||
|
||||
def self.capture_payment(paypal_invoice_id)
|
||||
resp = Faraday.post("https://api.sandbox.paypal.com/v2/checkout/orders/#{paypal_invoice_id}/capture") do |req|
|
||||
# req.params['limit'] = 100
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.headers['Authorization'] = 'Basic <ARd0BEqsIN77DZvZc_R-VeuSR5jo_rYUhxao-ScHrwtBvfymH0XvizvDpd1sRykFT6arD3kDPaVaAz2s:EJPXLRDlIjxiI85O4LIma6MGISNbuwn9erZAJo3fGFvZNPmxSun7iqkAFLMtNOOwptMEai5IMrm1eRgR>'
|
||||
# req.body = {query: 'salmon'}.to_json
|
||||
end
|
||||
end
|
||||
|
||||
def self.order_info(order_id)
|
||||
request = OrdersGetRequest::new(order_id)
|
||||
response = client::execute(request)
|
||||
|
||||
puts "Status Code: " + response.status_code.to_s
|
||||
puts "Status: " + response.result.status
|
||||
puts "Order ID: " + response.result.id
|
||||
puts "Intent: " + response.result.intent
|
||||
puts "Links:"
|
||||
for link in response.result.links
|
||||
puts "\t#{link["rel"]}: #{link["href"]}\tCall Type: #{link["method"]}"
|
||||
end
|
||||
puts "Gross Amount: " + response.result.purchase_units[0].amount.currency_code + response.result.purchase_units[0].amount.value
|
||||
|
||||
{
|
||||
order_id: response.result.id,
|
||||
status: response.result.status
|
||||
}
|
||||
end
|
||||
|
||||
def self.months_price(n_months)
|
||||
case n_months
|
||||
when 1
|
||||
9.00
|
||||
when 3
|
||||
24.00
|
||||
when 6
|
||||
48.00
|
||||
when 12
|
||||
84.00
|
||||
else
|
||||
raise "Invalid month prepay: #{n_months}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.client
|
||||
@paypal_client ||= begin
|
||||
client_id = Rails.application.config.paypal[:client_id]
|
||||
client_secret = Rails.application.config.paypal[:client_secret]
|
||||
|
||||
environment = if Rails.env.production?
|
||||
PayPal::PayPalEnvironment.new(client_id, client_secret)
|
||||
else
|
||||
PayPal::SandboxEnvironment.new(client_id, client_secret)
|
||||
end
|
||||
|
||||
PayPal::PayPalHttpClient.new(environment)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@ -227,9 +227,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12 m12 l3 hide-on-small-only"></div>
|
||||
<div class="col s12 m12 l9">
|
||||
<div class="card">
|
||||
|
||||
209
app/views/subscriptions/prepay.html.erb
Normal file
209
app/views/subscriptions/prepay.html.erb
Normal file
@ -0,0 +1,209 @@
|
||||
<div class="row">
|
||||
<div class="col s12 m10 offset-m1">
|
||||
<div class="hoverable card">
|
||||
<div class="card-content">
|
||||
<div class="card-title">You can now purchase sharable Premium codes for yourself or others</div>
|
||||
<p>
|
||||
Making a purchase below will generate a single-use code that can be redeemed at any time for a Premium subscription on Notebook.ai.
|
||||
You can purchase these codes for yourself if you'd like to prepay for a certain amount of months without a recurring monthly subscription,
|
||||
or for others if you'd like to gift a Notebook.ai Premium membership to someone else! You can purchase as many codes as you'd like
|
||||
and redeem them whenever you'd like.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="center grey-text">Choose a Premium code to purchase</h5>
|
||||
<div class="row">
|
||||
<div class="col s12 m6">
|
||||
<%= link_to prepay_paypal_gateway_path(months: 1), class: 'black-text' do %>
|
||||
<div class="hoverable card blue lighten-4" style="height: 140px;">
|
||||
<div class="card-content">
|
||||
<div class="card-title">
|
||||
<i class="material-icons left">star</i>
|
||||
1 month of Premium
|
||||
</div>
|
||||
<p>
|
||||
All Premium features
|
||||
for <strong>$9.00</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="col s12 m6">
|
||||
<%= link_to prepay_paypal_gateway_path(months: 3), class: 'black-text' do %>
|
||||
<div class="hoverable card blue lighten-4" style="height: 140px;">
|
||||
<div class="card-content">
|
||||
<div class="card-title">
|
||||
<i class="material-icons left">star</i>
|
||||
3 months of Premium
|
||||
</div>
|
||||
<p>
|
||||
All Premium features
|
||||
for <strong>$24.00</strong><br />
|
||||
(save $3.00 compared to monthly)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="col s12 m6">
|
||||
<%= link_to prepay_paypal_gateway_path(months: 6), class: 'black-text' do %>
|
||||
<div class="hoverable card blue lighten-4" style="height: 140px;">
|
||||
<div class="card-content">
|
||||
<div class="card-title">
|
||||
<i class="material-icons left">star</i>
|
||||
6 months of Premium
|
||||
</div>
|
||||
<p>
|
||||
All Premium features
|
||||
for <strong>$48.00</strong><br />
|
||||
(save $6.00 compared to monthly)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="col s12 m6">
|
||||
<%= link_to prepay_paypal_gateway_path(months: 12), class: 'black-text' do %>
|
||||
<div class="hoverable card blue lighten-4" style="height: 140px;">
|
||||
<div class="card-content">
|
||||
<div class="card-title">
|
||||
<i class="material-icons left">star</i>
|
||||
12 months of Premium
|
||||
</div>
|
||||
<p>
|
||||
All Premium features
|
||||
for <strong>$84.00</strong><br />
|
||||
(save $24.00 compared to monthly)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12 m10 offset-m1">
|
||||
Selecting a package will take you to Paypal to complete your purchase, where you can pay from a Paypal balance or with a credit/debit card.
|
||||
Afterwards, you will be redirected here where you will find the code you've purchased listed below.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h5 class="center grey-text">Your purchased Premium codes</h5>
|
||||
<p class="center grey-text">
|
||||
Please note that codes may take up to 5 minutes to appear here after purchase.
|
||||
</p>
|
||||
|
||||
<% if current_user.on_premium_plan? && @invoices.any? %>
|
||||
<div class="card-panel green">
|
||||
Since you already have a Premium subscription active, you won't be able to activate any codes below.
|
||||
Activation links will appear again whenever you don't have Premium active.
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% @invoices.each do |invoice| %>
|
||||
<div class="col s12 m12 l6">
|
||||
<div class="hoverable card medium <%= 'blue lighten-5' if invoice.activateable? %>">
|
||||
<div class="card-content">
|
||||
<div class="card-title">
|
||||
<% if invoice.page_unlock_promo_code&.uses_remaining&.zero? %>
|
||||
<span class="badge orange black-text">used</span>
|
||||
<% end %>
|
||||
<%= pluralize invoice.months, 'month' %> of Premium
|
||||
</div>
|
||||
<p>
|
||||
<% if invoice.page_unlock_promo_code.present? %>
|
||||
<% if invoice.activateable? %>
|
||||
Promo code: <strong><%= invoice.page_unlock_promo_code.code %></strong>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% if invoice.status == 'APPROVED' %>
|
||||
<em>Processing payment...</em>
|
||||
<% else %>
|
||||
<%= link_to 'Awaiting payment', invoice.next_step_url %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="grey-text">
|
||||
<% unless invoice.page_unlock_promo_code&.uses_remaining&.zero? %>
|
||||
<% if invoice.status == 'CREATED' %>
|
||||
Ready for payment
|
||||
<% else %>
|
||||
Purchased
|
||||
<% end %>
|
||||
<%= time_ago_in_words invoice.created_at %> ago
|
||||
<% end %>
|
||||
</p>
|
||||
<% if invoice.page_unlock_promo_code %>
|
||||
<hr style="margin: 20px 0" />
|
||||
<p>
|
||||
<div>
|
||||
Unlocks these <%= pluralize invoice.page_unlock_promo_code.page_types.count, 'page type' %>
|
||||
for <%= invoice.months * 30 %> days:
|
||||
</div>
|
||||
<% invoice.page_unlock_promo_code.page_types.each do |page_type| %>
|
||||
<% page_type_class = page_type.constantize %>
|
||||
<%= link_to polymorphic_path(page_type_class) do %>
|
||||
<i class="material-icons <%= page_type_class.color %>-text tooltipped" data-tooltip="<%= page_type.pluralize %>">
|
||||
<%= page_type_class.icon %>
|
||||
</i>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if invoice.activateable? %>
|
||||
<div class="card-action">
|
||||
<%= link_to '#', class: 'btn hoverable black-text white right activator', onclick: 'return false' do %>
|
||||
<i class="material-icons left">share</i>
|
||||
Share
|
||||
<% end %>
|
||||
<% unless current_user.on_premium_plan? %>
|
||||
<%= form_for :promotional_code, url: redeem_path do |form| %>
|
||||
<%= form.hidden_field :promo_code, value: invoice.page_unlock_promo_code.code %>
|
||||
<%= form.submit 'Activate', class: 'hoverable btn blue white-text', data: { confirm: "Are you sure you wish to activate this promo code? It can only be used once." } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="card-action">
|
||||
<% if invoice.page_unlock_promo_code && invoice.page_unlock_promo_code.uses_remaining.zero? %>
|
||||
Activated <%= time_ago_in_words invoice.page_unlock_promo_code.promotions.last.created_at %> ago
|
||||
<% else %>
|
||||
<% if invoice.status == 'CREATED' %>
|
||||
Ready for payment
|
||||
<% else %>
|
||||
Purchased
|
||||
<% end %>
|
||||
<%= time_ago_in_words invoice.created_at %> ago
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if invoice.page_unlock_promo_code.present? %>
|
||||
<div class="card-reveal">
|
||||
<span class="card-title grey-text text-darken-4">
|
||||
Share this code
|
||||
<i class="material-icons right">close</i>
|
||||
</span>
|
||||
<p>
|
||||
Anyone you send this code to can redeem it for their own account, activating the
|
||||
<%= pluralize invoice.months, 'month' %> of Premium immediately. To share it, send
|
||||
the following link with them:
|
||||
</p>
|
||||
<p>
|
||||
<input type="text" value="<%= gift_code_url(code: invoice.page_unlock_promo_code.code, referral: current_user.referral_code.code) %>" onClick="this.select();">
|
||||
</p>
|
||||
<p>
|
||||
If they don't already have a Notebook.ai account, you'll also be credited with their referral.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
89
app/views/subscriptions/redeem.html.erb
Normal file
89
app/views/subscriptions/redeem.html.erb
Normal file
@ -0,0 +1,89 @@
|
||||
<% if @code.nil? %>
|
||||
<div class="red white-text card-panel center">
|
||||
This code is no longer valid.
|
||||
</div>
|
||||
|
||||
<% elsif @code.uses_remaining.zero?%>
|
||||
<div class="green white-text card-panel center">
|
||||
This code has been activated!
|
||||
</div>
|
||||
<div class="center">
|
||||
<%= link_to 'Click here to customize your notebook pages', customization_content_types_path, class: 'btn btn-large blue white-text' %>
|
||||
</div>
|
||||
|
||||
<% else %>
|
||||
<h1 class="animated zoomInUp center">
|
||||
Someone has gifted you<br />
|
||||
<strong><%= pluralize(@code.days_active / 30, 'month') %></strong><br />
|
||||
of Notebook.ai Premium!
|
||||
</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="hoverable card">
|
||||
<div class="card-content">
|
||||
<div class="card-title">Activate your free Premium benefits</div>
|
||||
<p>
|
||||
You can activate your Premium benefits by clicking "Activate" below.
|
||||
Your account will be immediately upgraded for <%= pluralize @code.days_active, 'day' %>
|
||||
and will automatically downgrade back to the free tier afterwards instead of renewing monthly.
|
||||
This code can only be used <%= pluralize @code.uses_remaining, 'time' %>, so thank your friend!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if user_signed_in? %>
|
||||
<% if current_user.on_premium_plan? %>
|
||||
<div class="col s12">
|
||||
<div class="card-panel green white-text">
|
||||
<p>
|
||||
Since you already have a Premium subscription active, you can't activate this code yet.
|
||||
An activation link will appear again whenever you don't have Premium active.
|
||||
</p>
|
||||
<p>
|
||||
Don't worry—codes don't expire!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= form_for :promotional_code, url: redeem_path do |form| %>
|
||||
<%= form.hidden_field :promo_code, value: @code.code %>
|
||||
<%= form.submit 'Activate', class: 'hoverable btn btn-large blue white-text col s12', data: { confirm: "Are you sure you wish to activate this promo code? It can only be used once." } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="center">
|
||||
Before activating your Premium, please either create a Notebook.ai account or log in to an existing one.<br />
|
||||
</div>
|
||||
<div class="col s12 m6">
|
||||
<%= link_to 'Create an account', new_user_registration_path, class: 'hoverable btn-large btn blue white-text', style: 'width: 100%' %>
|
||||
</div>
|
||||
<div class="col s12 m6">
|
||||
<%= link_to 'Log in', new_user_session_path, class: 'hoverable btn-large btn blue white-text', style: 'width: 100%' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% unless user_signed_in? %>
|
||||
<div class="center">
|
||||
When you're logged in, return to this link so you can redeem this code!
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="font-size: 1.5em; padding-top: 100px; font-weight: bold" id="how-to-redeem">Notebook pages available with Premium</div>
|
||||
<p>
|
||||
Create any of these pages — and as many as you need — to start putting ideas to paper and flesh your world out one piece at a time.
|
||||
Each page comes with a fully-customizable template that will ask you questions to get you started, but stay out of your way when you've got your
|
||||
momentum.
|
||||
</p>
|
||||
<div class="row">
|
||||
<% (Rails.application.config.content_types[:all]).each do |content_type| %>
|
||||
<div class="col l4 m6 s12">
|
||||
<%= render partial: 'cards/intros/content_type_intro', locals: { content_type: content_type } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
<% end %>
|
||||
@ -1 +1 @@
|
||||
Rails.application.config.mixpanel_token = ENV.fetch('MIXPANEL_TOKEN', 'test')
|
||||
Rails.application.config.mixpanel_token = ENV.fetch('MIXPANEL_TOKEN', 'test')
|
||||
|
||||
4
config/initializers/paypal.rb
Normal file
4
config/initializers/paypal.rb
Normal file
@ -0,0 +1,4 @@
|
||||
Rails.application.config.paypal = {
|
||||
client_id: ENV.fetch('PAYPAL_CLIENT_ID', ''),
|
||||
client_secret: ENV.fetch('PAYPAL_SECRET', '')
|
||||
}
|
||||
@ -49,6 +49,9 @@ Rails.application.routes.draw do
|
||||
get '/subscription', to: 'subscriptions#new', as: :subscription
|
||||
get '/history', to: 'subscriptions#history', as: :billing_history
|
||||
get '/referrals', to: 'subscriptions#referrals', as: :referrals
|
||||
get '/prepay', to: 'subscriptions#prepay', as: :prepay
|
||||
get '/prepay_redirect_to_paypal', to: 'subscriptions#prepay_redirect_to_paypal', as: :prepay_paypal_gateway
|
||||
get '/gift/:code', to: 'subscriptions#redeem', as: :gift_code
|
||||
|
||||
get '/to/:stripe_plan_id', to: 'subscriptions#change', as: :change_subscription
|
||||
|
||||
|
||||
13
db/migrate/20200110050855_create_paypal_invoices.rb
Normal file
13
db/migrate/20200110050855_create_paypal_invoices.rb
Normal file
@ -0,0 +1,13 @@
|
||||
class CreatePaypalInvoices < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :paypal_invoices do |t|
|
||||
t.string :paypal_id
|
||||
t.string :status
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.integer :months
|
||||
t.integer :amount_cents
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,5 @@
|
||||
class AddPromoCodeLinkToPayoPaypalInvoice < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_reference :paypal_invoices, :page_unlock_promo_code, foreign_key: true
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,5 @@
|
||||
class AddApprovalLinkToPaypalInvoices < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :paypal_invoices, :approval_url, :string
|
||||
end
|
||||
end
|
||||
18
db/schema.rb
18
db/schema.rb
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2020_01_03_190122) do
|
||||
ActiveRecord::Schema.define(version: 2020_01_16_072334) do
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
@ -1664,6 +1664,20 @@ ActiveRecord::Schema.define(version: 2020_01_03_190122) do
|
||||
t.integer "past_owner_id"
|
||||
end
|
||||
|
||||
create_table "paypal_invoices", force: :cascade do |t|
|
||||
t.string "paypal_id"
|
||||
t.string "status"
|
||||
t.integer "user_id", null: false
|
||||
t.integer "months"
|
||||
t.integer "amount_cents"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.integer "page_unlock_promo_code_id"
|
||||
t.string "approval_url"
|
||||
t.index ["page_unlock_promo_code_id"], name: "index_paypal_invoices_on_page_unlock_promo_code_id"
|
||||
t.index ["user_id"], name: "index_paypal_invoices_on_user_id"
|
||||
end
|
||||
|
||||
create_table "planet_countries", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "planet_id"
|
||||
@ -2858,6 +2872,8 @@ ActiveRecord::Schema.define(version: 2020_01_03_190122) do
|
||||
add_foreign_key "location_notable_towns", "users"
|
||||
add_foreign_key "notice_dismissals", "users"
|
||||
add_foreign_key "page_tags", "users"
|
||||
add_foreign_key "paypal_invoices", "page_unlock_promo_codes"
|
||||
add_foreign_key "paypal_invoices", "users"
|
||||
add_foreign_key "planet_countries", "countries"
|
||||
add_foreign_key "planet_countries", "planets"
|
||||
add_foreign_key "planet_countries", "users"
|
||||
|
||||
15
test/fixtures/paypal_invoices.yml
vendored
Normal file
15
test/fixtures/paypal_invoices.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
one:
|
||||
paypal_id: MyString
|
||||
status: MyString
|
||||
user: one
|
||||
months: 1
|
||||
amount_cents: 1
|
||||
|
||||
two:
|
||||
paypal_id: MyString
|
||||
status: MyString
|
||||
user: two
|
||||
months: 1
|
||||
amount_cents: 1
|
||||
7
test/models/paypal_invoice_test.rb
Normal file
7
test/models/paypal_invoice_test.rb
Normal file
@ -0,0 +1,7 @@
|
||||
require 'test_helper'
|
||||
|
||||
class PaypalInvoiceTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
||||
Loading…
Reference in New Issue
Block a user