FIX: One-time purchase pending invoice item (#210)

This change ensures we attach the invoice item to the invoice to avoid
any occurrences of an empty invoice being paid with pending invoice
items.
This commit is contained in:
Blake Erickson 2024-05-07 08:57:37 -06:00 committed by GitHub
parent e132913db4
commit d63c84eca5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 5 deletions

View File

@ -100,16 +100,22 @@ module DiscourseSubscriptions
else else
coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] && coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] &&
promo_code[:coupon][:id] promo_code[:coupon][:id]
invoice = ::Stripe::Invoice.create(customer: customer[:id])
invoice_item = invoice_item =
::Stripe::InvoiceItem.create( ::Stripe::InvoiceItem.create(
customer: customer[:id], customer: customer[:id],
price: params[:plan], price: params[:plan],
discounts: [{ coupon: coupon_id }], discounts: [{ coupon: coupon_id }],
invoice: invoice[:id],
) )
invoice = ::Stripe::Invoice.create(customer: customer[:id])
transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id]) transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id])
payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] == payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] ==
"open" "open"
if payment_intent.nil?
return(
render_json_error I18n.t("js.discourse_subscriptions.subscribe.transaction_error")
)
end
transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] == transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] ==
"successful" "successful"
end end

View File

@ -45,8 +45,10 @@ module DiscourseSubscriptions
invoices_with_products = invoices_with_products =
all_invoices[:data].select do |invoice| all_invoices[:data].select do |invoice|
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data] invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
invoice_product_id = parse_invoice_lines(invoice_lines) if invoice_lines
product_ids.include?(invoice_product_id) invoice_product_id = parse_invoice_lines(invoice_lines)
product_ids.include?(invoice_product_id)
end
end end
end end

View File

@ -266,6 +266,35 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
}.to change { DiscourseSubscriptions::Customer.count } }.to change { DiscourseSubscriptions::Customer.count }
end end
it "returns 422 on a one time payment subscription error" do
# It's possible that the invoice item doesn't get attached
# to the invoice. This means the invoice is paid, but for $0.00 with
# a pending invoice item.
::Stripe::Price.expects(:retrieve).returns(
type: "one_time",
product: "product_12345",
metadata: {
group_name: "awesome",
},
)
::Stripe::InvoiceItem.expects(:create)
::Stripe::Invoice.expects(:create).returns(status: "open", id: "in_123")
::Stripe::Invoice.expects(:finalize_invoice).returns(
id: "in_123",
status: "paid",
payment_intent: "pi_123",
)
expect {
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
}.not_to change { DiscourseSubscriptions::Customer.count }
expect(response.status).to eq 422
end
it "creates a one time payment subscription" do it "creates a one time payment subscription" do
::Stripe::Price.expects(:retrieve).returns( ::Stripe::Price.expects(:retrieve).returns(
type: "one_time", type: "one_time",
@ -414,14 +443,15 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
}, },
) )
::Stripe::Invoice.expects(:create).returns(status: "open", id: "in_123")
::Stripe::InvoiceItem.expects(:create).with( ::Stripe::InvoiceItem.expects(:create).with(
customer: "cus_1234", customer: "cus_1234",
price: "plan_1234", price: "plan_1234",
discounts: [{ coupon: "c123" }], discounts: [{ coupon: "c123" }],
invoice: "in_123",
) )
::Stripe::Invoice.expects(:create).returns(status: "open", id: "in_123")
::Stripe::Invoice.expects(:finalize_invoice).returns( ::Stripe::Invoice.expects(:finalize_invoice).returns(
id: "in_123", id: "in_123",
status: "open", status: "open",