This commit is contained in:
Seth Call 2020-10-09 17:22:20 -05:00
parent 9476141a6c
commit 2ec522a366
7 changed files with 235 additions and 20 deletions

View File

@ -56,4 +56,27 @@ ALTER TABLE users ADD COLUMN beta BOOLEAN default FALSE;
ALTER TABLE arses ADD COLUMN beta BOOLEAN default FALSE;
ALTER TABLE generic_state ADD COLUMN event_page_top_logo_url VARCHAR(100000) DEFAULT '/assets/event/eventbrite-logo.png';
ALTER TABLE generic_state ADD COLUMN event_page_top_logo_url VARCHAR(100000) DEFAULT '/assets/event/eventbrite-logo.png';
ALTER TABLE users ADD COLUMN recurly_subscription_id VARCHAR(100) DEFAULT NULL;
ALTER TABLE users ADD COLUMN recurly_token VARCHAR(200) DEFAULT NULL;
ALTER TABLE users ADD COLUMN recurly_subscription_state VARCHAR(20) DEFAULT NULL;
CREATE TABLE subscriptions (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(200) UNIQUE NOT NULL UNIQUE NOT NULL,
play_time_per_session_mins INT DEFAULT NULL,
play_time_per_month_mins INT DEFAULT NULL,
can_record BOOLEAN DEFAULT TRUE,
audio_max_bitrate INT DEFAULT NULL,
save_as_wave BOOLEAN DEFAULT FALSE,
pro_audio BOOLEAN DEFAULT FALSE,
video_resolution VARCHAR(50) DEFAULT NULL,
broadcasting_type VARCHAR(50) DEFAULT NULL,
music_lessons VARCHAR(50) DEFAULT NULL,
support VARCHAR(50) DEFAULT NULL,
max_players_per_session INT DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@ -6,6 +6,7 @@ module JamRuby
JAMTRACK_SALE = 'jamtrack'
LESSON_SALE = 'lesson'
POSA_SALE = 'posacard'
SUBSCRIPTION_SALE = 'subscription'
SOURCE_RECURLY = 'recurly'
SOURCE_IOS = 'ios'
@ -260,6 +261,41 @@ module JamRuby
{sale: sale}
end
def self.purchase_subscription(current_user, recurly_token, plan_code)
sale = nil
Sale.transaction(:requires_new => true) do
current_user.recurly_token = recurly_token
current_user.subscription_plan_code = plan_code
sale = create_subscription_sale(current_user)
if sale.valid?
client = RecurlyClient.new
account = client.get_account(current_user)
if account.present?
recurly_response = client.create_subscription(current_user, plan_code, account)
current_user.recurly_subscription_id = recurly_response.uuid
current_user.save(validate: false)
SaleLineItem.create_from_subscription(current_user, sale, plan_code, recurly_response)
sale.recurly_subtotal_in_cents = recurly_response.unit_amount_in_cents
sale.recurly_tax_in_cents = recurly_response.tax_in_cents
sale.recurly_total_in_cents = sale.recurly_subtotal_in_cents + sale.recurly_tax_in_cents
sale.recurly_currency = recurly_response.currency
sale.save(validate: false)
else
raise RecurlyClientError, "Could not find account to place order."
end
end
end
{sale: sale}
end
# this is easy to make generic, but right now, it just purchases lessons
def self.purchase_lesson(charge, current_user, lesson_booking, lesson_package_type, lesson_session = nil, lesson_package_purchase = nil, force = false, posa_card = nil)
stripe_charge = nil
@ -776,7 +812,16 @@ module JamRuby
def self.create_lesson_sale(user)
sale = Sale.new
sale.user = user
sale.sale_type = LESSON_SALE # gift cards and jam tracks are sold with this type of sale
sale.sale_type = LESSON_SALE
sale.order_total = 0
sale.save
sale
end
def self.create_subscription_sale(user)
sale = Sale.new
sale.user = user
sale.sale_type = SUBSCRIPTION_SALE
sale.order_total = 0
sale.save
sale
@ -785,7 +830,7 @@ module JamRuby
def self.create_posa_sale(retailer, posa_card)
sale = Sale.new
sale.retailer = retailer
sale.sale_type = POSA_SALE # gift cards and jam tracks are sold with this type of sale
sale.sale_type = POSA_SALE
sale.order_total = posa_card.product_info[:price]
sale.save
sale

View File

@ -7,6 +7,7 @@ module JamRuby
GIFTCARD = 'GiftCardType'
LESSON = 'LessonPackageType'
POSACARD = 'PosaCard'
SUBSCRIPTION = 'Subscription'
belongs_to :sale, class_name: 'JamRuby::Sale'
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
@ -22,7 +23,7 @@ module JamRuby
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale_line_item, foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid'
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK, GIFTCARD, LESSON, POSACARD]}
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK, GIFTCARD, LESSON, POSACARD, SUBSCRIPTION]}
validates :unit_price, numericality: {only_integer: false}
validates :quantity, numericality: {only_integer: true}
validates :free, numericality: {only_integer: true}
@ -40,9 +41,15 @@ module JamRuby
product_type == GIFTCARD
end
def is_subscription?
product_type == SUBSCRIPTION
end
def product
if product_type == JAMTRACK
JamTrack.find_by_id(product_id)
if product_type == SUBSCRIPTION
{name: product_id}
elsif product_type == GIFTCARD
GiftCardType.find_by_id(product_id)
elsif product_type == LESSON
@ -105,6 +112,24 @@ module JamRuby
self.save!
end
end
def self.create_from_subscription(current_user, sale, plan_code, recurly_response)
sale_line_item = SaleLineItem.new
sale_line_item.product_type = SUBSCRIPTION
sale_line_item.product_id = recurly_response.uuid
sale_line_item.unit_price = recurly_response.unit_amount_in_cents
sale_line_item.quantity = 1
sale_line_item.free = false
sale_line_item.sales_tax = recurly_response.tax_in_cents
sale_line_item.shipping_handling = 0
sale_line_item.recurly_plan_code = plan_code
sale.sale_line_items << sale_line_item
sale_line_item.save
sale_line_item
end
# in a shopping-cart less world (ios purchase), let's reuse as much logic as possible
def self.create_from_lesson_package(current_user, sale, lesson_package_type, lesson_booking)
teacher = lesson_booking.teacher if lesson_booking

View File

@ -186,6 +186,44 @@ module JamRuby
raise RecurlyClientError.new(plan.errors) if plan.errors.any?
end
# https://dev.recurly.com/docs/create-subscription
def create_subscription(user, plan_code, account)
subscription = Recurly::Subscription.create(
:plan_code => plan_code,
:currency => 'USD',
:customer_notes => 'Thank you for your business!',
:account => {
:account_code => account.account_code
},
:auto_renew => true
)
subscription
end
def find_subscription(user)
if user.recurly_subscription_id.nil?
nil
else
Recurly::Subscription.find(user.recurly_subscription_id)
end
end
def sync_subscription(user)
subscription = find_subscription(user)
if subscription.nil?
if user.subscription_plan_code
user.subscription_plan_code = nil
user.recurly_subscription_state = nil
user.save(validate:false)
end
else
user.recurly_subscription_state = subscription.state
if user.subscription_plan_code != subscription.plan.plan_code
user.subscription_plan_code = subscription.plan.plan_code
end
end
end
def find_or_create_account(current_user, billing_info)
account = get_account(current_user)

View File

@ -1,21 +1,35 @@
context = window
rest = context.JK.Rest()
logger = context.JK.logger
LocationActions = context.LocationActions
UserStore = context.UserStore
@Subscription = React.createClass({
mixins: [Reflux.listenTo(@LocationStore, "onLocationsChanged")]
getInitialState: () ->
{
clicked: false
clicked: false,
selectedCountry: null
}
onLocationsChanged: (countries) ->
console.log("countires in ", countries)
@setState({countries: countries})
onCountryChanged: (e) ->
val = $(e.target).val()
@setState({selectedCountry: val})
currentCountry: () ->
this.state.selectedCountry || this.props.selectedCountry || ''
openBrowser: () ->
context.JK.popExternalLink("https://www.jamkazam.com/client#/subscription")
onRecurlyToken: (err, token) ->
console.log("TOKEN", token)
if err
@ -38,6 +52,8 @@ UserStore = context.UserStore
window.configuredRecurly = true
componentDidMount: () ->
LocationActions.load()
@configureRecurly()
@elements = recurly.Elements()
@ -67,32 +83,48 @@ UserStore = context.UserStore
document.querySelector('#subscription-form').addEventListener('submit', @onFormSubmit.bind(this))
defaultText: () ->
'Select Country'
render: () ->
if @state.countries?
countries = [`<option key="" value="">{this.defaultText()}</option>`]
for countryId, countryInfo of @state.countries
countries.push(`<option key={countryId} value={countryId}>{countryInfo.name}</option>`)
country = @state.countries[this.currentCountry()]
else
countries = []
countryJsx = `
<select name="countries" onChange={this.onCountryChanged} value={this.currentCountry()} data-recurly="country" autocomplete="shipping country">{countries}</select>`
`<form id="subscription-form">
<div id="subscription-account-data">
<label for="first_name">First Name:</label>
<input type="text" data-recurly="first_name"></input>
<input type="text" data-recurly="first_name" required autocomplete="cc-give-name"></input>
<label for="last_name">Last Name:</label>
<input type="text" data-recurly="last_name"></input>
<input type="text" data-recurly="last_name" required autocomplete="cc-family-name"></input>
<label for="address1">Address 1:</label>
<input type="text" data-recurly="address1"></input>
<input type="text" data-recurly="address1" required autocomplete="shipping address-line1"></input>
<label for="address2">Address 2:</label>
<input type="text" data-recurly="address2"></input>
<input type="text" data-recurly="address2" required autocomplete="shipping address-line1"></input>
<label for="city">City:</label>
<input type="text" data-recurly="city"></input>
<input type="text" data-recurly="city" required autocomplete="shipping address-level2"></input>
<label for="state">State:</label>
<input type="text" data-recurly="state"></input>
<input type="text" data-recurly="state" required autocomplete="shipping address-level1"></input>
<label for="postal_code">Postal Code:</label>
<input type="text" data-recurly="postal_code"></input>
<input type="text" data-recurly="postal_code" autocomplete="shipping postal-code"></input>
<label for="country">Country:</label>
<input type="text" data-recurly="country"></input>
{countryJsx}
</div>
<div id="subscription-elements">

View File

@ -54,7 +54,7 @@
input[type="text"], select {
position: relative;
width: 100%;
border: 2px solid #c2c2c2;
border: 1px solid #c2c2c2;
background: white;
padding: 0.5rem;
margin: 0 0 1rem;
@ -64,7 +64,7 @@
font-weight: bold;
box-shadow: none;
border-radius: 0;
color: #c2c2c2;
color: black;
-webkit-appearance: none;
-webkit-transition: border-color 0.3s;
-moz-transition: border-color 0.3s;
@ -74,8 +74,9 @@
}
input:focus {
border-color: #2c0730;
color: #2c0730;
border: 1px solid white;
color: black;
background:#d2d2d2;
z-index: 10;
}
@ -84,7 +85,7 @@
}
div.error .recurly-hosted-field {
border: 2px solid #e43c29;
border: 1px solid #c2c2c2;
}
@media screen and (max-height: 599px) {

View File

@ -124,6 +124,57 @@ class ApiRecurlyController < ApiController
render json: {message: x.inspect, errors: x.errors}, :status => 404
end
def create_subscriptions
begin
sale = Sale.purchase_subscription(current_user, params[:recurly_token], params[:plan_code])
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
render :json => subscription.to_json
rescue RecurlyClientError => x
render json: {:message => x.inspect, errors: x.errors}, :status => 404
end
end
def get_subscription
subscription_id = current_user.recurly_subscription_id
subscription = Recurly::Subscription.find(subscription_id) if subscription_id
if subscription
render :json => subscription
else
render :json => {}
end
end
def cancel_subscription
begin
@client.cancel_subscription(current_user.recurly_subscription_id)
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
render :json => subscription.to_json
rescue RecurlyClientError => x
render json: {:message => x.inspect, errors: x.errors}, :status => 404
end
end
def change_subscription_plan
begin
@client.change_subscription_plan(current_user.recurly_subscription_id, params[:plan_code])
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
render :json => subscription.to_json
rescue RecurlyClientError => x
render json: {:message => x.inspect, errors: x.errors}, :status => 404
end
end
def change_subscription_payment
begin
@client.change_subscription_payment(current_user.recurly_subscription_id, params[:recurly_token], params[:billing_ifo])
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
render :json => subscription.to_json
rescue RecurlyClientError => x
render json: {:message => x.inspect, errors: x.errors}, :status => 404
end
end
def place_order
error=nil
response = {jam_tracks: [], gift_cards: []}