Files
ubicloud/routes/project/billing.rb
Enes Cakir 310b285141 Trim whitespace from billing email before sending
I noticed a few failed web requests caused by whitespace email address.

Let's trim the email address before sending it to the API.
2025-01-07 11:16:13 +03:00

174 lines
6.3 KiB
Ruby

# frozen_string_literal: true
require "stripe"
require "countries"
class Clover
hash_branch(:project_prefix, "billing") do |r|
r.web do
unless (Stripe.api_key = Config.stripe_secret_key)
response.status = 501
response["content-type"] = "text/plain"
next "Billing is not enabled. Set STRIPE_SECRET_KEY to enable billing."
end
authorize("Project:billing", @project.id)
r.get true do
if (billing_info = @project.billing_info)
@billing_info_data = Serializers::BillingInfo.serialize(billing_info)
@payment_methods = Serializers::PaymentMethod.serialize(billing_info.payment_methods)
@invoices = Serializers::Invoice.serialize(@project.invoices.prepend(@project.current_invoice))
end
@usage_alerts = Serializers::UsageAlert.serialize(@project.usage_alerts_dataset.eager(:user))
view "project/billing"
end
r.post true do
if (billing_info = @project.billing_info)
begin
Stripe::Customer.update(billing_info.stripe_id, {
name: r.params["name"],
email: r.params["email"].strip,
address: {
country: r.params["country"],
state: r.params["state"],
city: r.params["city"],
postal_code: r.params["postal_code"],
line1: r.params["address"],
line2: nil
},
metadata: {
tax_id: r.params["tax_id"],
company_name: r.params["company_name"]
}
})
rescue Stripe::InvalidRequestError => e
flash["error"] = e.message
end
r.redirect @project.path + "/billing"
end
checkout = Stripe::Checkout::Session.create(
payment_method_types: ["card"],
mode: "setup",
customer_creation: "always",
billing_address_collection: "required",
success_url: "#{Config.base_url}#{@project.path}/billing/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "#{Config.base_url}#{@project.path}/billing"
)
r.redirect checkout.url, 303
end
r.get "success" do
checkout_session = Stripe::Checkout::Session.retrieve(r.params["session_id"])
setup_intent = Stripe::SetupIntent.retrieve(checkout_session["setup_intent"])
stripe_id = setup_intent["payment_method"]
card_fingerprint = Stripe::PaymentMethod.retrieve(stripe_id)["card"]["fingerprint"]
if PaymentMethod.where(fraud: true).select_map(:card_fingerprint).include?(card_fingerprint)
flash["error"] = "Payment method you added is labeled as fraud. Please contact support."
r.redirect @project.path + "/billing"
end
# Pre-authorize card to check if it is valid, if so
# authorization won't be captured and will be refunded immediately
begin
customer_stripe_id = setup_intent["customer"]
# Pre-authorizing random amount to verify card. As it is
# commonly done with other companies, apparently it is
# better to detect fraud then pre-authorizing fixed amount.
# That money will be kept until next billing period and if
# it's not a fraud, it will be applied to the invoice.
preauth_amount = [100, 200, 300, 400, 500].sample
payment_intent = Stripe::PaymentIntent.create({
amount: preauth_amount,
currency: "usd",
confirm: true,
off_session: true,
capture_method: "manual",
customer: customer_stripe_id,
payment_method: stripe_id
})
if payment_intent.status != "requires_capture"
raise "Authorization failed"
end
rescue
# Log and redirect if Stripe card error or our manual raise
Clog.emit("Couldn't pre-authorize card") { {card_authorization: {project_id: @project.id, customer_stripe_id: customer_stripe_id}} }
flash["error"] = "We couldn't pre-authorize your card for verification. Please make sure it can be pre-authorized up to $5 or contact our support team at support@ubicloud.com."
r.redirect @project.path + "/billing"
end
DB.transaction do
unless (billing_info = @project.billing_info)
billing_info = BillingInfo.create_with_id(stripe_id: customer_stripe_id)
@project.update(billing_info_id: billing_info.id)
end
PaymentMethod.create_with_id(billing_info_id: billing_info.id, stripe_id: stripe_id, card_fingerprint: card_fingerprint, preauth_intent_id: payment_intent.id, preauth_amount: preauth_amount)
end
r.redirect @project.path + "/billing"
end
r.on "payment-method" do
r.get "create" do
next unless (billing_info = @project.billing_info)
checkout = Stripe::Checkout::Session.create(
payment_method_types: ["card"],
mode: "setup",
customer: billing_info.stripe_id,
success_url: "#{Config.base_url}#{@project.path}/billing/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "#{Config.base_url}#{@project.path}/billing"
)
r.redirect checkout.url, 303
end
r.is String do |pm_ubid|
next unless (payment_method = PaymentMethod.from_ubid(pm_ubid))
r.delete true do
unless payment_method.billing_info.payment_methods.count > 1
response.status = 400
next {message: "You can't delete the last payment method of a project."}
end
payment_method.destroy
204
end
end
end
r.on "invoice" do
r.is String do |invoice_ubid|
invoice = (invoice_ubid == "current") ? @project.current_invoice : Invoice.from_ubid(invoice_ubid)
next unless invoice
r.get true do
@invoice_data = Serializers::Invoice.serialize(invoice, {detailed: true})
if r.params["pdf"] == "1"
response["Content-Type"] = "application/pdf"
response["Content-Disposition"] = "filename=\"#{@invoice_data[:filename]}.pdf\""
next invoice.generate_pdf(@invoice_data)
end
view "project/invoice"
end
end
end
end
end
end