Rails - StimulusJS: how to set target value from within setTimeout? - ruby-on-rails

In my Rails-Stimulus controller, I fetch data in a loop from a function in which I've setTimeout. How do I set the target value from within the setTimeout?
My form partial
<%= text_field_tag :first_date, "Select Date One", data: {controller: 'flatpickr'} %>
<div data-controller="hist">
<div data-hist-target="one"></div>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 border border-blue-700 rounded"
data-action='hist#get_history'>
Hist
</button>
</div>
My controller
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
static targets = [ "one" ]
connect() {
}
get_date_history(aDate) {
setTimeout(function() {
var history = new Array()
let month = aDate.slice(5,7)
let date = aDate.slice(8,10)
let url = 'http://numbersapi.com/' + month + '/' + date + '/date'
for (let step = 0; step < 5; step++) {
fetch(url)
.then(response => response.text())
.then(function(data) {
history.push(data)
})
.catch(err => console.log('Request Failed', err)) // Catch errors
}
// can we set oneTarget value here? how?
}, 2500)
}
get_history() {
var date = document.getElementById("first_date").value
this.get_date_history(date)
}
}

Related

What needs to be done on the Input form field to mask the format to the database?

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 + "-";
}
}
)
}
}

How to limit upload files in dropzone.js?

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

trying to implement retrieve attachments on update action

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 %>

Stripe SCA one-time payments Rails

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

custom Bitcoin Stripe Rails payments

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.
:)

Resources