paypal support
This commit is contained in:
parent
458637b1de
commit
5e04f72d8e
|
|
@ -370,3 +370,4 @@ second_ed_v2.sql
|
|||
retailers_v2.sql
|
||||
retailer_interest.sql
|
||||
connection_role.sql
|
||||
retailer_payment_split.sql
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE retailers ADD COLUMN payment VARCHAR;
|
||||
ALTER TABLE lesson_bookings ADD COLUMN payment VARCHAR;
|
||||
|
|
@ -40,6 +40,7 @@ require "jam_ruby/errors/state_error"
|
|||
require "jam_ruby/errors/jam_argument_error"
|
||||
require "jam_ruby/errors/jam_record_not_found"
|
||||
require "jam_ruby/errors/conflict_error"
|
||||
require "jam_ruby/errors/pay_pal_client_error"
|
||||
require "jam_ruby/lib/app_config"
|
||||
require "jam_ruby/lib/s3_manager_mixin"
|
||||
require "jam_ruby/lib/s3_public_manager_mixin"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
module JamRuby
|
||||
class PayPalClientError < StandardError
|
||||
|
||||
attr_accessor :errors
|
||||
def initialize(data)
|
||||
if data.respond_to?('has_key?')
|
||||
self.errors = data
|
||||
else
|
||||
self.errors = {:message=>data.to_s}
|
||||
end
|
||||
end # initialize
|
||||
|
||||
def to_s
|
||||
s=super
|
||||
s << ", errors: #{errors.inspect}" if self.errors.any?
|
||||
s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -531,7 +531,7 @@ module JamRuby
|
|||
distribution = teacher_distribution_price_in_cents(target)
|
||||
|
||||
if education
|
||||
(distribution * 0.0625).round
|
||||
(distribution * 0.0625).round # 0.0625 is 1/4th of 25%
|
||||
else
|
||||
distribution
|
||||
end
|
||||
|
|
|
|||
|
|
@ -216,11 +216,20 @@ module JamRuby
|
|||
|
||||
self.status = STATUS_COMPLETED
|
||||
|
||||
# RETAILERPAY2
|
||||
|
||||
|
||||
if success && lesson_booking.requires_teacher_distribution?(self)
|
||||
is_education_school_on_school = lesson_booking.school_on_school_payment?
|
||||
|
||||
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
|
||||
if lesson_booking.school_on_school_payment?
|
||||
if is_education_school_on_school
|
||||
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, true)
|
||||
end
|
||||
|
||||
# this is a bit of a hack, in how the code is structured.
|
||||
# but basically, the distributions calculated are too dynamic for the above code.
|
||||
# if this is a retailer
|
||||
end
|
||||
|
||||
if self.save
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module JamRuby
|
|||
|
||||
SOURCE_RECURLY = 'recurly'
|
||||
SOURCE_IOS = 'ios'
|
||||
SOURCE_PAYPAL = 'paypal'
|
||||
|
||||
belongs_to :retailer, class_name: 'JamRuby::Retailer'
|
||||
belongs_to :user, class_name: 'JamRuby::User'
|
||||
|
|
@ -166,7 +167,7 @@ module JamRuby
|
|||
# individual subscriptions will end up create their own sale (you can't have N subscriptions in one sale--recurly limitation)
|
||||
# jamtracks however can be piled onto the same sale as adjustments (VRFS-3028)
|
||||
# so this method may create 1 or more sales, , where 2 or more sales can occur if there are more than one subscriptions or subscription + jamtrack
|
||||
def self.place_order(current_user, shopping_carts)
|
||||
def self.place_order(current_user, shopping_carts, paypal = false)
|
||||
|
||||
sales = []
|
||||
|
||||
|
|
@ -176,7 +177,7 @@ module JamRuby
|
|||
# return sales
|
||||
#end
|
||||
|
||||
jam_track_sale = order_jam_tracks(current_user, shopping_carts)
|
||||
jam_track_sale = order_jam_tracks(current_user, shopping_carts, paypal)
|
||||
sales << jam_track_sale if jam_track_sale
|
||||
|
||||
# TODO: process shopping_carts_subscriptions
|
||||
|
|
@ -377,7 +378,7 @@ module JamRuby
|
|||
|
||||
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
|
||||
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
|
||||
def self.order_jam_tracks(current_user, shopping_carts)
|
||||
def self.order_jam_tracks(current_user, shopping_carts, is_paypal)
|
||||
|
||||
shopping_carts_jam_tracks = []
|
||||
shopping_carts_subscriptions = []
|
||||
|
|
@ -395,11 +396,9 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
client = RecurlyClient.new
|
||||
|
||||
sale = nil
|
||||
Sale.transaction do
|
||||
sale = create_jam_track_sale(current_user, SOURCE_RECURLY)
|
||||
sale = create_jam_track_sale(current_user, is_paypal ? SOURCE_PAYPAL : SOURCE_RECURLY)
|
||||
|
||||
if sale.valid?
|
||||
if is_only_freebie(shopping_carts)
|
||||
|
|
@ -429,61 +428,142 @@ module JamRuby
|
|||
|
||||
else
|
||||
|
||||
account = client.get_account(current_user)
|
||||
if account.present?
|
||||
if is_paypal
|
||||
|
||||
purge_pending_adjustments(account)
|
||||
sale.process_shopping_carts(current_user, shopping_carts)
|
||||
|
||||
created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account)
|
||||
paypal_auth = current_user.paypal_auth
|
||||
|
||||
# now invoice the sale ... almost done
|
||||
@api = PayPal::SDK::Merchant::API.new
|
||||
@get_express_checkout_details = @api.build_get_express_checkout_details({:Token => paypal_auth.token})
|
||||
@response = @api.get_express_checkout_details(@get_express_checkout_details)
|
||||
|
||||
begin
|
||||
invoice = account.invoice!
|
||||
sale.recurly_invoice_id = invoice.uuid
|
||||
sale.recurly_invoice_number = invoice.invoice_number
|
||||
@@log.info("User #{current_user.email}, GetExpressCheckout: #{@response.inspect}")
|
||||
|
||||
tax = false
|
||||
if @response.Ack == 'Success'
|
||||
payerInfo = @response.GetExpressCheckoutDetailsResponseDetails.PayerInfo
|
||||
if payerInfo.Address && ( payerInfo.Address.Country == 'US' && payerInfo.Address.StateOrProvince == 'TX')
|
||||
# we need to ask for taxes
|
||||
tax = true
|
||||
end
|
||||
end
|
||||
|
||||
tax_rate = tax ? 0.0825 : 0
|
||||
total = current_user.shopping_cart_total.round(2)
|
||||
tax_total = (total * tax_rate).round(2)
|
||||
total = total + tax_total
|
||||
total = total.round(2)
|
||||
|
||||
@do_express_checkout_payment = @api.build_do_express_checkout_payment({
|
||||
:DoExpressCheckoutPaymentRequestDetails => {
|
||||
:PaymentDetails =>
|
||||
[
|
||||
{
|
||||
:OrderTotal => {
|
||||
:currencyID => "USD",
|
||||
:value => total
|
||||
},
|
||||
:PaymentAction => "Sale"
|
||||
}
|
||||
],
|
||||
:Token => paypal_auth.token,
|
||||
:PayerID => paypal_auth.uid, }})
|
||||
@pay_response = @api.do_express_checkout_payment(@do_express_checkout_payment)
|
||||
|
||||
@@log.info("User #{current_user.email}, DoExpressCheckoutPayment: #{@pay_response.inspect}")
|
||||
|
||||
# #<PayPal::SDK::Merchant::DataTypes::DoExpressCheckoutPaymentResponseType:0x007fe511dd9b88
|
||||
# @Timestamp=Sun, 11 Dec 2016 02:09:31 +0000, @Ack="Success",
|
||||
# @CorrelationID="b28faf6bd90d9", @Version="117.0", @Build="000000",
|
||||
# @DoExpressCheckoutPaymentResponseDetails=#<PayPal::SDK::Merchant::DataTypes::DoExpressCheckoutPaymentResponseDetailsType:0x007fe511dd38c8
|
||||
# @Token="EC-7A4606566T700564B",
|
||||
# @PaymentInfo=[#<PayPal::SDK::Merchant::DataTypes::PaymentInfoType:0x007fe511dd3008 @TransactionID="63C410710F2619403", @ParentTransactionID=nil,
|
||||
# @ReceiptID=nil, @TransactionType="express-checkout", @PaymentType="instant", @PaymentDate=Sun, 11 Dec 2016 02:09:31 +0000,
|
||||
# @GrossAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dd0c90 @currencyID="USD", @value="1.99">,
|
||||
# @FeeAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dd04e8 @currencyID="USD", @value="0.36">,
|
||||
# @TaxAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dcbe98 @currencyID="USD", @value="0.00">,
|
||||
# @ExchangeRate=nil, @PaymentStatus="Completed", @PendingReason="none", @ReasonCode="none", @ProtectionEligibility="Eligible",
|
||||
# @ProtectionEligibilityType="ItemNotReceivedEligible,UnauthorizedPaymentEligible", @SellerDetails=#<PayPal::SDK::Merchant::DataTypes::SellerDetailsType:0x007fe511dcb358 @SecureMerchantAccountID="6MB486RSBRMJ2">>],
|
||||
# @SuccessPageRedirectRequested="false", @CoupledPaymentInfo=[#<PayPal::SDK::Merchant::DataTypes::CoupledPaymentInfoType:0x007fe511dca7a0>]>>
|
||||
|
||||
if @pay_response.Ack == 'Success'
|
||||
details = @pay_response.DoExpressCheckoutPaymentResponseDetails.PaymentInfo[0]
|
||||
sale.recurly_invoice_id = details.TransactionID
|
||||
sale.recurly_invoice_number = details.ReceiptID
|
||||
|
||||
# now slap in all the real tax/purchase totals
|
||||
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
|
||||
sale.recurly_tax_in_cents = invoice.tax_in_cents
|
||||
sale.recurly_total_in_cents = invoice.total_in_cents
|
||||
sale.recurly_currency = invoice.currency
|
||||
|
||||
# and resolve against sale_line_items
|
||||
sale.sale_line_items.each do |sale_line_item|
|
||||
found_line_item = false
|
||||
invoice.line_items.each do |line_item|
|
||||
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
|
||||
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
|
||||
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
|
||||
sale_line_item.recurly_currency = line_item.currency
|
||||
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
|
||||
found_line_item = true
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if !found_line_item
|
||||
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
|
||||
puts "CANT FIND LINE ITEM"
|
||||
end
|
||||
end
|
||||
sale.recurly_subtotal_in_cents = ((details.GrossAmount.value.to_f - details.TaxAmount.value.to_f) * 100).to_i
|
||||
sale.recurly_tax_in_cents = (details.TaxAmount.value.to_f * 100).to_i
|
||||
sale.recurly_total_in_cents = (details.GrossAmount.value.to_f * 100).to_i
|
||||
sale.recurly_currency = details.GrossAmount.currencyID
|
||||
|
||||
unless sale.save
|
||||
puts "WTF"
|
||||
raise RecurlyClientError, "Invalid sale (at end)."
|
||||
puts "Invalid sale (at end)."
|
||||
raise PayPalClientError, "Invalid sale (at end)."
|
||||
end
|
||||
rescue Recurly::Resource::Invalid => e
|
||||
# this exception is thrown by invoice! if the invoice is invalid
|
||||
sale.rollback_adjustments(current_user, created_adjustments)
|
||||
sale = nil
|
||||
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
|
||||
rescue => e
|
||||
puts "UNKNOWN E #{e}"
|
||||
else
|
||||
@@log.error("User #{current_user.email}, DoExpressCheckoutPayment: #{@pay_response.inspect}")
|
||||
raise PayPalClientError, @pay_response.Errors[0].LongMessage
|
||||
end
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to place order."
|
||||
client = RecurlyClient.new
|
||||
account = client.get_account(current_user)
|
||||
if account.present?
|
||||
|
||||
purge_pending_adjustments(account)
|
||||
|
||||
created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account)
|
||||
|
||||
# now invoice the sale ... almost done
|
||||
|
||||
begin
|
||||
invoice = account.invoice!
|
||||
sale.recurly_invoice_id = invoice.uuid
|
||||
sale.recurly_invoice_number = invoice.invoice_number
|
||||
|
||||
# now slap in all the real tax/purchase totals
|
||||
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
|
||||
sale.recurly_tax_in_cents = invoice.tax_in_cents
|
||||
sale.recurly_total_in_cents = invoice.total_in_cents
|
||||
sale.recurly_currency = invoice.currency
|
||||
|
||||
# and resolve against sale_line_items
|
||||
sale.sale_line_items.each do |sale_line_item|
|
||||
found_line_item = false
|
||||
invoice.line_items.each do |line_item|
|
||||
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
|
||||
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
|
||||
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
|
||||
sale_line_item.recurly_currency = line_item.currency
|
||||
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
|
||||
found_line_item = true
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if !found_line_item
|
||||
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
|
||||
puts "CANT FIND LINE ITEM"
|
||||
end
|
||||
end
|
||||
|
||||
unless sale.save
|
||||
puts "Invalid sale (at end)."
|
||||
raise RecurlyClientError, "Invalid sale (at end)."
|
||||
end
|
||||
rescue Recurly::Resource::Invalid => e
|
||||
# this exception is thrown by invoice! if the invoice is invalid
|
||||
sale.rollback_adjustments(current_user, created_adjustments)
|
||||
sale = nil
|
||||
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
|
||||
rescue => e
|
||||
puts "UNKNOWN E #{e}"
|
||||
end
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to place order."
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
|
@ -493,7 +573,7 @@ module JamRuby
|
|||
sale
|
||||
end
|
||||
|
||||
def process_shopping_carts(current_user, shopping_carts, account)
|
||||
def process_shopping_carts(current_user, shopping_carts, account = nil)
|
||||
|
||||
created_adjustments = []
|
||||
|
||||
|
|
@ -515,7 +595,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
|
||||
def process_shopping_cart(current_user, shopping_cart, account, created_adjustments)
|
||||
def process_shopping_cart(current_user, shopping_cart, recurly_account, created_adjustments)
|
||||
recurly_adjustment_uuid = nil
|
||||
recurly_adjustment_credit_uuid = nil
|
||||
|
||||
|
|
@ -536,14 +616,14 @@ module JamRuby
|
|||
end
|
||||
|
||||
|
||||
if account
|
||||
if recurly_account
|
||||
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
|
||||
adjustments = shopping_cart.create_adjustment_attributes(current_user)
|
||||
|
||||
adjustments.each do |adjustment|
|
||||
|
||||
# create the adjustment at Recurly (this may not look like it, but it is a REST API)
|
||||
created_adjustment = account.adjustments.new(adjustment)
|
||||
created_adjustment = recurly_account.adjustments.new(adjustment)
|
||||
created_adjustment.save
|
||||
|
||||
# if the adjustment could not be made, bail
|
||||
|
|
|
|||
|
|
@ -1912,6 +1912,13 @@ module JamRuby
|
|||
stats
|
||||
end
|
||||
|
||||
def shopping_cart_total
|
||||
total = 0
|
||||
shopping_carts.each do |shopping_cart|
|
||||
total += shopping_cart.product_info[:total_price]
|
||||
end
|
||||
total
|
||||
end
|
||||
def destroy_all_shopping_carts
|
||||
ShoppingCart.where("user_id=?", self).destroy_all
|
||||
end
|
||||
|
|
@ -2053,6 +2060,15 @@ module JamRuby
|
|||
user_authorizations.where(provider: "stripe_connect").first
|
||||
end
|
||||
|
||||
def paypal_auth
|
||||
user_authorizations.where(provider: 'paypal').first
|
||||
end
|
||||
|
||||
def has_paypal_auth?
|
||||
auth = paypal_auth
|
||||
auth && (!auth.token_expiration || auth.token_expiration > Time.now)
|
||||
end
|
||||
|
||||
def has_stripe_connect?
|
||||
auth = stripe_auth
|
||||
auth && (!auth.token_expiration || auth.token_expiration > Time.now)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ gem 'sprockets-rails', '2.3.2'
|
|||
gem 'non-stupid-digest-assets'
|
||||
#gem 'license_finder'
|
||||
gem 'pg_migrate', '0.1.14'
|
||||
#gem 'paypal-sdk-rest'
|
||||
gem 'paypal-sdk-merchant', github: 'sylv3rblade/merchant-sdk-ruby'
|
||||
gem 'kickbox'
|
||||
gem 'oj', '2.10.2'
|
||||
gem 'builder'
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
//= require jquery.Jcrop
|
||||
//= require jquery.naturalsize
|
||||
//= require jquery.queryparams
|
||||
//= require jquery.clipboard
|
||||
//= require clipboard
|
||||
//= require jquery.timeago
|
||||
//= require jquery.easydropdown
|
||||
//= require jquery.scrollTo
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
var userDetail = null;
|
||||
var entity = null;
|
||||
var remainingCap = 140 - 22 - 1; // 140 tweet max, minus 22 for link size, minus 1 for space
|
||||
var clipboard = null;
|
||||
|
||||
function showSpinner() {
|
||||
$(dialogId + ' .dialog-inner').hide();
|
||||
|
|
@ -444,27 +445,6 @@
|
|||
|
||||
function afterShow() {
|
||||
$("#shareType").text(entityType);
|
||||
if(context.JK.hasFlash()) {
|
||||
$("#btn-share-copy").clipboard({
|
||||
path: '/assets/jquery.clipboard.swf',
|
||||
copy: function() {
|
||||
// Return text in closest element (useful when you have multiple boxes that can be copied)
|
||||
return $(".link-contents").text();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
if(context.jamClient) {
|
||||
// uses bridge call to ultimately access QClipboard
|
||||
$("#btn-share-copy").unbind('click').click(function() {
|
||||
context.jamClient.SaveToClipboard($(".link-contents").text());
|
||||
return false;
|
||||
})
|
||||
}
|
||||
else {
|
||||
logger.debug("no copy-to-clipboard capabilities")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
|
|
@ -486,6 +466,20 @@
|
|||
//initDialog();
|
||||
|
||||
facebookHelper.deferredLoginStatus().done(function(response) { handleFbStateChange(response); });
|
||||
|
||||
if(context.jamClient.IsNativeClient()) {
|
||||
$("#btn-share-copy").unbind('click').click(function() {
|
||||
context.jamClient.SaveToClipboard($("#link-contents").text());
|
||||
return false;
|
||||
})
|
||||
}
|
||||
else {
|
||||
clipboard = new Clipboard('#btn-share-copy', {
|
||||
text: function(trigger) {
|
||||
return $("#link-contents").text();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
|
|
|||
|
|
@ -2734,6 +2734,27 @@
|
|||
})
|
||||
}
|
||||
|
||||
function paypalDetail(options) {
|
||||
options = options || {}
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/paypal/checkout/detail',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function paypalPlaceOrder(options) {
|
||||
options = options || {}
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/paypal/checkout/confirm',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
return self;
|
||||
|
|
@ -2977,6 +2998,8 @@
|
|||
this.posaActivate = posaActivate;
|
||||
this.posaClaim = posaClaim;
|
||||
this.sendRetailerCustomerEmail = sendRetailerCustomerEmail;
|
||||
this.paypalDetail = paypalDetail;
|
||||
this.paypalPlaceOrder = paypalPlaceOrder;
|
||||
return this;
|
||||
};
|
||||
})(window,jQuery);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
//= require jquery.queryparams
|
||||
//= require jquery.hoverIntent
|
||||
//= require jquery.cookie
|
||||
//= require jquery.clipboard
|
||||
//= require clipboard
|
||||
//= require jquery.easydropdown
|
||||
//= require jquery.carousel-1.1
|
||||
//= require jquery.mousewheel-3.1.9
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
.done((response) =>
|
||||
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
|
||||
)
|
||||
.fail(() =>
|
||||
.fail((jqXHR) =>
|
||||
@app.notifyServerError jqXHR, 'Search Unavailable'
|
||||
@setState({searching: false, first_search: false})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -326,12 +326,12 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
.done((response) =>
|
||||
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
|
||||
)
|
||||
.fail(() =>
|
||||
.fail((jqXHR) =>
|
||||
@app.notifyServerError jqXHR, 'Search Unavailable'
|
||||
@setState({searching: false, first_search: false})
|
||||
)
|
||||
)
|
||||
.fail(() =>
|
||||
.fail((jqXHR) =>
|
||||
@app.notifyServerError jqXHR, 'Search Unavailable'
|
||||
@setState({searching: false, first_search: false})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
context = window
|
||||
MIX_MODES = context.JK.MIX_MODES
|
||||
|
||||
@PayPalConfirmationScreen = React.createClass({
|
||||
|
||||
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")]
|
||||
|
||||
render: () ->
|
||||
content = null
|
||||
|
||||
|
||||
if this.state.sold
|
||||
|
||||
if context.jamClient && context.jamClient.IsNativeClient()
|
||||
platformMessage = `<div>
|
||||
<p> To play your purchased JamTrack, start a session and then open the JamTrack</p>
|
||||
<a className="download-jamkazam-wrapper" href="/client#/createSession">
|
||||
<div className="download-jamkazam">
|
||||
Click Here to Start a Session
|
||||
</div>
|
||||
</a>
|
||||
</div>`
|
||||
else
|
||||
platformMessage =
|
||||
`<div>
|
||||
<a href='/client#/jamtrack' className="download-jamkazam-wrapper jt-popup">
|
||||
<div className="download-jamkazam">
|
||||
Click Here to Start Using Your JamTrack
|
||||
</div>
|
||||
</a>
|
||||
<a href='/downloads' rel="external" className="download-jamkazam-wrapper" target="_blank">
|
||||
<div>
|
||||
Do More With Your JamTrack - Click Here to Download Our Application
|
||||
</div>
|
||||
</a>
|
||||
<a className="back-to-browsing" href="/client#/jamtrack">
|
||||
or click here to browse more jamtracks
|
||||
</a>
|
||||
</div>`
|
||||
content = `<div className="sold-notice">
|
||||
<h2>Thank you for your order!</h2>
|
||||
{platformMessage}
|
||||
</div>`
|
||||
else
|
||||
orderButtons = {"button-orange": true, "place-order-btn": true, disabled: this.state.ordering }
|
||||
cancelButtons = {"button-grey": true, "cancel": true, disabled: this.state.ordering }
|
||||
|
||||
content = `<div>
|
||||
<h2 className="confirm-header">Confirm PayPal Payment</h2>
|
||||
|
||||
<p>You have not yet made a payment via PayPal. Please review your purchase and confirm or cancel.</p>
|
||||
|
||||
<div className="controls">
|
||||
<a href="#" className={classNames(orderButtons)} onClick={this.placeOrder}>CONFIRM PURCHASE WITH
|
||||
PAYPAL</a>
|
||||
<a href="#" className={classNames(cancelButtons)} onClick={this.cancel}>CANCEL</a>
|
||||
|
||||
<div className="clearall"/>
|
||||
</div>
|
||||
<ShoppingCartContents carts={this.state.carts}/>
|
||||
|
||||
<div className="controls bottom">
|
||||
<a href="#" className={classNames(orderButtons)} onClick={this.placeOrder}>CONFIRM PURCHASE WITH
|
||||
PAYPAL</a>
|
||||
<a href="#" className={classNames(cancelButtons)} onClick={this.cancel}>CANCEL</a>
|
||||
|
||||
<div className="clearall"/>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
`<div className="PayPalConfirmationScreen">
|
||||
<div className="content-body-scroller">
|
||||
{content}
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
placeOrder: (e) ->
|
||||
e.preventDefault()
|
||||
if this.state.ordering
|
||||
return
|
||||
@setState({ordering: true})
|
||||
|
||||
console.log("placing order with paypal")
|
||||
@rest.paypalPlaceOrder()
|
||||
.done((response) =>
|
||||
console.log("paypal detail obtained", response)
|
||||
|
||||
@setState({sold: true, ordering: false})
|
||||
context.JK.JamTrackUtils.checkShoppingCart()
|
||||
@app.refreshUser()
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
@setState({ordering: false})
|
||||
if jqXHR.status == 404
|
||||
context.JK.Banner.showAlert('PayPal Session Over', 'Your PayPal authorization has expired. Please restart the PayPal confirmation process. <a href="/client#/checkoutPayment">Click Here to Checkout Again.</a>')
|
||||
else if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
context.JK.Banner.showAlert('PayPal Purchase Error', 'PayPal: ' + response.message)
|
||||
else
|
||||
context.JK.Banner.showAlert('PayPal/Sales Error', 'Please contact support@jamkazam.com')
|
||||
)
|
||||
|
||||
cancelOrder: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
window.location = '/client#/jamtrack'
|
||||
|
||||
getInitialState: () ->
|
||||
{}
|
||||
|
||||
componentDidMount: () ->
|
||||
|
||||
componentDidUpdate: () ->
|
||||
|
||||
afterShow: (data) ->
|
||||
rest.getShoppingCarts()
|
||||
.done((carts) =>
|
||||
@setState({carts: carts})
|
||||
if carts.length == 0
|
||||
window.location = '/client#/jamtrack'
|
||||
return
|
||||
|
||||
@rest.paypalDetail()
|
||||
.done((response) =>
|
||||
console.log("paypal detail obtained", response)
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
if jqXHR.status == 404
|
||||
context.JK.Banner.showAlert('PayPal Session Over', 'Your PayPal authorization has expired. Please restart the PayPal confirmation process. <a href="/client#/checkoutPayment">Click Here to Checkout Again.</a>')
|
||||
else if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
context.JK.Banner.showAlert('PayPal Purchase Error', 'PayPal: ' + response.message)
|
||||
else
|
||||
context.JK.Banner.showAlert('PayPal/Sales Error', 'Please contact support@jamkazam.com')
|
||||
@app.notifyServerError jqXHR, 'PayPal Communication Error'
|
||||
)
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
@app.notifyServerError jqXHR, 'Unable to fetch carts'
|
||||
)
|
||||
|
||||
|
||||
beforeShow: () ->
|
||||
this.setState({sold: false})
|
||||
|
||||
onAppInit: (@app) ->
|
||||
@EVENTS = context.JK.EVENTS
|
||||
@rest = context.JK.Rest()
|
||||
@logger = context.JK.logger
|
||||
|
||||
screenBindings =
|
||||
'beforeShow': @beforeShow
|
||||
'afterShow': @afterShow
|
||||
|
||||
@app.bindScreen('paypal/confirm', screenBindings)
|
||||
|
||||
onUserChanged: (userState) ->
|
||||
@user = userState?.user
|
||||
|
||||
})
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
context = window
|
||||
MIX_MODES = context.JK.MIX_MODES
|
||||
|
||||
@ShoppingCartContents = React.createClass({
|
||||
|
||||
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")]
|
||||
|
||||
render: () ->
|
||||
|
||||
carts = []
|
||||
|
||||
if this.props.carts?
|
||||
if this.props.carts.length == 0
|
||||
carts = `<div className="no-cart-items">You have nothing in your cart</div>`
|
||||
else
|
||||
taxRate = 0
|
||||
if this.props.tax
|
||||
taxRate = 0.0825
|
||||
|
||||
estimatedTax = 0
|
||||
estimatedTotal = 0
|
||||
|
||||
for cart in this.props.carts
|
||||
cart_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem
|
||||
estimatedTax += cart.product_info.price * cart_quantity * taxRate
|
||||
estimatedTotal += cart.product_info.price * cart_quantity
|
||||
|
||||
estimatedTax = Math.round(estimatedTax * 100) / 100
|
||||
estimatedTotal = Math.round((estimatedTotal + estimatedTax) * 100) / 100
|
||||
|
||||
for cart in this.props.carts
|
||||
console.log("CART", cart)
|
||||
freeNotice = null
|
||||
if cart.product_info.free
|
||||
freeNotice = `<span className="first-one-free">| (first one free)</span>`
|
||||
carts.push(`<div className="cart-item" key={cart.id}>
|
||||
<div className="cart-item-caption">
|
||||
<span>{cart.product_info.sale_display}</span>
|
||||
{freeNotice}
|
||||
</div>
|
||||
<div className="cart-item-price">
|
||||
$ {Number(cart.product_info.real_price).toFixed(2)}
|
||||
</div>
|
||||
<div className="cart-item-quantity">
|
||||
{cart.quantity}
|
||||
</div>
|
||||
<div className="clearall"/>
|
||||
</div>`)
|
||||
|
||||
carts.push(`<div className="cart-item tax-total" key={'tax'}>
|
||||
<div className="cart-item-caption">
|
||||
<span>Tax</span>
|
||||
</div>
|
||||
<div className="cart-item-price">
|
||||
$ {estimatedTax.toFixed(2)}
|
||||
</div>
|
||||
<div className="cart-item-quantity">
|
||||
|
||||
</div>
|
||||
<div className="clearall"/>
|
||||
</div>`)
|
||||
|
||||
carts.push(`<div className="cart-item total" key={'total'}>
|
||||
<div className="cart-item-caption">
|
||||
<span>Total</span>
|
||||
</div>
|
||||
<div className="cart-item-price">
|
||||
$ {estimatedTotal.toFixed(2)}
|
||||
</div>
|
||||
<div className="cart-item-quantity">
|
||||
|
||||
</div>
|
||||
<div className="clearall"/>
|
||||
</div>`)
|
||||
else
|
||||
carts = `<div className="loading-indicator">Loading...</div>`
|
||||
|
||||
`<div className="shopping-cart-contents">
|
||||
<div className="order-items-page">
|
||||
<div className="cart-items">
|
||||
<div className="cart-item-caption">
|
||||
<span>YOUR ORDER INCLUDES:</span>
|
||||
</div>
|
||||
<div className="cart-item-price">
|
||||
<span>PRICE</span>
|
||||
</div>
|
||||
<div className="cart-item-quantity">
|
||||
<span>QUANTITY</span>
|
||||
</div>
|
||||
<div className="clearall"></div>
|
||||
{carts}
|
||||
<div className="clearall"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
onAppInit: (@app) ->
|
||||
@EVENTS = context.JK.EVENTS
|
||||
@rest = context.JK.Rest()
|
||||
@logger = context.JK.logger
|
||||
|
||||
|
||||
onUserChanged: (userState) ->
|
||||
@user = userState?.user
|
||||
|
||||
})
|
||||
|
|
@ -43,6 +43,7 @@ proficiencyDescriptionMap = {
|
|||
TILE_RATINGS: 'ratings'
|
||||
TILE_PRICES: 'prices'
|
||||
visible: false
|
||||
profileClipboard: null
|
||||
|
||||
TILES: ['about', 'experience', 'samples', 'ratings', 'prices']
|
||||
|
||||
|
|
@ -65,11 +66,26 @@ proficiencyDescriptionMap = {
|
|||
@root = $(@getDOMNode())
|
||||
@screen = $('#teacher-profile')
|
||||
@starbox()
|
||||
@clipboard()
|
||||
|
||||
componentDidUpdate:() ->
|
||||
@starbox()
|
||||
|
||||
context.JK.popExternalLinks(@root)
|
||||
@clipboard()
|
||||
|
||||
clipboard: () ->
|
||||
$profileLink = @root.find('.copy-profile-link')
|
||||
|
||||
if $profileLink.length > 0 && !@profileClipboard?
|
||||
# mount it
|
||||
@profileClipboard = new Clipboard($profileLink.get(0), {
|
||||
text: =>
|
||||
return context.JK.makeAbsolute('/client#/teacher/profile/' + @state.user.teacher?.id)
|
||||
})
|
||||
else if $profileLink.length == 0 && @profileClipboard?
|
||||
@profileClipboard.destroy()
|
||||
@profileClipboard = null
|
||||
|
||||
starbox:() ->
|
||||
$ratings = @root.find('.ratings-box')
|
||||
|
|
@ -230,7 +246,9 @@ proficiencyDescriptionMap = {
|
|||
biography = biography.replace(/\n/g, "<br/>")
|
||||
|
||||
`<div className="section bio">
|
||||
<h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
|
||||
<a className="copy-profile-link button-orange" href='#' onClick={this.copyProfileLink}>
|
||||
COPY PROFILE URL TO CLIPBOARD
|
||||
</a><h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
|
||||
<div className="section-content">
|
||||
<div dangerouslySetInnerHTML={{__html: biography}}></div>
|
||||
</div>
|
||||
|
|
@ -694,6 +712,16 @@ proficiencyDescriptionMap = {
|
|||
</div>
|
||||
</div>`
|
||||
|
||||
copyProfileLink: (e) ->
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
@app.layout.notify({
|
||||
title: 'Teacher Profile Link Copied',
|
||||
text: "Your clipboard now has a link to this teacher that you can share with anyone."
|
||||
})
|
||||
|
||||
|
||||
selectionMade: (selection, e) ->
|
||||
e.preventDefault()
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@
|
|||
var details = { clientId: app.clientId, reason: 'rest', detail: arguments, isRecording: false }
|
||||
$self.triggerHandler('startedRecording', details);
|
||||
currentlyRecording = false;
|
||||
context.RecordingActions.startedRecording(details);
|
||||
context.RecordingActions.startedRecording(details);
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
//= require jquery.queryparams
|
||||
//= require jquery.hoverIntent
|
||||
//= require jquery.cookie
|
||||
//= require jquery.clipboard
|
||||
//= require clipboard
|
||||
//= require jquery.easydropdown
|
||||
//= require jquery.carousel-1.1
|
||||
//= require jquery.mousewheel-3.1.9
|
||||
|
|
|
|||
|
|
@ -33,6 +33,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
.paypal-region {
|
||||
text-align: center;
|
||||
margin:10px auto 0;
|
||||
/**margin: 10px auto 0;
|
||||
padding: 10px 10px 5px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
border-color: #ccc;
|
||||
border-style: solid;
|
||||
border-width: 3px;
|
||||
width:145px;*/
|
||||
}
|
||||
|
||||
.or-text {
|
||||
margin: 60px auto 0;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color:white;
|
||||
background-color:#4d4d4d;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
@import "client/common.scss";
|
||||
|
||||
[data-react-class="PayPalConfirmationScreen"] {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
.content-body-scroller {
|
||||
height: calc(100% - 30px) ! important; // 15px top and bottom padding, and 48px used by .controls
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.confirm-header {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.controls.bottom {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.place-order-btn {
|
||||
text-align: center;
|
||||
margin-right:0;
|
||||
}
|
||||
|
||||
.or-holder {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cancel-order-btn {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.shopping-cart-contents {
|
||||
@include border-box_sizing;
|
||||
width: 50%;
|
||||
margin-top:20px;
|
||||
}
|
||||
.controls {
|
||||
@include border-box_sizing;
|
||||
width:50%;
|
||||
a {
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
.loading-indicator {
|
||||
margin-bottom:20px;
|
||||
padding-bottom:20px;
|
||||
}
|
||||
|
||||
.sold-notice {
|
||||
h2 {
|
||||
font-size:30px;
|
||||
text-align:center;
|
||||
}
|
||||
}
|
||||
.download-jamkazam {
|
||||
color:$ColorLink;
|
||||
border-radius: 4px;
|
||||
border-style:solid;
|
||||
border-color:#AAA;
|
||||
border-width:1px;
|
||||
padding:10px;
|
||||
margin-top:20px;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.download-jamkazam-wrapper, .back-to-browsing {
|
||||
text-align:center;
|
||||
display:block;
|
||||
margin-top:35px;
|
||||
|
||||
&.hidden {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
@import "client/common.scss";
|
||||
|
||||
.shopping-cart-contents {
|
||||
|
||||
background-color:#262626;
|
||||
border-width:0 1px 0 0;
|
||||
border-style:solid;
|
||||
border-color:#333;
|
||||
padding:20px 20px 0;
|
||||
.cart-item-caption {
|
||||
width: 50%;
|
||||
text-align: left;
|
||||
float: left;
|
||||
margin-bottom: 10px;
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
.first-one-free {
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.cart-item-price {
|
||||
width: 25%;
|
||||
text-align: right;
|
||||
float: left;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 10px;
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
.cart-item-quantity {
|
||||
width: 10%;
|
||||
text-align: right;
|
||||
float: left;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 10px;
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
.cart-items {
|
||||
margin-top: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.no-cart-items {
|
||||
}
|
||||
|
||||
.tax-total {
|
||||
margin-top:10px;
|
||||
border-width:1px 0 0 0;
|
||||
border-color:white;
|
||||
border-style:solid;
|
||||
padding-top:10px;
|
||||
}
|
||||
.cart-item.total {
|
||||
margin-top:5px;
|
||||
}
|
||||
}
|
||||
|
|
@ -199,6 +199,9 @@
|
|||
position:absolute;
|
||||
}
|
||||
|
||||
.copy-profile-link {
|
||||
float:right;
|
||||
}
|
||||
|
||||
.spinner-large {
|
||||
width:200px;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ class ApiLessonSessionsController < ApiController
|
|||
render "api_lesson_sessions/index", :layout => nil
|
||||
end
|
||||
|
||||
|
||||
def show
|
||||
|
||||
end
|
||||
|
|
@ -72,13 +71,13 @@ class ApiLessonSessionsController < ApiController
|
|||
|
||||
if params[:update_all]
|
||||
# check if the next scheduled lesson is doable
|
||||
if 24.hours.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start
|
||||
if 15.minutes.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start
|
||||
response = {message: 'time_limit'}
|
||||
render :json => response, :status => 422
|
||||
return
|
||||
end
|
||||
else
|
||||
if 24.hours.from_now > @lesson_session.music_session.scheduled_start
|
||||
if 15.minutes.from_now > @lesson_session.music_session.scheduled_start
|
||||
response = {message: 'time_limit'}
|
||||
render :json => response, :status => 422
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
class ApiPayPalController < ApiController
|
||||
|
||||
before_filter :api_signed_in_user
|
||||
|
||||
respond_to :json
|
||||
|
||||
|
||||
def log
|
||||
@log || Logging.logger[VanillaForumsController]
|
||||
end
|
||||
|
||||
def start_checkout
|
||||
cancel_path = params[:path] ? params[:path] : ERB::Util.url_encode('/client#/checkoutPayment')
|
||||
|
||||
tax = true
|
||||
tax_rate = tax ? 0.0825 : 0
|
||||
total = current_user.shopping_cart_total.round(2)
|
||||
tax_total = (total * tax_rate).round(2)
|
||||
total = total + tax_total
|
||||
total = total.round(2)
|
||||
|
||||
|
||||
@api = PayPal::SDK::Merchant::API.new
|
||||
@set_express_checkout = @api.build_set_express_checkout(
|
||||
{
|
||||
:Version => "117.0",
|
||||
:SetExpressCheckoutRequestDetails =>
|
||||
{
|
||||
:ReturnURL => ApplicationHelper.base_uri(request) + '/auth/paypal/checkout',
|
||||
:CancelURL => ApplicationHelper.base_uri(request) + '/auth/paypal/checkout?cancel=1&path=' + cancel_path,
|
||||
# :NoShipping => "1",
|
||||
# :ReqConfirmShipping => "0",
|
||||
# :ReqBillingAddress => "1",
|
||||
:PaymentDetails =>
|
||||
[
|
||||
{
|
||||
:OrderTotal => {
|
||||
:currencyID => "USD",
|
||||
:value => total
|
||||
},
|
||||
:PaymentAction => "Sale"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
@set_express_checkout_response = @api.set_express_checkout(@set_express_checkout)
|
||||
|
||||
log.info("User #{current_user.email}, SetExpressCheckout #{@set_express_checkout_response.inspect}")
|
||||
|
||||
if @set_express_checkout_response.Ack == 'Failure'
|
||||
render json: {message: @set_express_checkout_response.Errors[0].LongMessage}, status: 422
|
||||
return
|
||||
end
|
||||
|
||||
redirect_to Rails.configuration.paypal_express_url + '&token=' + ERB::Util.url_encode(@set_express_checkout_response.Token)
|
||||
end
|
||||
|
||||
# called by frontend after the user comes back from initial express page
|
||||
def checkout_detail
|
||||
# here we can see if they will pay tax
|
||||
|
||||
if !current_user.has_paypal_auth?
|
||||
render json: {}, :status => 404
|
||||
return
|
||||
end
|
||||
paypal_auth = current_user.paypal_auth
|
||||
|
||||
@api = PayPal::SDK::Merchant::API.new
|
||||
@get_express_checkout_details = @api.build_get_express_checkout_details({:Token => paypal_auth.token})
|
||||
@response = @api.get_express_checkout_details(@get_express_checkout_details)
|
||||
|
||||
puts @response.inspect
|
||||
tax = false
|
||||
if @response.Ack == 'Success'
|
||||
payerInfo = @response.GetExpressCheckoutDetailsResponseDetails.PayerInfo
|
||||
if payerInfo.Address && ( payerInfo.Address.Country == 'US' && payerInfo.Address.StateOrProvince == 'TX')
|
||||
# we need to ask for taxes
|
||||
tax = true
|
||||
end
|
||||
else
|
||||
render json: {message: @response.Errors[0].LongMessage}, status: 422
|
||||
return
|
||||
end
|
||||
|
||||
log.debug("User #{current_user.email}, GetExpressCheckout: #{@get_express_checkout_details_response.inspect}")
|
||||
|
||||
render json: {tax: tax}
|
||||
end
|
||||
|
||||
# called by frontend when the user selects finally 'confirm purchase' (PLACE ORDER btn)
|
||||
def confirm_purchase
|
||||
if !current_user.has_paypal_auth?
|
||||
render json: {}, :status => 404
|
||||
return
|
||||
end
|
||||
|
||||
error = nil
|
||||
response = {jam_tracks: [], gift_cards: []}
|
||||
|
||||
#if Sale.is_mixed(current_user.shopping_carts)
|
||||
# msg = "has free and non-free items. Try removing non-free items."
|
||||
# render json: {message: "Cart " + msg, errors: {cart: [msg]}}, :status => 404
|
||||
# return
|
||||
#end
|
||||
|
||||
begin
|
||||
sales = Sale.place_order(current_user, current_user.shopping_carts, true)
|
||||
rescue RecurlyClientError => e
|
||||
render json: {message: e.errors[:message]}, :status => 422
|
||||
return
|
||||
rescue PayPalClientError => x
|
||||
render json: {message: x.errors[:message]}, :status => 422
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
sales.each do |sale|
|
||||
sale.sale_line_items.each do |line_item|
|
||||
if line_item.is_jam_track?
|
||||
jam_track = line_item.product
|
||||
jam_track_right = jam_track.right_for_user(current_user)
|
||||
response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
|
||||
elsif line_item.is_gift_card?
|
||||
gift_card = line_item.product
|
||||
response[:gift_cards] << {name: gift_card.name, id: gift_card.id}
|
||||
else
|
||||
raise 'unknown sale line item type: ' + line_item.product_type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
set_purchased_jamtrack_cookie
|
||||
render :json => response, :status => 200
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
# this is not a jam session - this is an 'auth session'
|
||||
class SessionsController < ApplicationController
|
||||
|
||||
before_filter :api_signed_in_user, only: :paypal_express_checkout
|
||||
|
||||
layout "web"
|
||||
|
||||
def signin
|
||||
|
|
@ -37,6 +39,42 @@ class SessionsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def paypal_express_checkout
|
||||
# should get 'token' and 'PayerID' on success
|
||||
|
||||
# on failure, cancel=1
|
||||
|
||||
if params[:cancel] == '1' || params[:cancel] == 1
|
||||
redirect_to params[:path] ? params[:path] : '/client#/jamtrack'
|
||||
return
|
||||
end
|
||||
|
||||
authorization = current_user.paypal_auth
|
||||
|
||||
# Always make and save a new authorization. This is because they expire, and honestly there's no cost
|
||||
# to just making and saving it.
|
||||
|
||||
user_auth_hash = {
|
||||
:provider => 'paypal',
|
||||
:uid => params[:PayerID],
|
||||
:token => params[:token],
|
||||
:refresh_token => nil,
|
||||
:token_expiration => 3.hours.from_now, # according to paypal docs, a token is good for 3 hours
|
||||
:secret => nil
|
||||
}
|
||||
|
||||
if authorization.nil?
|
||||
authorization = current_user.user_authorizations.build(user_auth_hash)
|
||||
authorization.save
|
||||
else
|
||||
authorization.token = user_auth_hash[:token]
|
||||
authorization.token_expiration = user_auth_hash[:token_expiration]
|
||||
authorization.uid = user_auth_hash[:uid]
|
||||
authorization.save
|
||||
end
|
||||
|
||||
redirect_to '/client#/paypal/confirm'
|
||||
end
|
||||
|
||||
# OAuth docs
|
||||
# http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/
|
||||
|
|
|
|||
|
|
@ -120,6 +120,13 @@ div layout="screen" layout-id="checkoutPayment" id="checkoutPaymentScreen" class
|
|||
.divSaveCardHelper
|
||||
label for="save-card" Save card for future use
|
||||
.clearall
|
||||
|
||||
- if !Rails.application.config.paypal_admin_only || any_user.admin
|
||||
.or-text or instead use:
|
||||
.paypal-region
|
||||
a href="/paypal/checkout/start" data-paypal-button="true"
|
||||
img src="https://www.paypalobjects.com/webstatic/en_US/i/btn/png/gold-pill-paypalcheckout-34px.png" alt="PayPal Checkout"
|
||||
a
|
||||
.clearall
|
||||
.clearall
|
||||
.row.second
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
.screen.secondary layout='screen' layout-id='paypal/confirm'
|
||||
.content
|
||||
.content-head
|
||||
.content-icon=image_tag("content/icon_jamtracks.png", height: 19, width: 19)
|
||||
h1 confirm payment
|
||||
= render "screen_navigation"
|
||||
.content-body
|
||||
= react_component 'PayPalConfirmationScreen', {}
|
||||
|
|
@ -58,6 +58,7 @@
|
|||
<%= render "jamtrack_search" %>
|
||||
<%= render "jamtrack_filter" %>
|
||||
<%= render "jamtrack_landing" %>
|
||||
<%= render "paypal_confirmation" %>
|
||||
<%= render "shopping_cart" %>
|
||||
<%= render "checkout_signin" %>
|
||||
<%= render "checkout_payment" %>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
<div class="share-link border-bottom">
|
||||
<h3>Share a Link:</h3>
|
||||
<div class="link-contents">
|
||||
<div class="link-contents" id="link-contents">
|
||||
</div>
|
||||
|
||||
<div class="right"><a id="btn-share-copy" class="button-orange">COPY LINK</a></div>
|
||||
|
|
|
|||
|
|
@ -452,6 +452,17 @@ if defined?(Bundler)
|
|||
# This would transparently migrate your existing Marshal-serialized cookies into the new JSON-based format.
|
||||
config.action_dispatch.cookies_serializer = :hybrid
|
||||
|
||||
config.jam_class_card_wait_period_year = 1
|
||||
config.jam_class_card_wait_period_year = 1
|
||||
|
||||
config.paypal_mode = 'sandbox' #sandbox or live
|
||||
config.app_id = 'APP-80W284485P519543T' # this is constant across all sandbox accts
|
||||
config.paypal_username = 'seth+ppmerchant_api1.jamkazam.com' # seth+ppmerchant@jamkazam.com' # 'seth+ppmerchant_api1.jamkazam.com'
|
||||
config.paypal_client_id = 'AZ5CCd8lHNntZ0ddxk_Wvo6LUaACd-bsMr7OPu_M1oI9vPN3d89mdSKswns9GEKB57qIwTT9_NyyK1c8'
|
||||
config.paypal_client_secret = 'EIK0KWr8G5ntzYyJzbGCrsdr62aaJw2PdtXJrQddI9WuraBxJfiL4aMtKWu7Cyo4ACt13hLvCqg84HjP'
|
||||
config.paypal_password = 'UXDKMPLYNE5YRLMK'
|
||||
config.paypal_signature = 'AFcWxV21C7fd0v3bYYYRCpSSRl31AmvhBjN40M8etif4EA.L8EyMxdsu'
|
||||
config.paypal_debug = true
|
||||
config.paypal_express_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout' # drop sandbox for production
|
||||
config.paypal_admin_only = true
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
PayPal::SDK.configure(
|
||||
:mode => Rails.configuration.paypal_mode,
|
||||
:username => Rails.configuration.paypal_username,
|
||||
:password => Rails.configuration.paypal_password,
|
||||
:signature => Rails.configuration.paypal_signature,
|
||||
:ssl_options => { } )
|
||||
|
||||
# :client_id => Rails.configuration.paypal_client_id,
|
||||
# :client_secret => Rails.configuration.paypal_client_secret,
|
||||
|
||||
|
||||
PayPal::SDK.logger = Logging.logger['PayPal']
|
||||
PayPal::SDK.logger.level = Rails.configuration.paypal_debug ? Logger::DEBUG : Logger::INFO
|
||||
|
|
@ -77,6 +77,7 @@ Rails.application.routes.draw do
|
|||
get '/auth/:provider/callback', :to => 'sessions#oauth_callback'
|
||||
get '/auth/failure', :to => 'sessions#failure'
|
||||
get '/auth/has_google_auth', :to => 'sessions#has_google_auth'
|
||||
get '/auth/paypal/checkout', :to => 'sessions#paypal_express_checkout'
|
||||
|
||||
# session info page
|
||||
get '/sessions/:id/details' => 'music_sessions#session_info', :as => 'music_scheduled_session_detail'
|
||||
|
|
@ -125,6 +126,8 @@ Rails.application.routes.draw do
|
|||
|
||||
get '/endorse/:id/:service', to: 'users#endorse', :as => 'endorse'
|
||||
|
||||
get '/paypal/checkout/start' => 'api_pay_pal#start_checkout'
|
||||
|
||||
# embed resque-web if this is development mode
|
||||
if Rails.env == "development" || Rails.application.config.allow_spikes
|
||||
require 'resque/server'
|
||||
|
|
@ -260,7 +263,6 @@ Rails.application.routes.draw do
|
|||
match '/music_notations/:id' => 'api_music_notations#download', :via => :get, :as => :download_music_notation
|
||||
match '/music_notations/:id' => 'api_music_notations#delete', :via => :delete, :as => :delete_music_notation
|
||||
|
||||
|
||||
# Backing track_show
|
||||
match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list'
|
||||
|
||||
|
|
@ -363,6 +365,10 @@ Rails.application.routes.draw do
|
|||
match '/recurly/place_order' => 'api_recurly#place_order', :via => :post
|
||||
match '/ios/order_placed' => 'api_jam_tracks#ios_order_placed', :via => :post
|
||||
|
||||
# paypal
|
||||
match '/paypal/checkout/detail' => 'api_pay_pal#checkout_detail', :via => :post
|
||||
match '/paypal/checkout/confirm' => 'api_pay_pal#confirm_purchase', :via => :post
|
||||
|
||||
# sale info
|
||||
match '/payment_histories' => 'api_payment_histories#index', :via => :get
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,753 @@
|
|||
/*!
|
||||
* clipboard.js v1.5.15
|
||||
* https://zenorocha.github.io/clipboard.js
|
||||
*
|
||||
* Licensed MIT © Zeno Rocha
|
||||
*/
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
/**
|
||||
* A polyfill for Element.matches()
|
||||
*/
|
||||
if (Element && !Element.prototype.matches) {
|
||||
var proto = Element.prototype;
|
||||
|
||||
proto.matches = proto.matchesSelector ||
|
||||
proto.mozMatchesSelector ||
|
||||
proto.msMatchesSelector ||
|
||||
proto.oMatchesSelector ||
|
||||
proto.webkitMatchesSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the closest parent that matches a selector.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {String} selector
|
||||
* @return {Function}
|
||||
*/
|
||||
function closest (element, selector) {
|
||||
while (element && element !== document) {
|
||||
if (element.matches(selector)) return element;
|
||||
element = element.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = closest;
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
var closest = require('./closest');
|
||||
|
||||
/**
|
||||
* Delegates event to a selector.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {String} selector
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @param {Boolean} useCapture
|
||||
* @return {Object}
|
||||
*/
|
||||
function delegate(element, selector, type, callback, useCapture) {
|
||||
var listenerFn = listener.apply(this, arguments);
|
||||
|
||||
element.addEventListener(type, listenerFn, useCapture);
|
||||
|
||||
return {
|
||||
destroy: function() {
|
||||
element.removeEventListener(type, listenerFn, useCapture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds closest match and invokes callback.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {String} selector
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Function}
|
||||
*/
|
||||
function listener(element, selector, type, callback) {
|
||||
return function(e) {
|
||||
e.delegateTarget = closest(e.target, selector);
|
||||
|
||||
if (e.delegateTarget) {
|
||||
callback.call(element, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = delegate;
|
||||
|
||||
},{"./closest":1}],3:[function(require,module,exports){
|
||||
/**
|
||||
* Check if argument is a HTML element.
|
||||
*
|
||||
* @param {Object} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
exports.node = function(value) {
|
||||
return value !== undefined
|
||||
&& value instanceof HTMLElement
|
||||
&& value.nodeType === 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if argument is a list of HTML elements.
|
||||
*
|
||||
* @param {Object} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
exports.nodeList = function(value) {
|
||||
var type = Object.prototype.toString.call(value);
|
||||
|
||||
return value !== undefined
|
||||
&& (type === '[object NodeList]' || type === '[object HTMLCollection]')
|
||||
&& ('length' in value)
|
||||
&& (value.length === 0 || exports.node(value[0]));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if argument is a string.
|
||||
*
|
||||
* @param {Object} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
exports.string = function(value) {
|
||||
return typeof value === 'string'
|
||||
|| value instanceof String;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if argument is a function.
|
||||
*
|
||||
* @param {Object} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
exports.fn = function(value) {
|
||||
var type = Object.prototype.toString.call(value);
|
||||
|
||||
return type === '[object Function]';
|
||||
};
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
var is = require('./is');
|
||||
var delegate = require('delegate');
|
||||
|
||||
/**
|
||||
* Validates all params and calls the right
|
||||
* listener function based on its target type.
|
||||
*
|
||||
* @param {String|HTMLElement|HTMLCollection|NodeList} target
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
function listen(target, type, callback) {
|
||||
if (!target && !type && !callback) {
|
||||
throw new Error('Missing required arguments');
|
||||
}
|
||||
|
||||
if (!is.string(type)) {
|
||||
throw new TypeError('Second argument must be a String');
|
||||
}
|
||||
|
||||
if (!is.fn(callback)) {
|
||||
throw new TypeError('Third argument must be a Function');
|
||||
}
|
||||
|
||||
if (is.node(target)) {
|
||||
return listenNode(target, type, callback);
|
||||
}
|
||||
else if (is.nodeList(target)) {
|
||||
return listenNodeList(target, type, callback);
|
||||
}
|
||||
else if (is.string(target)) {
|
||||
return listenSelector(target, type, callback);
|
||||
}
|
||||
else {
|
||||
throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener to a HTML element
|
||||
* and returns a remove listener function.
|
||||
*
|
||||
* @param {HTMLElement} node
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
function listenNode(node, type, callback) {
|
||||
node.addEventListener(type, callback);
|
||||
|
||||
return {
|
||||
destroy: function() {
|
||||
node.removeEventListener(type, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener to a list of HTML elements
|
||||
* and returns a remove listener function.
|
||||
*
|
||||
* @param {NodeList|HTMLCollection} nodeList
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
function listenNodeList(nodeList, type, callback) {
|
||||
Array.prototype.forEach.call(nodeList, function(node) {
|
||||
node.addEventListener(type, callback);
|
||||
});
|
||||
|
||||
return {
|
||||
destroy: function() {
|
||||
Array.prototype.forEach.call(nodeList, function(node) {
|
||||
node.removeEventListener(type, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener to a selector
|
||||
* and returns a remove listener function.
|
||||
*
|
||||
* @param {String} selector
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
function listenSelector(selector, type, callback) {
|
||||
return delegate(document.body, selector, type, callback);
|
||||
}
|
||||
|
||||
module.exports = listen;
|
||||
|
||||
},{"./is":3,"delegate":2}],5:[function(require,module,exports){
|
||||
function select(element) {
|
||||
var selectedText;
|
||||
|
||||
if (element.nodeName === 'SELECT') {
|
||||
element.focus();
|
||||
|
||||
selectedText = element.value;
|
||||
}
|
||||
else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
||||
element.focus();
|
||||
element.setSelectionRange(0, element.value.length);
|
||||
|
||||
selectedText = element.value;
|
||||
}
|
||||
else {
|
||||
if (element.hasAttribute('contenteditable')) {
|
||||
element.focus();
|
||||
}
|
||||
|
||||
var selection = window.getSelection();
|
||||
var range = document.createRange();
|
||||
|
||||
range.selectNodeContents(element);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
selectedText = selection.toString();
|
||||
}
|
||||
|
||||
return selectedText;
|
||||
}
|
||||
|
||||
module.exports = select;
|
||||
|
||||
},{}],6:[function(require,module,exports){
|
||||
function E () {
|
||||
// Keep this empty so it's easier to inherit from
|
||||
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
|
||||
}
|
||||
|
||||
E.prototype = {
|
||||
on: function (name, callback, ctx) {
|
||||
var e = this.e || (this.e = {});
|
||||
|
||||
(e[name] || (e[name] = [])).push({
|
||||
fn: callback,
|
||||
ctx: ctx
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
once: function (name, callback, ctx) {
|
||||
var self = this;
|
||||
function listener () {
|
||||
self.off(name, listener);
|
||||
callback.apply(ctx, arguments);
|
||||
};
|
||||
|
||||
listener._ = callback
|
||||
return this.on(name, listener, ctx);
|
||||
},
|
||||
|
||||
emit: function (name) {
|
||||
var data = [].slice.call(arguments, 1);
|
||||
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
|
||||
var i = 0;
|
||||
var len = evtArr.length;
|
||||
|
||||
for (i; i < len; i++) {
|
||||
evtArr[i].fn.apply(evtArr[i].ctx, data);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
off: function (name, callback) {
|
||||
var e = this.e || (this.e = {});
|
||||
var evts = e[name];
|
||||
var liveEvents = [];
|
||||
|
||||
if (evts && callback) {
|
||||
for (var i = 0, len = evts.length; i < len; i++) {
|
||||
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
|
||||
liveEvents.push(evts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove event from queue to prevent memory leak
|
||||
// Suggested by https://github.com/lazd
|
||||
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
|
||||
|
||||
(liveEvents.length)
|
||||
? e[name] = liveEvents
|
||||
: delete e[name];
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = E;
|
||||
|
||||
},{}],7:[function(require,module,exports){
|
||||
(function (global, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(['module', 'select'], factory);
|
||||
} else if (typeof exports !== "undefined") {
|
||||
factory(module, require('select'));
|
||||
} else {
|
||||
var mod = {
|
||||
exports: {}
|
||||
};
|
||||
factory(mod, global.select);
|
||||
global.clipboardAction = mod.exports;
|
||||
}
|
||||
})(this, function (module, _select) {
|
||||
'use strict';
|
||||
|
||||
var _select2 = _interopRequireDefault(_select);
|
||||
|
||||
function _interopRequireDefault(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
default: obj
|
||||
};
|
||||
}
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
|
||||
return typeof obj;
|
||||
} : function (obj) {
|
||||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
||||
};
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
}
|
||||
|
||||
var _createClass = function () {
|
||||
function defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var descriptor = props[i];
|
||||
descriptor.enumerable = descriptor.enumerable || false;
|
||||
descriptor.configurable = true;
|
||||
if ("value" in descriptor) descriptor.writable = true;
|
||||
Object.defineProperty(target, descriptor.key, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return function (Constructor, protoProps, staticProps) {
|
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||
if (staticProps) defineProperties(Constructor, staticProps);
|
||||
return Constructor;
|
||||
};
|
||||
}();
|
||||
|
||||
var ClipboardAction = function () {
|
||||
/**
|
||||
* @param {Object} options
|
||||
*/
|
||||
function ClipboardAction(options) {
|
||||
_classCallCheck(this, ClipboardAction);
|
||||
|
||||
this.resolveOptions(options);
|
||||
this.initSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines base properties passed from constructor.
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
|
||||
_createClass(ClipboardAction, [{
|
||||
key: 'resolveOptions',
|
||||
value: function resolveOptions() {
|
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
|
||||
this.action = options.action;
|
||||
this.emitter = options.emitter;
|
||||
this.target = options.target;
|
||||
this.text = options.text;
|
||||
this.trigger = options.trigger;
|
||||
|
||||
this.selectedText = '';
|
||||
}
|
||||
}, {
|
||||
key: 'initSelection',
|
||||
value: function initSelection() {
|
||||
if (this.text) {
|
||||
this.selectFake();
|
||||
} else if (this.target) {
|
||||
this.selectTarget();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'selectFake',
|
||||
value: function selectFake() {
|
||||
var _this = this;
|
||||
|
||||
var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
|
||||
|
||||
this.removeFake();
|
||||
|
||||
this.fakeHandlerCallback = function () {
|
||||
return _this.removeFake();
|
||||
};
|
||||
this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || true;
|
||||
|
||||
this.fakeElem = document.createElement('textarea');
|
||||
// Prevent zooming on iOS
|
||||
this.fakeElem.style.fontSize = '12pt';
|
||||
// Reset box model
|
||||
this.fakeElem.style.border = '0';
|
||||
this.fakeElem.style.padding = '0';
|
||||
this.fakeElem.style.margin = '0';
|
||||
// Move element out of screen horizontally
|
||||
this.fakeElem.style.position = 'absolute';
|
||||
this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
|
||||
// Move element to the same position vertically
|
||||
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
|
||||
this.fakeElem.addEventListener('focus', window.scrollTo(0, yPosition));
|
||||
this.fakeElem.style.top = yPosition + 'px';
|
||||
|
||||
this.fakeElem.setAttribute('readonly', '');
|
||||
this.fakeElem.value = this.text;
|
||||
|
||||
document.body.appendChild(this.fakeElem);
|
||||
|
||||
this.selectedText = (0, _select2.default)(this.fakeElem);
|
||||
this.copyText();
|
||||
}
|
||||
}, {
|
||||
key: 'removeFake',
|
||||
value: function removeFake() {
|
||||
if (this.fakeHandler) {
|
||||
document.body.removeEventListener('click', this.fakeHandlerCallback);
|
||||
this.fakeHandler = null;
|
||||
this.fakeHandlerCallback = null;
|
||||
}
|
||||
|
||||
if (this.fakeElem) {
|
||||
document.body.removeChild(this.fakeElem);
|
||||
this.fakeElem = null;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'selectTarget',
|
||||
value: function selectTarget() {
|
||||
this.selectedText = (0, _select2.default)(this.target);
|
||||
this.copyText();
|
||||
}
|
||||
}, {
|
||||
key: 'copyText',
|
||||
value: function copyText() {
|
||||
var succeeded = void 0;
|
||||
|
||||
try {
|
||||
succeeded = document.execCommand(this.action);
|
||||
} catch (err) {
|
||||
succeeded = false;
|
||||
}
|
||||
|
||||
this.handleResult(succeeded);
|
||||
}
|
||||
}, {
|
||||
key: 'handleResult',
|
||||
value: function handleResult(succeeded) {
|
||||
this.emitter.emit(succeeded ? 'success' : 'error', {
|
||||
action: this.action,
|
||||
text: this.selectedText,
|
||||
trigger: this.trigger,
|
||||
clearSelection: this.clearSelection.bind(this)
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'clearSelection',
|
||||
value: function clearSelection() {
|
||||
if (this.target) {
|
||||
this.target.blur();
|
||||
}
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
}, {
|
||||
key: 'destroy',
|
||||
value: function destroy() {
|
||||
this.removeFake();
|
||||
}
|
||||
}, {
|
||||
key: 'action',
|
||||
set: function set() {
|
||||
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';
|
||||
|
||||
this._action = action;
|
||||
|
||||
if (this._action !== 'copy' && this._action !== 'cut') {
|
||||
throw new Error('Invalid "action" value, use either "copy" or "cut"');
|
||||
}
|
||||
},
|
||||
get: function get() {
|
||||
return this._action;
|
||||
}
|
||||
}, {
|
||||
key: 'target',
|
||||
set: function set(target) {
|
||||
if (target !== undefined) {
|
||||
if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
|
||||
if (this.action === 'copy' && target.hasAttribute('disabled')) {
|
||||
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
|
||||
}
|
||||
|
||||
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
|
||||
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
|
||||
}
|
||||
|
||||
this._target = target;
|
||||
} else {
|
||||
throw new Error('Invalid "target" value, use a valid Element');
|
||||
}
|
||||
}
|
||||
},
|
||||
get: function get() {
|
||||
return this._target;
|
||||
}
|
||||
}]);
|
||||
|
||||
return ClipboardAction;
|
||||
}();
|
||||
|
||||
module.exports = ClipboardAction;
|
||||
});
|
||||
|
||||
},{"select":5}],8:[function(require,module,exports){
|
||||
(function (global, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], factory);
|
||||
} else if (typeof exports !== "undefined") {
|
||||
factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener'));
|
||||
} else {
|
||||
var mod = {
|
||||
exports: {}
|
||||
};
|
||||
factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener);
|
||||
global.clipboard = mod.exports;
|
||||
}
|
||||
})(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) {
|
||||
'use strict';
|
||||
|
||||
var _clipboardAction2 = _interopRequireDefault(_clipboardAction);
|
||||
|
||||
var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);
|
||||
|
||||
var _goodListener2 = _interopRequireDefault(_goodListener);
|
||||
|
||||
function _interopRequireDefault(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
default: obj
|
||||
};
|
||||
}
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
}
|
||||
|
||||
var _createClass = function () {
|
||||
function defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var descriptor = props[i];
|
||||
descriptor.enumerable = descriptor.enumerable || false;
|
||||
descriptor.configurable = true;
|
||||
if ("value" in descriptor) descriptor.writable = true;
|
||||
Object.defineProperty(target, descriptor.key, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return function (Constructor, protoProps, staticProps) {
|
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||
if (staticProps) defineProperties(Constructor, staticProps);
|
||||
return Constructor;
|
||||
};
|
||||
}();
|
||||
|
||||
function _possibleConstructorReturn(self, call) {
|
||||
if (!self) {
|
||||
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||||
}
|
||||
|
||||
return call && (typeof call === "object" || typeof call === "function") ? call : self;
|
||||
}
|
||||
|
||||
function _inherits(subClass, superClass) {
|
||||
if (typeof superClass !== "function" && superClass !== null) {
|
||||
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
|
||||
}
|
||||
|
||||
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
||||
constructor: {
|
||||
value: subClass,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
|
||||
}
|
||||
|
||||
var Clipboard = function (_Emitter) {
|
||||
_inherits(Clipboard, _Emitter);
|
||||
|
||||
/**
|
||||
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
|
||||
* @param {Object} options
|
||||
*/
|
||||
function Clipboard(trigger, options) {
|
||||
_classCallCheck(this, Clipboard);
|
||||
|
||||
var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this));
|
||||
|
||||
_this.resolveOptions(options);
|
||||
_this.listenClick(trigger);
|
||||
return _this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if attributes would be resolved using internal setter functions
|
||||
* or custom functions that were passed in the constructor.
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
|
||||
_createClass(Clipboard, [{
|
||||
key: 'resolveOptions',
|
||||
value: function resolveOptions() {
|
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
|
||||
this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
|
||||
this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
|
||||
this.text = typeof options.text === 'function' ? options.text : this.defaultText;
|
||||
}
|
||||
}, {
|
||||
key: 'listenClick',
|
||||
value: function listenClick(trigger) {
|
||||
var _this2 = this;
|
||||
|
||||
this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) {
|
||||
return _this2.onClick(e);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'onClick',
|
||||
value: function onClick(e) {
|
||||
var trigger = e.delegateTarget || e.currentTarget;
|
||||
|
||||
if (this.clipboardAction) {
|
||||
this.clipboardAction = null;
|
||||
}
|
||||
|
||||
this.clipboardAction = new _clipboardAction2.default({
|
||||
action: this.action(trigger),
|
||||
target: this.target(trigger),
|
||||
text: this.text(trigger),
|
||||
trigger: trigger,
|
||||
emitter: this
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'defaultAction',
|
||||
value: function defaultAction(trigger) {
|
||||
return getAttributeValue('action', trigger);
|
||||
}
|
||||
}, {
|
||||
key: 'defaultTarget',
|
||||
value: function defaultTarget(trigger) {
|
||||
var selector = getAttributeValue('target', trigger);
|
||||
|
||||
if (selector) {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'defaultText',
|
||||
value: function defaultText(trigger) {
|
||||
return getAttributeValue('text', trigger);
|
||||
}
|
||||
}, {
|
||||
key: 'destroy',
|
||||
value: function destroy() {
|
||||
this.listener.destroy();
|
||||
|
||||
if (this.clipboardAction) {
|
||||
this.clipboardAction.destroy();
|
||||
this.clipboardAction = null;
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
||||
return Clipboard;
|
||||
}(_tinyEmitter2.default);
|
||||
|
||||
/**
|
||||
* Helper function to retrieve attribute value.
|
||||
* @param {String} suffix
|
||||
* @param {Element} element
|
||||
*/
|
||||
function getAttributeValue(suffix, element) {
|
||||
var attribute = 'data-clipboard-' + suffix;
|
||||
|
||||
if (!element.hasAttribute(attribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return element.getAttribute(attribute);
|
||||
}
|
||||
|
||||
module.exports = Clipboard;
|
||||
});
|
||||
|
||||
},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)
|
||||
});
|
||||
|
|
@ -1,611 +0,0 @@
|
|||
/*
|
||||
* jQuery Clipboard :: Fork of zClip :: Uses ZeroClipboard v1.2.3
|
||||
*
|
||||
* https://github.com/valeriansaliou/jquery.clipboard
|
||||
* http://www.steamdev.com/zclip/
|
||||
*
|
||||
* Copyright 2013, Valérian Saliou
|
||||
* Copyright 2011, SteamDev
|
||||
*
|
||||
* Released under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Version: v1.2
|
||||
* Date: Sun Dec 1, 2013
|
||||
*/
|
||||
|
||||
/* Component: jQuery Clipboard */
|
||||
(function ($) {
|
||||
var $clip = null;
|
||||
var $is_loaded = false;
|
||||
|
||||
$.fn.clipboard = function (params) {
|
||||
if ((typeof params == 'object' && !params.length) || (typeof params == 'undefined')) {
|
||||
var settings = $.extend({
|
||||
path: 'jquery.clipboard.swf',
|
||||
copy: null,
|
||||
beforeCopy: null,
|
||||
afterCopy: null,
|
||||
clickAfter: true
|
||||
}, (params || {}));
|
||||
|
||||
return this.each(function () {
|
||||
var o = $(this);
|
||||
|
||||
if (o.is(':visible') && (typeof settings.copy == 'string' || $.isFunction(settings.copy))) {
|
||||
if ($.isFunction(settings.copy)) {
|
||||
o.bind('Clipboard_copy',settings.copy);
|
||||
}
|
||||
if ($.isFunction(settings.beforeCopy)) {
|
||||
o.bind('Clipboard_beforeCopy',settings.beforeCopy);
|
||||
}
|
||||
if ($.isFunction(settings.afterCopy)) {
|
||||
o.bind('Clipboard_afterCopy',settings.afterCopy);
|
||||
}
|
||||
|
||||
if($clip === null) {
|
||||
$clip = new ZeroClipboard(null, {
|
||||
moviePath: settings.path,
|
||||
trustedDomains: '*',
|
||||
hoverClass: 'hover',
|
||||
activeClass: 'active'
|
||||
});
|
||||
|
||||
$clip.on('load', function(client) {
|
||||
client.on('mouseover', function (client) {
|
||||
$(this).trigger('mouseenter');
|
||||
});
|
||||
|
||||
client.on('mouseout', function (client) {
|
||||
$(this).trigger('mouseleave');
|
||||
});
|
||||
|
||||
client.on('mousedown', function (client) {
|
||||
$(this).trigger('mousedown');
|
||||
|
||||
if (!$.isFunction(settings.copy)) {
|
||||
client.setText(settings.copy);
|
||||
} else {
|
||||
client.setText($(this).triggerHandler('Clipboard_copy'));
|
||||
}
|
||||
|
||||
if ($.isFunction(settings.beforeCopy)) {
|
||||
$(this).trigger('Clipboard_beforeCopy');
|
||||
}
|
||||
});
|
||||
|
||||
client.on('complete', function (client, args) {
|
||||
if ($.isFunction(settings.afterCopy)) {
|
||||
$(this).trigger('Clipboard_afterCopy');
|
||||
} else {
|
||||
$(this).removeClass('hover');
|
||||
}
|
||||
|
||||
if (settings.clickAfter) {
|
||||
$(this).trigger('click');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$clip.glue(o[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
/* Component: ZeroClipboard */
|
||||
(function() {
|
||||
"use strict";
|
||||
var _camelizeCssPropName = function() {
|
||||
var matcherRegex = /\-([a-z])/g, replacerFn = function(match, group) {
|
||||
return group.toUpperCase();
|
||||
};
|
||||
return function(prop) {
|
||||
return prop.replace(matcherRegex, replacerFn);
|
||||
};
|
||||
}();
|
||||
var _getStyle = function(el, prop) {
|
||||
var value, camelProp, tagName, possiblePointers, i, len;
|
||||
if (window.getComputedStyle) {
|
||||
value = window.getComputedStyle(el, null).getPropertyValue(prop);
|
||||
} else {
|
||||
camelProp = _camelizeCssPropName(prop);
|
||||
if (el.currentStyle) {
|
||||
value = el.currentStyle[camelProp];
|
||||
} else {
|
||||
value = el.style[camelProp];
|
||||
}
|
||||
}
|
||||
if (prop === "cursor") {
|
||||
if (!value || value === "auto") {
|
||||
tagName = el.tagName.toLowerCase();
|
||||
possiblePointers = [ "a" ];
|
||||
for (i = 0, len = possiblePointers.length; i < len; i++) {
|
||||
if (tagName === possiblePointers[i]) {
|
||||
return "pointer";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
var _elementMouseOver = function(event) {
|
||||
if (!ZeroClipboard.prototype._singleton) return;
|
||||
if (!event) {
|
||||
event = window.event;
|
||||
}
|
||||
var target;
|
||||
if (this !== window) {
|
||||
target = this;
|
||||
} else if (event.target) {
|
||||
target = event.target;
|
||||
} else if (event.srcElement) {
|
||||
target = event.srcElement;
|
||||
}
|
||||
ZeroClipboard.prototype._singleton.setCurrent(target);
|
||||
};
|
||||
var _addEventHandler = function(element, method, func) {
|
||||
if (element.addEventListener) {
|
||||
element.addEventListener(method, func, false);
|
||||
} else if (element.attachEvent) {
|
||||
element.attachEvent("on" + method, func);
|
||||
}
|
||||
};
|
||||
var _removeEventHandler = function(element, method, func) {
|
||||
if (element.removeEventListener) {
|
||||
element.removeEventListener(method, func, false);
|
||||
} else if (element.detachEvent) {
|
||||
element.detachEvent("on" + method, func);
|
||||
}
|
||||
};
|
||||
var _addClass = function(element, value) {
|
||||
if (element.addClass) {
|
||||
element.addClass(value);
|
||||
return element;
|
||||
}
|
||||
if (value && typeof value === "string") {
|
||||
var classNames = (value || "").split(/\s+/);
|
||||
if (element.nodeType === 1) {
|
||||
if (!element.className) {
|
||||
element.className = value;
|
||||
} else {
|
||||
var className = " " + element.className + " ", setClass = element.className;
|
||||
for (var c = 0, cl = classNames.length; c < cl; c++) {
|
||||
if (className.indexOf(" " + classNames[c] + " ") < 0) {
|
||||
setClass += " " + classNames[c];
|
||||
}
|
||||
}
|
||||
element.className = setClass.replace(/^\s+|\s+$/g, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
return element;
|
||||
};
|
||||
var _removeClass = function(element, value) {
|
||||
if (element.removeClass) {
|
||||
element.removeClass(value);
|
||||
return element;
|
||||
}
|
||||
if (value && typeof value === "string" || value === undefined) {
|
||||
var classNames = (value || "").split(/\s+/);
|
||||
if (element.nodeType === 1 && element.className) {
|
||||
if (value) {
|
||||
var className = (" " + element.className + " ").replace(/[\n\t]/g, " ");
|
||||
for (var c = 0, cl = classNames.length; c < cl; c++) {
|
||||
className = className.replace(" " + classNames[c] + " ", " ");
|
||||
}
|
||||
element.className = className.replace(/^\s+|\s+$/g, "");
|
||||
} else {
|
||||
element.className = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return element;
|
||||
};
|
||||
var _getZoomFactor = function() {
|
||||
var rect, physicalWidth, logicalWidth, zoomFactor = 1;
|
||||
if (typeof document.body.getBoundingClientRect === "function") {
|
||||
rect = document.body.getBoundingClientRect();
|
||||
physicalWidth = rect.right - rect.left;
|
||||
logicalWidth = document.body.offsetWidth;
|
||||
zoomFactor = Math.round(physicalWidth / logicalWidth * 100) / 100;
|
||||
}
|
||||
return zoomFactor;
|
||||
};
|
||||
var _getDOMObjectPosition = function(obj) {
|
||||
var info = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
zIndex: 999999999
|
||||
};
|
||||
var zi = _getStyle(obj, "z-index");
|
||||
if (zi && zi !== "auto") {
|
||||
info.zIndex = parseInt(zi, 10);
|
||||
}
|
||||
if (obj.getBoundingClientRect) {
|
||||
var rect = obj.getBoundingClientRect();
|
||||
var pageXOffset, pageYOffset, zoomFactor;
|
||||
if ("pageXOffset" in window && "pageYOffset" in window) {
|
||||
pageXOffset = window.pageXOffset;
|
||||
pageYOffset = window.pageYOffset;
|
||||
} else {
|
||||
zoomFactor = _getZoomFactor();
|
||||
pageXOffset = Math.round(document.documentElement.scrollLeft / zoomFactor);
|
||||
pageYOffset = Math.round(document.documentElement.scrollTop / zoomFactor);
|
||||
}
|
||||
var leftBorderWidth = document.documentElement.clientLeft || 0;
|
||||
var topBorderWidth = document.documentElement.clientTop || 0;
|
||||
info.left = rect.left + pageXOffset - leftBorderWidth;
|
||||
info.top = rect.top + pageYOffset - topBorderWidth;
|
||||
info.width = "width" in rect ? rect.width : rect.right - rect.left;
|
||||
info.height = "height" in rect ? rect.height : rect.bottom - rect.top;
|
||||
}
|
||||
return info;
|
||||
};
|
||||
var _noCache = function(path, options) {
|
||||
var useNoCache = !(options && options.useNoCache === false);
|
||||
if (useNoCache) {
|
||||
return (path.indexOf("?") === -1 ? "?" : "&") + "nocache=" + new Date().getTime();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
var _vars = function(options) {
|
||||
var str = [];
|
||||
var origins = [];
|
||||
if (options.trustedOrigins) {
|
||||
if (typeof options.trustedOrigins === "string") {
|
||||
origins.push(options.trustedOrigins);
|
||||
} else if (typeof options.trustedOrigins === "object" && "length" in options.trustedOrigins) {
|
||||
origins = origins.concat(options.trustedOrigins);
|
||||
}
|
||||
}
|
||||
if (options.trustedDomains) {
|
||||
if (typeof options.trustedDomains === "string") {
|
||||
origins.push(options.trustedDomains);
|
||||
} else if (typeof options.trustedDomains === "object" && "length" in options.trustedDomains) {
|
||||
origins = origins.concat(options.trustedDomains);
|
||||
}
|
||||
}
|
||||
if (origins.length) {
|
||||
str.push("trustedOrigins=" + encodeURIComponent(origins.join(",")));
|
||||
}
|
||||
if (typeof options.amdModuleId === "string" && options.amdModuleId) {
|
||||
str.push("amdModuleId=" + encodeURIComponent(options.amdModuleId));
|
||||
}
|
||||
if (typeof options.cjsModuleId === "string" && options.cjsModuleId) {
|
||||
str.push("cjsModuleId=" + encodeURIComponent(options.cjsModuleId));
|
||||
}
|
||||
return str.join("&");
|
||||
};
|
||||
var _inArray = function(elem, array) {
|
||||
if (array.indexOf) {
|
||||
return array.indexOf(elem);
|
||||
}
|
||||
for (var i = 0, length = array.length; i < length; i++) {
|
||||
if (array[i] === elem) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
var _prepGlue = function(elements) {
|
||||
if (typeof elements === "string") throw new TypeError("ZeroClipboard doesn't accept query strings.");
|
||||
if (!elements.length) return [ elements ];
|
||||
return elements;
|
||||
};
|
||||
var _dispatchCallback = function(func, element, instance, args, async) {
|
||||
if (async) {
|
||||
window.setTimeout(function() {
|
||||
func.call(element, instance, args);
|
||||
}, 0);
|
||||
} else {
|
||||
func.call(element, instance, args);
|
||||
}
|
||||
};
|
||||
var currentElement, gluedElements = [], flashState = {};
|
||||
var ZeroClipboard = function(elements, options) {
|
||||
if (elements) (ZeroClipboard.prototype._singleton || this).glue(elements);
|
||||
if (ZeroClipboard.prototype._singleton) return ZeroClipboard.prototype._singleton;
|
||||
ZeroClipboard.prototype._singleton = this;
|
||||
this.options = {};
|
||||
for (var kd in _defaults) this.options[kd] = _defaults[kd];
|
||||
for (var ko in options) this.options[ko] = options[ko];
|
||||
this.handlers = {};
|
||||
if (!flashState.hasOwnProperty(this.options.moviePath)) {
|
||||
flashState[this.options.moviePath] = {
|
||||
noflash: !ZeroClipboard.detectFlashSupport(),
|
||||
wrongflash: false,
|
||||
ready: false,
|
||||
version: "0.0.0"
|
||||
};
|
||||
}
|
||||
if (flashState[this.options.moviePath].noflash === false) {
|
||||
_bridge();
|
||||
}
|
||||
};
|
||||
ZeroClipboard.prototype.setCurrent = function(element) {
|
||||
currentElement = element;
|
||||
this.reposition();
|
||||
var titleAttr = element.getAttribute("title");
|
||||
if (titleAttr) {
|
||||
this.setTitle(titleAttr);
|
||||
}
|
||||
var useHandCursor = this.options.forceHandCursor === true || _getStyle(element, "cursor") === "pointer";
|
||||
_setHandCursor.call(this, useHandCursor);
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.prototype.setText = function(newText) {
|
||||
if (newText && newText !== "") {
|
||||
this.options.text = newText;
|
||||
if (this.ready()) this.flashBridge.setText(newText);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.prototype.setTitle = function(newTitle) {
|
||||
if (newTitle && newTitle !== "") this.htmlBridge.setAttribute("title", newTitle);
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.prototype.setSize = function(width, height) {
|
||||
if (this.ready()) this.flashBridge.setSize(width, height);
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.prototype.setHandCursor = function(enabled) {
|
||||
enabled = typeof enabled === "boolean" ? enabled : !!enabled;
|
||||
_setHandCursor.call(this, enabled);
|
||||
this.options.forceHandCursor = enabled;
|
||||
return this;
|
||||
};
|
||||
var _setHandCursor = function(enabled) {
|
||||
if (this.ready()) this.flashBridge.setHandCursor(enabled);
|
||||
};
|
||||
ZeroClipboard.version = "1.2.3";
|
||||
var _defaults = {
|
||||
moviePath: "ZeroClipboard.swf",
|
||||
trustedOrigins: null,
|
||||
text: null,
|
||||
hoverClass: "zeroclipboard-is-hover",
|
||||
activeClass: "zeroclipboard-is-active",
|
||||
allowScriptAccess: "sameDomain",
|
||||
useNoCache: true,
|
||||
forceHandCursor: false
|
||||
};
|
||||
ZeroClipboard.setDefaults = function(options) {
|
||||
for (var ko in options) _defaults[ko] = options[ko];
|
||||
};
|
||||
ZeroClipboard.destroy = function() {
|
||||
if (ZeroClipboard.prototype._singleton) {
|
||||
ZeroClipboard.prototype._singleton.unglue(gluedElements);
|
||||
var bridge = ZeroClipboard.prototype._singleton.htmlBridge;
|
||||
if (bridge && bridge.parentNode) {
|
||||
bridge.parentNode.removeChild(bridge);
|
||||
}
|
||||
delete ZeroClipboard.prototype._singleton;
|
||||
}
|
||||
};
|
||||
ZeroClipboard.detectFlashSupport = function() {
|
||||
var hasFlash = false;
|
||||
if (typeof ActiveXObject === "function") {
|
||||
try {
|
||||
if (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")) {
|
||||
hasFlash = true;
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
if (!hasFlash && navigator.mimeTypes["application/x-shockwave-flash"]) {
|
||||
hasFlash = true;
|
||||
}
|
||||
return hasFlash;
|
||||
};
|
||||
var _amdModuleId = null;
|
||||
var _cjsModuleId = null;
|
||||
var _bridge = function() {
|
||||
var flashBridge, len;
|
||||
var client = ZeroClipboard.prototype._singleton;
|
||||
var container = document.getElementById("global-zeroclipboard-html-bridge");
|
||||
if (!container) {
|
||||
var opts = {};
|
||||
for (var ko in client.options) opts[ko] = client.options[ko];
|
||||
opts.amdModuleId = _amdModuleId;
|
||||
opts.cjsModuleId = _cjsModuleId;
|
||||
var flashvars = _vars(opts);
|
||||
var html = ' <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="global-zeroclipboard-flash-bridge" width="100%" height="100%"> <param name="movie" value="' + client.options.moviePath + _noCache(client.options.moviePath, client.options) + '"/> <param name="allowScriptAccess" value="' + client.options.allowScriptAccess + '"/> <param name="scale" value="exactfit"/> <param name="loop" value="false"/> <param name="menu" value="false"/> <param name="quality" value="best" /> <param name="bgcolor" value="#ffffff"/> <param name="wmode" value="transparent"/> <param name="flashvars" value="' + flashvars + '"/> <embed src="' + client.options.moviePath + _noCache(client.options.moviePath, client.options) + '" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="100%" height="100%" name="global-zeroclipboard-flash-bridge" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" wmode="transparent" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="' + flashvars + '" scale="exactfit"> </embed> </object>';
|
||||
container = document.createElement("div");
|
||||
container.id = "global-zeroclipboard-html-bridge";
|
||||
container.setAttribute("class", "global-zeroclipboard-container");
|
||||
container.style.position = "absolute";
|
||||
container.style.left = "0px";
|
||||
container.style.top = "-9999px";
|
||||
container.style.width = "15px";
|
||||
container.style.height = "15px";
|
||||
container.style.zIndex = "9999";
|
||||
document.body.appendChild(container);
|
||||
container.innerHTML = html;
|
||||
}
|
||||
client.htmlBridge = container;
|
||||
flashBridge = document["global-zeroclipboard-flash-bridge"];
|
||||
if (flashBridge && (len = flashBridge.length)) {
|
||||
flashBridge = flashBridge[len - 1];
|
||||
}
|
||||
client.flashBridge = flashBridge || container.children[0].lastElementChild;
|
||||
};
|
||||
ZeroClipboard.prototype.resetBridge = function() {
|
||||
if (this.htmlBridge) {
|
||||
this.htmlBridge.style.left = "0px";
|
||||
this.htmlBridge.style.top = "-9999px";
|
||||
this.htmlBridge.removeAttribute("title");
|
||||
}
|
||||
if (currentElement) {
|
||||
_removeClass(currentElement, this.options.activeClass);
|
||||
currentElement = null;
|
||||
}
|
||||
this.options.text = null;
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.prototype.ready = function() {
|
||||
return flashState[this.options.moviePath].ready === true;
|
||||
};
|
||||
ZeroClipboard.prototype.reposition = function() {
|
||||
if (!currentElement) return false;
|
||||
var pos = _getDOMObjectPosition(currentElement);
|
||||
this.htmlBridge.style.top = pos.top + "px";
|
||||
this.htmlBridge.style.left = pos.left + "px";
|
||||
this.htmlBridge.style.width = pos.width + "px";
|
||||
this.htmlBridge.style.height = pos.height + "px";
|
||||
this.htmlBridge.style.zIndex = pos.zIndex + 1;
|
||||
this.setSize(pos.width, pos.height);
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.dispatch = function(eventName, args) {
|
||||
ZeroClipboard.prototype._singleton.receiveEvent(eventName, args);
|
||||
};
|
||||
ZeroClipboard.prototype.on = function(eventName, func) {
|
||||
var events = eventName.toString().split(/\s/g), added = {};
|
||||
for (var i = 0, len = events.length; i < len; i++) {
|
||||
eventName = events[i].toLowerCase().replace(/^on/, "");
|
||||
added[eventName] = true;
|
||||
if (!this.handlers[eventName]) {
|
||||
this.handlers[eventName] = func;
|
||||
}
|
||||
}
|
||||
if (added.noflash && flashState[this.options.moviePath].noflash) {
|
||||
this.receiveEvent("onNoFlash", {});
|
||||
}
|
||||
if (added.wrongflash && flashState[this.options.moviePath].wrongflash) {
|
||||
this.receiveEvent("onWrongFlash", {
|
||||
flashVersion: flashState[this.options.moviePath].version
|
||||
});
|
||||
}
|
||||
if (added.load && flashState[this.options.moviePath].ready) {
|
||||
this.receiveEvent("onLoad", {
|
||||
flashVersion: flashState[this.options.moviePath].version
|
||||
});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.prototype.addEventListener = ZeroClipboard.prototype.on;
|
||||
ZeroClipboard.prototype.off = function(eventName, func) {
|
||||
var events = eventName.toString().split(/\s/g);
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
eventName = events[i].toLowerCase().replace(/^on/, "");
|
||||
for (var event in this.handlers) {
|
||||
if (event === eventName && this.handlers[event] === func) {
|
||||
delete this.handlers[event];
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.prototype.removeEventListener = ZeroClipboard.prototype.off;
|
||||
ZeroClipboard.prototype.receiveEvent = function(eventName, args) {
|
||||
eventName = eventName.toString().toLowerCase().replace(/^on/, "");
|
||||
var element = currentElement;
|
||||
var performCallbackAsync = true;
|
||||
switch (eventName) {
|
||||
case "load":
|
||||
if (args && args.flashVersion) {
|
||||
if (!_isFlashVersionSupported(args.flashVersion)) {
|
||||
this.receiveEvent("onWrongFlash", {
|
||||
flashVersion: args.flashVersion
|
||||
});
|
||||
return;
|
||||
}
|
||||
flashState[this.options.moviePath].ready = true;
|
||||
flashState[this.options.moviePath].version = args.flashVersion;
|
||||
}
|
||||
break;
|
||||
|
||||
case "wrongflash":
|
||||
if (args && args.flashVersion && !_isFlashVersionSupported(args.flashVersion)) {
|
||||
flashState[this.options.moviePath].wrongflash = true;
|
||||
flashState[this.options.moviePath].version = args.flashVersion;
|
||||
}
|
||||
break;
|
||||
|
||||
case "mouseover":
|
||||
_addClass(element, this.options.hoverClass);
|
||||
break;
|
||||
|
||||
case "mouseout":
|
||||
_removeClass(element, this.options.hoverClass);
|
||||
this.resetBridge();
|
||||
break;
|
||||
|
||||
case "mousedown":
|
||||
_addClass(element, this.options.activeClass);
|
||||
break;
|
||||
|
||||
case "mouseup":
|
||||
_removeClass(element, this.options.activeClass);
|
||||
break;
|
||||
|
||||
case "datarequested":
|
||||
var targetId = element.getAttribute("data-clipboard-target"), targetEl = !targetId ? null : document.getElementById(targetId);
|
||||
if (targetEl) {
|
||||
var textContent = targetEl.value || targetEl.textContent || targetEl.innerText;
|
||||
if (textContent) {
|
||||
this.setText(textContent);
|
||||
}
|
||||
} else {
|
||||
var defaultText = element.getAttribute("data-clipboard-text");
|
||||
if (defaultText) {
|
||||
this.setText(defaultText);
|
||||
}
|
||||
}
|
||||
performCallbackAsync = false;
|
||||
break;
|
||||
|
||||
case "complete":
|
||||
this.options.text = null;
|
||||
break;
|
||||
}
|
||||
if (this.handlers[eventName]) {
|
||||
var func = this.handlers[eventName];
|
||||
if (typeof func === "string" && typeof window[func] === "function") {
|
||||
func = window[func];
|
||||
}
|
||||
if (typeof func === "function") {
|
||||
_dispatchCallback(func, element, this, args, performCallbackAsync);
|
||||
}
|
||||
}
|
||||
};
|
||||
ZeroClipboard.prototype.glue = function(elements) {
|
||||
elements = _prepGlue(elements);
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (elements[i] && elements[i].nodeType === 1) {
|
||||
if (_inArray(elements[i], gluedElements) == -1) {
|
||||
gluedElements.push(elements[i]);
|
||||
_addEventHandler(elements[i], "mouseover", _elementMouseOver);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
ZeroClipboard.prototype.unglue = function(elements) {
|
||||
elements = _prepGlue(elements);
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
_removeEventHandler(elements[i], "mouseover", _elementMouseOver);
|
||||
var arrayIndex = _inArray(elements[i], gluedElements);
|
||||
if (arrayIndex != -1) gluedElements.splice(arrayIndex, 1);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
function _isFlashVersionSupported(flashVersion) {
|
||||
return parseFloat(flashVersion.replace(/,/g, ".").replace(/[^0-9\.]/g, "")) >= 10;
|
||||
}
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define([ "require", "exports", "module" ], function(require, exports, module) {
|
||||
_amdModuleId = module && module.id || null;
|
||||
return ZeroClipboard;
|
||||
});
|
||||
} else if (typeof module === "object" && module && typeof module.exports === "object" && module.exports) {
|
||||
_cjsModuleId = module.id || null;
|
||||
module.exports = ZeroClipboard;
|
||||
} else {
|
||||
window.ZeroClipboard = ZeroClipboard;
|
||||
}
|
||||
})();
|
||||
Loading…
Reference in New Issue