Related
Formatted the form_input_field, but the number displays and stores in the db differently. Please let me know if you need further details.
<div class="my-5" data-controller="phoneinput">
<%= form.label :Phone %>
<%= form.text_field :phone, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full", data: { 'phoneinput-target': 'input'}, :maxlength=>"12", placeholder: "555-111-5555" %>
</div>
to perform 555555 to 555-111-5555 using a stimulus controller
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
static targets = [ "input" ]
connect() {
const input = this.inputTarget;
// console.log("hellow");
input.addEventListener("keydown", (e) => {
if (e.key === "Backspace" || e.key === "Delete") return
if (e.target.value.length === 3) {
input.value = input.value + "-"
}
if (e.target.value.length === 7) {
input.value = input.value + "-"
}
if(e.target.value.length === 14) {
input.value = input.value + "-";
}
}
)
}
}
I have ruby on rails application with stimulus.js and dropzone.js for uploading attachment. There is now a limit on uploading one file, but this allows you to upload more than one file and just shows an error message on them. I need that it is not possible to upload more than one file and if after that the user tries to upload another one, a replacement occurs.
dropzone_controller.js
import Dropzone from "dropzone";
import { Controller } from "stimulus";
import { DirectUpload } from "#rails/activestorage";
import {
getMetaValue,
toArray,
findElement,
removeElement,
insertAfter
} from "helpers";
export default class extends Controller {
static targets = ["input"];
connect() {
this.dropZone = createDropZone(this);
this.hideFileInput();
this.bindEvents();
Dropzone.autoDiscover = false; // necessary quirk for Dropzone error in console
}
// Private
hideFileInput() {
this.inputTarget.disabled = true;
this.inputTarget.style.display = "none";
}
bindEvents() {
this.dropZone.on("addedfile", file => {
setTimeout(() => {
file.accepted && createDirectUploadController(this, file).start();
}, 500);
});
this.dropZone.on("removedfile", file => {
file.controller && removeElement(file.controller.hiddenInput);
});
this.dropZone.on("canceled", file => {
file.controller && file.controller.xhr.abort();
});
}
get headers() {
return { "X-CSRF-Token": getMetaValue("csrf-token") };
}
get url() {
return this.inputTarget.getAttribute("data-direct-upload-url");
}
get maxFiles() {
return this.data.get("maxFiles") || 1;
}
get maxFileSize() {
return this.data.get("maxFileSize") || 256;
}
get acceptedFiles() {
return this.data.get("acceptedFiles");
}
get addRemoveLinks() {
return this.data.get("addRemoveLinks") || true;
}
}
class DirectUploadController {
constructor(source, file) {
this.directUpload = createDirectUpload(file, source.url, this);
this.source = source;
this.file = file;
}
start() {
this.file.controller = this;
this.hiddenInput = this.createHiddenInput();
this.directUpload.create((error, attributes) => {
if (error) {
removeElement(this.hiddenInput);
this.emitDropzoneError(error);
} else {
this.hiddenInput.value = attributes.signed_id;
this.emitDropzoneSuccess();
}
});
}
createHiddenInput() {
const input = document.createElement("input");
input.type = "hidden";
input.name = this.source.inputTarget.name;
insertAfter(input, this.source.inputTarget);
return input;
}
directUploadWillStoreFileWithXHR(xhr) {
this.bindProgressEvent(xhr);
this.emitDropzoneUploading();
}
bindProgressEvent(xhr) {
this.xhr = xhr;
this.xhr.upload.addEventListener("progress", event =>
this.uploadRequestDidProgress(event)
);
}
uploadRequestDidProgress(event) {
const element = this.source.element;
const progress = (event.loaded / event.total) * 100;
findElement(
this.file.previewTemplate,
".dz-upload"
).style.width = `${progress}%`;
}
emitDropzoneUploading() {
this.file.status = Dropzone.UPLOADING;
this.source.dropZone.emit("processing", this.file);
}
emitDropzoneError(error) {
this.file.status = Dropzone.ERROR;
this.source.dropZone.emit("error", this.file, error);
this.source.dropZone.emit("complete", this.file);
}
emitDropzoneSuccess() {
this.file.status = Dropzone.SUCCESS;
this.source.dropZone.emit("success", this.file);
this.source.dropZone.emit("complete", this.file);
}
}
function createDirectUploadController(source, file) {
return new DirectUploadController(source, file);
}
function createDirectUpload(file, url, controller) {
return new DirectUpload(file, url, controller);
}
function createDropZone(controller) {
return new Dropzone(controller.element, {
url: controller.url,
headers: controller.headers,
maxFiles: controller.maxFiles,
maxFilesize: controller.maxFileSize,
acceptedFiles: controller.acceptedFiles,
addRemoveLinks: controller.addRemoveLinks,
autoQueue: false
});
}
_form.html.erb
<div data-lite-visibility-target="dynamic" class="space-y-8 <%= #automate_task_report.attachment.present? ? '' : "hidden" %>" >
<div class="form-group inverted">
<%= form.label :attachment, "Upload test execution results", class: "form-label" %>
<button type="button" class="dropzone dropzone-default dz-clickable form-control form-file form-file-btn" data-controller="dropzone" data-dropzone-max-file-size="10" data-dropzone-max-files="1" data-dropzone-accepted-files=".xml,.html,.jpg,.jpeg,.png,.gif">
<%= form.file_field :attachment, direct_upload: true, data: { target: 'dropzone.input' } %>
<div class="dropzone-msg dz-message needsclick flex m-0">
<% if #automate_task_report.attachment.attached? %>
<%= form.hidden_field :attachment, value: #automate_task_report.attachment.signed_id %>
<div class="mx-5 attachment vertical">
<%= link_to #automate_task_report.attachment, target: "_blank", class:"attachment-thumb" do %>
<%= image_tag(#automate_task_report.attachment) %>
<% end %>
<%= link_to #automate_task_report.attachment.filename.to_s, #automate_task_report.attachment, target: "_blank", class:"attachment-name" %>
<%= link_to #automate_task_report.attachment, download: #automate_task_report.attachment, class:"btn btn-primary attachment-btn" do %>
<span class="icon text-icon-default icon-download"></span>
<% end %>
</div>
<% end %>
<span class="icon text-icon-lg icon-file-image-plus-lg mr-3"></span>
<div class="text-left mt-0">
<p>Upload a file or drag and drop</p>
<p class="text-xs">XML, HTML, PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</button>
</div>
</div>
</div>
</div>
I'm already tried things like
= {
maxFiles: 1
};
but it didn't work for me. How can I achive expected result?
Use the "accept" callback to check for the max amount of files:
accept: function(file, done) {
if (/* do a check here */) {
done(error message);
// File is cancel.
file.status = Dropzone.CANCELED;
}
}
key point here is change the file status to cancelled to let dropzone know about it
I'm following the Andy Leverenz's (WebCrunch) Dropzone/Stimulus tutorial and don't know how to implement the update action. I can upload multiple files with ease but when I try to edit it, all my attachments disappear and I have to re-upload. I think that what I need to do is fetch my object attachments on stimulus controller (#rails/ujs) and check if browser URL match with edit_path, then append files on the form when it loads (document.ready).
dropzone_controller.js
import Dropzone from "dropzone";
import { Controller } from "stimulus";
import { DirectUpload } from "#rails/activestorage";
import {
getMetaValue,
toArray,
findElement,
removeElement,
insertAfter
} from "helpers";
export default class extends Controller {
static targets = ["input"];
connect() {
this.dropZone = createDropZone(this);
this.hideFileInput();
this.bindEvents();
Dropzone.autoDiscover = false;
}
// Private
hideFileInput() {
this.inputTarget.disabled = true;
this.inputTarget.style.display = "none";
}
bindEvents() {
this.dropZone.on("addedfile", file => {
setTimeout(() => {
file.accepted && createDirectUploadController(this, file).start();
}, 500);
});
this.dropZone.on("removedfile", file => {
file.controller && removeElement(file.controller.hiddenInput);
});
this.dropZone.on("canceled", file => {
file.controller && file.controller.xhr.abort();
});
}
get headers() {
return { "X-CSRF-Token": getMetaValue("csrf-token") };
}
get url() {
return this.inputTarget.getAttribute("data-direct-upload-url");
}
get maxFiles() {
return this.data.get("maxFiles") || 1;
}
get maxFileSize() {
return this.data.get("maxFileSize") || 256;
}
get acceptedFiles() {
return this.data.get("acceptedFiles");
}
get addRemoveLinks() {
return this.data.get("addRemoveLinks") || true;
}
}
class DirectUploadController {
constructor(source, file) {
this.directUpload = createDirectUpload(file, source.url, this);
this.source = source;
this.file = file;
}
start() {
this.file.controller = this;
this.hiddenInput = this.createHiddenInput();
this.directUpload.create((error, attributes) => {
if (error) {
removeElement(this.hiddenInput);
this.emitDropzoneError(error);
} else {
this.hiddenInput.value = attributes.signed_id;
this.emitDropzoneSuccess();
}
});
}
createHiddenInput() {
const input = document.createElement("input");
input.type = "hidden";
input.name = this.source.inputTarget.name;
insertAfter(input, this.source.inputTarget);
return input;
}
directUploadWillStoreFileWithXHR(xhr) {
this.bindProgressEvent(xhr);
this.emitDropzoneUploading();
}
bindProgressEvent(xhr) {
this.xhr = xhr;
this.xhr.upload.addEventListener("progress", event =>
this.uploadRequestDidProgress(event)
);
}
uploadRequestDidProgress(event) {
const element = this.source.element;
const progress = (event.loaded / event.total) * 100;
findElement(
this.file.previewTemplate,
".dz-upload"
).style.width = `${progress}%`;
}
emitDropzoneUploading() {
this.file.status = Dropzone.UPLOADING;
this.source.dropZone.emit("processing", this.file);
}
emitDropzoneError(error) {
this.file.status = Dropzone.ERROR;
this.source.dropZone.emit("error", this.file, error);
this.source.dropZone.emit("complete", this.file);
}
emitDropzoneSuccess() {
this.file.status = Dropzone.SUCCESS;
this.source.dropZone.emit("success", this.file);
this.source.dropZone.emit("complete", this.file);
}
}
function createDirectUploadController(source, file) {
return new DirectUploadController(source, file);
}
function createDirectUpload(file, url, controller) {
return new DirectUpload(file, url, controller);
}
function createDropZone(controller) {
return new Dropzone(controller.element, {
url: controller.url,
headers: controller.headers,
maxFiles: controller.maxFiles,
maxFilesize: controller.maxFileSize,
acceptedFiles: controller.acceptedFiles,
addRemoveLinks: controller.addRemoveLinks,
autoQueue: false
});
}
index.js
// Load all the controllers within this directory and all subdirectories.
// Controller files must be named *_controller.js.
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";
const application = Application.start();
const context = require.context("controllers", true, /_controller\.js$/);
application.load(definitionsFromContext(context));
_form.html.erb
<%= form_for #ad, :html => { :multipart => true } do |form| %>
<% if ad.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(ad.errors.count, "error") %> prohibited this ad from being saved:</h2>
<ul>
<% ad.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= form.label :title %>
<%= form.text_field :title, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :description %>
<%= form.text_area :description, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :images %>
<div class='dropzone dropzone-default dz-clickable' data-controller='dropzone' data-max-filesize='2'
data-dropzone-max-files='4'>
<%= form.file_field :images, multiple: true, direct_upload: true, data: { target: 'dropzone.input' } %>
<div class='dropzone-msg dz-message needsclick'>
<h3 class='dropzone-msg-title'>Drag here to upload or click here to browse</h3>
<span class='dropzone-msg-desc'>2 MB file size maximum. Allow file types png, jpg.</span>
</div>
</div>
</div>
<div class="form-group">
<%= form.submit class: 'btn btn-primary mb-2' %>
</div>
<% end %>
I'm integrating Stripe SCA with payment intents into my rails 5.2.3 (ruby 2.5.1) app. I successfully had one-time payments working properly, however after I integrated subscriptions (successfully working), the one-time payments are receiving an incomplete status on Stripe "The customer has not entered their payment method". Looking at the JSON I can see my payment intent with ID successfully being created, however my charges, data is showing up as null. I can't figure out why the data is not being passed to stripe. Here are the corresponding files:
purchases.show.html.erb
<div class="container">
<h1>Purchasing <%= #recipe.title %> for <%= number_to_currency(#recipe.price) %></h1>
<%= form_with url: recipe_purchase_path(#recipe.id), local: true, id: "payment-form", data: { payment_intent_id: #payment_intent.client_secret } do |form| %>
<div class="form-group">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element" class="form-control">
</div>
<div id="card-errors" role="alert">
</div>
</div>
<div class="form-group">
<label>Name on Card</label>
<%= form.text_field :name_on_card, placeholder: "Full name", class: "form-control" %>
</div>
<div class="form-group">
<%= form.hidden_field :payment_intent_id, value: #payment_intent.id %>
<button class="btn btn-outline-primary buy-recipe">Submit Payment</button>
</div>
<% end %>
</div>
purchases_controller.rb
class PurchasesController < ApplicationController
before_action :authenticate_user!
before_action :set_recipe, only:[:show, :create]
def receipt
#purchase = Purchase.find_by_uuid(params[:id])
#recipe = Recipe.find(#purchase.recipe_id)
end
def show
#payment_intent = Stripe::PaymentIntent.create(
amount: #recipe.price_in_cents,
currency: 'usd',
payment_method_types: params['card'],
metadata: {integration_check: 'accept_a_payment'},
)
end
def create
#payment_intent = Stripe::PaymentIntent.retrieve(params[:payment_intent_id])
if #payment_intent.status == "succeeded"
charge = #payment_intent.charges.data.first
card = charge.payment_method_details.card
purchase = Purchase.create(
customer_id: charge.id,
user_id: current_user.id,
recipe_id: #recipe.id,
uuid: SecureRandom.uuid,
amount: #recipe.price
)
current_user.favorites << #recipe
redirect_to recipe_path(#recipe.slug), notice: "#{#recipe.title} has been added to your Cookbook, thanks for purchasing!"
else
flash[:alert] = "Your order was unsuccessful. Please try again."
redirect_to recipe_purchase_path(#recipe.id)
end
end
private
def set_recipe
#recipe = Recipe.find(params[:recipe_id])
end
end
purchases.index.js
document.addEventListener("turbolinks:load", () => {
const form = document.querySelector("#payment-form")
if (form == null) { return }
const public_key = document.querySelector("meta[name='stripe-key']").getAttribute("content")
const stripe = Stripe(public_key)
const elements = stripe.elements()
const card = elements.create('card')
card.mount('#card-element')
card.addEventListener("change", (event) => {
var displayError = document.getElementById('card-errors')
if (event.error) {
displayError.textContent = event.error.message
} else {
displayError.textContent = ''
}
})
form.addEventListener("submit", (event) => {
event.preventDefault()
let data = {
payment_method: {
card: card,
billing_details: {
name: form.querySelector("#name_on_card").value
}
}
}
stripe.confirmCardPayment(form.dataset.paymentIntentId, data).then((result) => {
if (result.error) {
var errorElement = document.getElementById('card-errors')
errorElement.textContent = result.error.message
} else {
//
//
form.submit()
}
})
})
})
and a screenshot of JSON
here is my subscriptions.js file
document.addEventListener("turbolinks:load", () => {
let cardElement = document.querySelector("#card-element")
if (cardElement !== null) { setupStripe() }
})
function setupStripe() {
const stripe_key = document.querySelector("meta[name='stripe-key']").getAttribute("content")
const stripe = Stripe(stripe_key)
const elements = stripe.elements()
const card = elements.create('card')
card.mount('#card-element')
var displayError = document.getElementById('card-errors')
card.addEventListener('change', (event) => {
if (event.error) {
displayError.textContent = event.error.message
} else {
displayError.textContent = ''
}
})
const form = document.querySelector("#payment-form")
let paymentIntentId = form.dataset.paymentIntent
let setupIntentId = form.dataset.setupIntent
if (paymentIntentId) {
if (form.dataset.status == "requires_action") {
stripe.confirmCardPayment(paymentIntentId, { setup_future_usage: 'off_session' }).then((result) => {
if (result.error) {
displayError.textContent = result.error.message
form.querySelector("#card-details").classList.remove("d-none")
} else {
form.submit()
}
})
}
}
form.addEventListener('submit', (event) => {
event.preventDefault()
let name = form.querySelector("#name_on_card").value
let data = {
payment_method_data: {
card: card,
billing_details: {
name: name,
}
}
}
// Complete a payment intent
if (paymentIntentId) {
stripe.confirmCardPayment(paymentIntentId, {
payment_method: data.payment_method_data,
setup_future_usage: 'off_session',
save_payment_method: true,
}).then((result) => {
if (result.error) {
displayError.textContent = result.error.message
form.querySelector("#card-details").classList.remove("d-none")
} else {
form.submit()
}
})
// Updating a card or subscribing with a trial (using a SetupIntent)
} else if (setupIntentId) {
stripe.confirmCardSetup(setupIntentId, {
payment_method: data.payment_method_data
}).then((result) => {
if (result.error) {
displayError.textContent = result.error.message
} else {
addHiddenField(form, "payment_method_id", result.setupIntent.payment_method)
form.submit()
}
})
} else {
//subscribing w no trial
data.payment_method_data.type = 'card'
stripe.createPaymentMethod(data.payment_method_data).then((result) => {
if (result.error) {
displayError.textContent = result.error.message
} else {
addHiddenField(form, "payment_method_id", result.paymentMethod.id)
form.submit()
}
})
}
})
}
function addHiddenField(form, name, value) {
let input = document.createElement("input")
input.setAttribute("type", "hidden")
input.setAttribute("name", name)
input.setAttribute("value", value)
form.appendChild(input)
}
So special thanks to Chris Oliver on this...but what needed to be done was on the show.html.erb I had to change the payment_intent_id to :payment_intent in the form data.
<%= form_with url: recipe_purchase_path(#recipe.id), local: true, id: "payment-form", data: { payment_intent: #payment_intent.client_secret } do |form| %>
then in my show action in purchases_controller.rb I needed to add customer
def show
#payment_intent = Stripe::PaymentIntent.create(
amount: #recipe.price_in_cents,
currency: 'usd',
payment_method_types: ['card'],
customer: current_user.stripe_id || current_user.stripe_customer.id
)
end
Then I completely removed my purchases.js since the one-time payments are being handled in the subscription.js
I received an email from a customer saying that they never received their product after paying on Bitcoin/Stripe. the trouble is, I couldn't find a way to thoroughly test Bitcoin in a sandbox, so are not sure if my implementation is working as it should be. The sandbox automatically fills the receiver so I get the notification of payment, and all is ok. however when running live, im not sure if my polling is working correctly. I posted all my relevant code below, can anyone see any flaws in the code?
*my Store is running Ruby(2.3.1p112) on Rails (4.2.6)
my payment_bitcoin.html.erb
<%= render :partial => "offsite_checkout_summary" %>
<p>Your license key, along with your purchase receipt, will be sent to <strong><%= #order.licensee_name %></strong> at <strong><%= #order.email %></strong> once payment has been confirmed.</p>
<div id="extra_purchase_notes">
<em><strong>Note:</strong> The bitcoin link and price provided is only valid for <span id="countdown_time">10:00</span> minutes.</em>
</div>
<div class="d cl"></div>
<%= render :partial => "items_checkout_summary", :locals => { order: #order } %>
<div id="bitcoin_payment_address">
<script src="https://js.stripe.com/v2/stripe.js"></script>
<script type="text/javascript">
Stripe.setPublishableKey('<%= $STRIPE_PREFS['stripe_publishable_key'] %>');
function populateBitcoinCheckout(status, response) {
if (status === 200) {
document.getElementById("bitcoin_total").innerHTML = " (" + response.bitcoin_amount/100000000 + " BTC)";
document.getElementById("bitcoin_payment_string").innerHTML = 'Please send: <strong>' + response.bitcoin_amount/100000000 + ' BTC</strong> to <strong>' + '' + response.inbound_address + '</strong>';
document.getElementById("btc-button").href = response.bitcoin_uri + '&label=Software+Purchase';
//poll reciever
Stripe.bitcoinReceiver.pollReceiver(response.id, filledReceiverHandler(response));
//configure timer
function startTimer(duration, countdown) {
var timer = duration,minutes,seconds;
var t = setInterval(function () {
minutes = parseInt(timer / 60, 10)
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
countdown.textContent = minutes + ":" + seconds;
if (--timer < 0) {
clearInterval(t);
document.getElementById("bitcoin_total").innerHTML = "";
document.getElementById("bitcoin_payment_string").innerHTML = "";
document.getElementById("bitcoin_button_text").innerHTML = "Refresh Order"
document.getElementById("btc-button").href = "javascript:history.back()"
document.getElementById("extra_purchase_notes").innerHTML = "<em><strong>Oops...</strong> This order has expired, use the Refresh Order button to retry.</em>"
Stripe.bitcoinReceiver.cancelReceiverPoll(response.id);
}
}, 1000);
}
//start timer
var countdown = document.getElementById('countdown_time');
startTimer(600, countdown);
} else {
document.getElementById("bitcoin_uri_string").innerHTML = JSON.stringify(response);
Stripe.bitcoinReceiver.cancelReceiverPoll(response.id);
}
}
Stripe.bitcoinReceiver.createReceiver({
amount: "<%= (#order.total * 100).round %>",
currency: 'usd',
description: 'Software purchase',
email: "<%= #order.email %>"
}, populateBitcoinCheckout);
function filledReceiverHandler(response)
{
if (response.filled === true) {
function post(path, parameters) {
var form = $('<form></form>');
form.attr("method", "post");
form.attr("action", path);
$.each(parameters, function(key, value) {
if ( typeof value == 'object' || typeof value == 'array' ){
$.each(value, function(subkey, subvalue) {
var field = $('<input />');
field.attr("type", "hidden");
field.attr("name", key+'[]');
field.attr("value", subvalue);
form.append(field);
});
} else {
var field = $('<input />');
field.attr("type", "hidden");
field.attr("name", key);
field.attr("value", value);
form.append(field);
}
});
$(document.body).append(form);
form.submit();
}
post('purchase_bitcoin', response);
}
}
</script>
</div>
</div>
<p id="bitcoin_payment_string"></p>
<div class="d"></div>
<p style="text-align: right;">
<a id="btc-button"><button class="bitcoin-button" style="visibility: visible;"><span id="bitcoin_button_text">Pay with Bitcoin</span></button></a>
</p>
and the relevant controller method:
#bitcoin purchase
def purchase_bitcoin
require 'stripe'
if check_if_order_exists() == false
if session[:failure_reason] != nil
render :action => 'failed'
return
end
redirect_to :action => 'index'
return
end
if session[:item_number] == nil
flash[:notice] = 'Nothing to purchase'
redirect_to :action => 'index'
return
end
#create the order
generateNewOrder("Bitcoin")
#total in cents
the_total = #order.total.to_f
the_total_cents = (the_total*100).to_i
Stripe.api_key = $STRIPE_PREFS['stripe_secret']
#add order details
#order.transaction_number = params[:id]
# Create the charge on Stripe's servers - this will charge the user's card
session[:coin_total] = (params[:bitcoin_amount].to_f/100000000).to_s
charge = Stripe::Charge.create(
:amount => params[:amount],
:currency => params[:currency],
:source => params[:id],
:description => 'Software purchase'
)
#re-add completed order details
the_id = charge["id"]
#order.transaction_number = the_id
#order.comment = 'Total = ' + (params[:bitcoin_amount].to_f/100000000).to_s + 'BTC; payment address = ' + params[:inbound_address]
#order.status = 'C'
#order.finish_and_save()
session[:order_id] = #order.id
#send license
Thread.new do
OrderMailer.thankyou(#order).deliver
end
#logger.info session.inspect
render :action => 'thankyou_bitcoin'
end
I ended up adding the polling logic into the timer, and this seems to work.... although it was implied in the docs that i didn't need to manually poll? at least thats how i read it...
here is my complete amended payment_bitcoin.html.erb for anyone else experiencing issues getting this working
<%= render :partial => "offsite_checkout_summary" %>
<p>Your license key, along with your purchase receipt, will be sent to <strong><%= #order.licensee_name %></strong> at <strong><%= #order.email %></strong> once payment has been confirmed.</p>
<div id="extra_purchase_notes">
<em><strong>Note:</strong> The bitcoin link and price provided is only valid for <span id="countdown_time">10:00</span> minutes.</em>
</div>
<div class="d cl"></div>
<%= render :partial => "items_checkout_summary", :locals => { order: #order } %>
<div id="bitcoin_payment_address">
<script src="https://js.stripe.com/v2/stripe.js"></script>
<script type="text/javascript">
Stripe.setPublishableKey('<%= $STRIPE_PREFS['stripe_publishable_key'] %>');
function populateBitcoinCheckout(status, response) {
if (status === 200) {
document.getElementById("bitcoin_total").innerHTML = " (" + response.bitcoin_amount/100000000 + " BTC)";
document.getElementById("bitcoin_payment_string").innerHTML = 'Please send: <strong>' + response.bitcoin_amount/100000000 + ' BTC</strong> to <strong>' + '' + response.inbound_address + '</strong>';
document.getElementById("btc-button").href = response.bitcoin_uri + '&label=Software+Purchase';
//configure timer
function startTimer(duration, countdown) {
var timer = duration,minutes,seconds;
var t = setInterval(function () {
minutes = parseInt(timer / 60, 10)
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
//poll reciever
Stripe.bitcoinReceiver.pollReceiver(response.id, filledReceiverHandler(response));
countdown.textContent = minutes + ":" + seconds;
if (--timer < 0) {
clearInterval(t);
document.getElementById("bitcoin_total").innerHTML = "";
document.getElementById("bitcoin_payment_string").innerHTML = "";
document.getElementById("bitcoin_button_text").innerHTML = "Refresh Order"
document.getElementById("btc-button").href = "javascript:history.back()"
document.getElementById("extra_purchase_notes").innerHTML = "<em><strong>Oops...</strong> This order has expired, use the Refresh Order button to retry.</em>"
Stripe.bitcoinReceiver.cancelReceiverPoll(response.id);
}
}, 1000);
}
//start timer
var countdown = document.getElementById('countdown_time');
startTimer(600, countdown);
} else {
document.getElementById("bitcoin_uri_string").innerHTML = JSON.stringify(response);
}
}
Stripe.bitcoinReceiver.createReceiver({
amount: "<%= (#order.total * 100).round %>",
currency: 'usd',
description: 'Software purchase',
email: "<%= #order.email %>"
}, populateBitcoinCheckout);
function filledReceiverHandler(response)
{
if (response.filled === true) {
function post(path, parameters) {
var form = $('<form></form>');
form.attr("method", "post");
form.attr("action", path);
$.each(parameters, function(key, value) {
if ( typeof value == 'object' || typeof value == 'array' ){
$.each(value, function(subkey, subvalue) {
var field = $('<input />');
field.attr("type", "hidden");
field.attr("name", key+'[]');
field.attr("value", subvalue);
form.append(field);
});
} else {
var field = $('<input />');
field.attr("type", "hidden");
field.attr("name", key);
field.attr("value", value);
form.append(field);
}
});
$(document.body).append(form);
form.submit();
}
post('purchase_bitcoin', response);
}
}
</script>
</div>
</div>
<p id="bitcoin_payment_string"></p>
<div class="d"></div>
<p style="text-align: right;">
<a id="btc-button"><button class="bitcoin-button" style="visibility: visible;"><span id="bitcoin_button_text">Pay with Bitcoin</span></button></a>
</p>
I made more revisions, and actually removed all polling logic from my .erb, electing to use a web hook instead.
https://stripe.com/docs/webhooks
require 'json'
require 'stripe'
class Store::BitcoinController < ApplicationController
def payment_recieved
type = params[:type]
data = params[:data]
if (type.blank? || type != "bitcoin.receiver.filled" || data.blank?)
logger.warn("Got request to Bitcoin IPN with invalid receiver email from #{request.remote_addr || request.remote_ip}")
render :nothing => true, :status => 200
return
end
..process order here...
hopefully this will help some others with the same issues.
:)