diff --git a/app/controllers/user_upgrades_controller.rb b/app/controllers/user_upgrades_controller.rb
index be7ee116a..e2b8729d6 100644
--- a/app/controllers/user_upgrades_controller.rb
+++ b/app/controllers/user_upgrades_controller.rb
@@ -1,11 +1,12 @@
class UserUpgradesController < ApplicationController
helper_method :user
- skip_before_action :verify_authenticity_token, only: [:create]
+ respond_to :js, :html
def create
- if params[:stripeToken]
- create_stripe
- end
+ @user_upgrade = UserUpgrade.new(recipient: user, purchaser: CurrentUser.user, level: params[:level].to_i)
+ @checkout = @user_upgrade.create_checkout
+
+ respond_with(@user_upgrade)
end
def new
@@ -22,38 +23,4 @@ class UserUpgradesController < ApplicationController
CurrentUser.user
end
end
-
- private
-
- def create_stripe
- @user = user
-
- if params[:desc] == "Upgrade to Gold"
- level = User::Levels::GOLD
- cost = UserUpgrade.gold_price
- elsif params[:desc] == "Upgrade to Platinum"
- level = User::Levels::PLATINUM
- cost = UserUpgrade.platinum_price
- elsif params[:desc] == "Upgrade Gold to Platinum" && @user.level == User::Levels::GOLD
- level = User::Levels::PLATINUM
- cost = UserUpgrade.upgrade_price
- else
- raise "Invalid desc"
- end
-
- begin
- charge = Stripe::Charge.create(amount: cost, currency: "usd", source: params[:stripeToken], description: params[:desc])
- @user.promote_to!(level, User.system, is_upgrade: true)
- flash[:success] = true
- rescue Stripe::StripeError => e
- DanbooruLogger.log(e)
- flash[:error] = e.message
- end
-
- if @user == CurrentUser.user
- redirect_to user_upgrade_path
- else
- redirect_to user_upgrade_path(user_id: params[:user_id])
- end
- end
end
diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb
new file mode 100644
index 000000000..0ff5cdeae
--- /dev/null
+++ b/app/controllers/webhooks_controller.rb
@@ -0,0 +1,13 @@
+class WebhooksController < ApplicationController
+ skip_forgery_protection only: :receive
+ rescue_with Stripe::SignatureVerificationError, status: 400
+
+ def receive
+ if params[:source] == "stripe"
+ UserUpgrade.receive_webhook(request)
+ head 200
+ else
+ head 400
+ end
+ end
+end
diff --git a/app/helpers/user_upgrades_helper.rb b/app/helpers/user_upgrades_helper.rb
index 0b7c301aa..8d37de45d 100644
--- a/app/helpers/user_upgrades_helper.rb
+++ b/app/helpers/user_upgrades_helper.rb
@@ -1,24 +1,4 @@
module UserUpgradesHelper
- def stripe_button(desc, cost, user)
- html = %{
-
- }
-
- raw(html)
- end
-
def cents_to_usd(cents)
number_to_currency(cents / 100, precision: 0)
end
diff --git a/app/logical/user_upgrade.rb b/app/logical/user_upgrade.rb
index b29909faf..f9af14708 100644
--- a/app/logical/user_upgrade.rb
+++ b/app/logical/user_upgrade.rb
@@ -1,13 +1,145 @@
class UserUpgrade
+ attr_reader :recipient, :purchaser, :level
+
+ def self.stripe_publishable_key
+ Danbooru.config.stripe_publishable_key
+ end
+
+ def self.stripe_webhook_secret
+ Danbooru.config.stripe_webhook_secret
+ end
+
def self.gold_price
2000
end
def self.platinum_price
- 4000
+ 2 * gold_price
end
- def self.upgrade_price
- 2000
+ def self.gold_to_platinum_price
+ platinum_price - gold_price
+ end
+
+ def initialize(recipient:, purchaser:, level:)
+ @recipient, @purchaser, @level = recipient, purchaser, level.to_i
+ end
+
+ def upgrade_type
+ if level == User::Levels::GOLD && recipient.level == User::Levels::MEMBER
+ :gold_upgrade
+ elsif level == User::Levels::PLATINUM && recipient.level == User::Levels::MEMBER
+ :platinum_upgrade
+ elsif level == User::Levels::PLATINUM && recipient.level == User::Levels::GOLD
+ :gold_to_platinum_upgrade
+ else
+ raise ArgumentError, "Invalid upgrade"
+ end
+ end
+
+ def upgrade_price
+ case upgrade_type
+ when :gold_upgrade
+ UserUpgrade.gold_price
+ when :platinum_upgrade
+ UserUpgrade.platinum_price
+ when :gold_to_platinum_upgrade
+ UserUpgrade.gold_to_platinum_price
+ end
+ end
+
+ def upgrade_description
+ case upgrade_type
+ when :gold_upgrade
+ "Upgrade to Gold"
+ when :platinum_upgrade
+ "Upgrade to Platinum"
+ when :gold_to_platinum_upgrade
+ "Upgrade Gold to Platinum"
+ end
+ end
+
+ def is_gift?
+ recipient != purchaser
+ end
+
+ def process_upgrade!
+ recipient.with_lock do
+ upgrade_recipient!
+ end
+ end
+
+ def upgrade_recipient!
+ recipient.promote_to!(level, User.system, is_upgrade: true)
+ end
+
+ concerning :StripeMethods do
+ def create_checkout
+ Stripe::Checkout::Session.create(
+ mode: "payment",
+ success_url: Routes.user_upgrade_url(user_id: recipient.id),
+ cancel_url: Routes.new_user_upgrade_url(user_id: recipient.id),
+ client_reference_id: "user_#{purchaser.id}",
+ customer_email: recipient.email_address&.address,
+ payment_method_types: ["card"],
+ line_items: [{
+ price_data: {
+ unit_amount: upgrade_price,
+ currency: "usd",
+ product_data: {
+ name: upgrade_description,
+ },
+ },
+ quantity: 1,
+ }],
+ metadata: {
+ purchaser_id: purchaser.id,
+ recipient_id: recipient.id,
+ purchaser_name: purchaser.name,
+ recipient_name: recipient.name,
+ upgrade_type: upgrade_type,
+ is_gift: is_gift?,
+ level: level,
+ },
+ )
+ end
+
+ class_methods do
+ def register_webhook
+ webhook = Stripe::WebhookEndpoint.create({
+ url: Routes.webhook_user_upgrade_url(source: "stripe"),
+ enabled_events: [
+ "payment_intent.created",
+ "payment_intent.payment_failed",
+ "checkout.session.completed",
+ ],
+ })
+
+ webhook.secret
+ end
+
+ def receive_webhook(request)
+ event = build_event(request)
+
+ if event.type == "checkout.session.completed"
+ checkout_session_completed(event)
+ end
+ end
+
+ def build_event(request)
+ payload = request.body.read
+ signature = request.headers["Stripe-Signature"]
+ Stripe::Webhook.construct_event(payload, signature, stripe_webhook_secret)
+ end
+
+ def checkout_session_completed(event)
+ recipient = User.find(event.data.object.metadata.recipient_id)
+ purchaser = User.find(event.data.object.metadata.purchaser_id)
+ level = event.data.object.metadata.level
+
+ user_upgrade = UserUpgrade.new(recipient: recipient, purchaser: purchaser, level: level)
+ user_upgrade.process_upgrade!
+ end
+ end
end
end
diff --git a/app/views/user_upgrades/_stripe_payment.html.erb b/app/views/user_upgrades/_stripe_payment.html.erb
deleted file mode 100644
index b9e43052d..000000000
--- a/app/views/user_upgrades/_stripe_payment.html.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
You can pay with a credit or debit card. Safebooru uses Stripe as a payment intermediary so none of your personal information will be stored on the site.
-
- <% if user.level < User::Levels::GOLD %>
- <%= stripe_button("Upgrade to Gold", UserUpgrade.gold_price, user) %>
- <%= stripe_button("Upgrade to Platinum", UserUpgrade.platinum_price, user) %>
- <% elsif user.level < User::Levels::PLATINUM %>
- <%= stripe_button("Upgrade Gold to Platinum", UserUpgrade.upgrade_price, user) %>
- <% end %>
-
diff --git a/app/views/user_upgrades/_stripe_payment_safebooru.html.erb b/app/views/user_upgrades/_stripe_payment_safebooru.html.erb
deleted file mode 100644
index 730eab9f6..000000000
--- a/app/views/user_upgrades/_stripe_payment_safebooru.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
You can pay with a credit or debit card on <%= link_to "Safebooru", new_user_upgrade_url(host: "safebooru.donmai.us", protocol: "https") %>. Your account will then also be upgraded on Danbooru. You can login to Safebooru with the same username and password you use on Danbooru.
-
diff --git a/app/views/user_upgrades/create.js.erb b/app/views/user_upgrades/create.js.erb
new file mode 100644
index 000000000..e08bc3438
--- /dev/null
+++ b/app/views/user_upgrades/create.js.erb
@@ -0,0 +1,2 @@
+var stripe = Stripe("<%= j UserUpgrade.stripe_publishable_key %>");
+stripe.redirectToCheckout({ sessionId: "<%= j @checkout.id %>" });
diff --git a/app/views/user_upgrades/new.html.erb b/app/views/user_upgrades/new.html.erb
index 4cd610bb3..2ed284092 100644
--- a/app/views/user_upgrades/new.html.erb
+++ b/app/views/user_upgrades/new.html.erb
@@ -1,5 +1,6 @@
<% page_title "Account Upgrade" %>
-<% meta_description "Upgrade to a Gold or Platinum account on #{Danbooru.config.app_name}." %>
+<% meta_description "Upgrade to a Gold or Platinum account." %>
+
<%= render "users/secondary_links" %>
@@ -96,9 +97,24 @@
<% if CurrentUser.is_anonymous? %>
<%= link_to "Sign up", new_user_path %> or <%= link_to "login", login_path(url: new_user_upgrade_path) %> first to upgrade your account.
<% elsif CurrentUser.safe_mode? %>
- <%= render "stripe_payment" %>
+
+
You can pay with a credit or debit card. Safebooru uses Stripe
+ as a payment intermediary so none of your personal information will be stored on the site.
+
+ <% if user.level < User::Levels::GOLD %>
+
<%= button_to "Upgrade to Gold", user_upgrade_path(user_id: user.id, level: User::Levels::GOLD), remote: true, disable_with: "Redirecting..." %>
+
<%= button_to "Upgrade to Platinum", user_upgrade_path(user_id: user.id, level: User::Levels::PLATINUM), remote: true, disable_with: "Redirecting..." %>
+ <% elsif user.level < User::Levels::PLATINUM %>
+
<%= button_to "Upgrade Gold to Platinum", user_upgrade_path(user_id: user.id, level: User::Levels::PLATINUM), remote: true, disable_with: "Redirecting..." %>
+ <% end %>
+
<% else %>
- <%= render "stripe_payment_safebooru" %>
+
+
You can pay with a credit or debit card on
+ <%= link_to "Safebooru", new_user_upgrade_url(user_id: user.id, host: "safebooru.donmai.us", protocol: "https") %>.
+ Your account will then also be upgraded on Danbooru. You can login to
+ Safebooru with the same username and password you use on Danbooru.
+
<% end %>
<% end %>
diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb
index 41fd594d9..75622de73 100644
--- a/config/danbooru_default_config.rb
+++ b/config/danbooru_default_config.rb
@@ -359,6 +359,9 @@ module Danbooru
def stripe_publishable_key
end
+ def stripe_webhook_secret
+ end
+
def twitter_api_key
end
diff --git a/config/initializers/stripe.rb b/config/initializers/stripe.rb
index 43495044b..7a60018c9 100644
--- a/config/initializers/stripe.rb
+++ b/config/initializers/stripe.rb
@@ -1 +1,2 @@
Stripe.api_key = Danbooru.config.stripe_secret_key
+Stripe.api_version = "2020-08-27"
diff --git a/config/routes.rb b/config/routes.rb
index 9ab7cacfa..b027bb3a3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -257,6 +257,9 @@ Rails.application.routes.draw do
resource :user_upgrade, :only => [:new, :create, :show]
resources :user_feedbacks, except: [:destroy]
resources :user_name_change_requests, only: [:new, :create, :show, :index]
+ resources :webhooks do
+ post :receive, on: :collection
+ end
resources :wiki_pages, id: /.+?(?=\.json|\.xml|\.html)|.+/ do
put :revert, on: :member
get :search, on: :collection
diff --git a/test/fixtures/stripe-webhooks/checkout.session.completed.json b/test/fixtures/stripe-webhooks/checkout.session.completed.json
new file mode 100644
index 000000000..975bb555d
--- /dev/null
+++ b/test/fixtures/stripe-webhooks/checkout.session.completed.json
@@ -0,0 +1,55 @@
+{
+ "id": "evt_000",
+ "object": "event",
+ "api_version": "2020-08-27",
+ "created": 1608705740,
+ "data": {
+ "object": {
+ "id": "cs_test_000",
+ "object": "checkout.session",
+ "allow_promotion_codes": null,
+ "amount_subtotal": 2000,
+ "amount_total": 2000,
+ "billing_address_collection": null,
+ "cancel_url": "http://localhost/user_upgrade/new",
+ "client_reference_id": "user_12345",
+ "currency": "usd",
+ "customer": "cus_000",
+ "customer_email": null,
+ "livemode": false,
+ "locale": null,
+ "metadata": {
+ "purchaser_id": "12345",
+ "recipient_id": "12345",
+ "purchaser_name": "user_12345",
+ "recipient_name": "user_12345",
+ "upgrade_type": "gold_upgrade",
+ "is_gift": "false",
+ "level": "30"
+ },
+ "mode": "payment",
+ "payment_intent": "pi_000",
+ "payment_method_types": [
+ "card"
+ ],
+ "payment_status": "paid",
+ "setup_intent": null,
+ "shipping": null,
+ "shipping_address_collection": null,
+ "submit_type": null,
+ "subscription": null,
+ "success_url": "http://localhost/user_upgrade?user_id=12345",
+ "total_details": {
+ "amount_discount": 0,
+ "amount_tax": 0
+ }
+ }
+ },
+ "livemode": false,
+ "pending_webhooks": 3,
+ "request": {
+ "id": null,
+ "idempotency_key": null
+ },
+ "type": "checkout.session.completed"
+}
diff --git a/test/fixtures/stripe-webhooks/payment_intent.created.json b/test/fixtures/stripe-webhooks/payment_intent.created.json
new file mode 100644
index 000000000..915aa9f90
--- /dev/null
+++ b/test/fixtures/stripe-webhooks/payment_intent.created.json
@@ -0,0 +1,67 @@
+{
+ "id": "evt_000",
+ "object": "event",
+ "api_version": "2020-08-27",
+ "created": 1608705945,
+ "data": {
+ "object": {
+ "id": "pi_000",
+ "object": "payment_intent",
+ "amount": 2000,
+ "amount_capturable": 0,
+ "amount_received": 0,
+ "application": null,
+ "application_fee_amount": null,
+ "canceled_at": null,
+ "cancellation_reason": null,
+ "capture_method": "automatic",
+ "charges": {
+ "object": "list",
+ "data": [],
+ "has_more": false,
+ "total_count": 0,
+ "url": "/v1/charges?payment_intent=pi_000"
+ },
+ "client_secret": "pi_000",
+ "confirmation_method": "automatic",
+ "created": 1608705945,
+ "currency": "usd",
+ "customer": null,
+ "description": null,
+ "invoice": null,
+ "last_payment_error": null,
+ "livemode": false,
+ "metadata": {},
+ "next_action": null,
+ "on_behalf_of": null,
+ "payment_method": null,
+ "payment_method_options": {
+ "card": {
+ "installments": null,
+ "network": null,
+ "request_three_d_secure": "automatic"
+ }
+ },
+ "payment_method_types": [
+ "card"
+ ],
+ "receipt_email": null,
+ "review": null,
+ "setup_future_usage": null,
+ "shipping": null,
+ "source": null,
+ "statement_descriptor": null,
+ "statement_descriptor_suffix": null,
+ "status": "requires_payment_method",
+ "transfer_data": null,
+ "transfer_group": null
+ }
+ },
+ "livemode": false,
+ "pending_webhooks": 3,
+ "request": {
+ "id": "req_000",
+ "idempotency_key": null
+ },
+ "type": "payment_intent.created"
+}
diff --git a/test/functional/user_upgrades_controller_test.rb b/test/functional/user_upgrades_controller_test.rb
index db88aa327..58e92a8aa 100644
--- a/test/functional/user_upgrades_controller_test.rb
+++ b/test/functional/user_upgrades_controller_test.rb
@@ -1,14 +1,6 @@
require 'test_helper'
class UserUpgradesControllerTest < ActionDispatch::IntegrationTest
- setup do
- StripeMock.start
- end
-
- teardown do
- StripeMock.stop
- end
-
context "The user upgrades controller" do
context "new action" do
should "render" do
@@ -25,103 +17,24 @@ class UserUpgradesControllerTest < ActionDispatch::IntegrationTest
end
context "create action" do
- setup do
- @user = create(:user)
- @token = StripeMock.generate_card_token
- end
+ mock_stripe!
- context "a self upgrade" do
- should "upgrade a Member to Gold" do
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade to Gold" }
-
- assert_redirected_to user_upgrade_path
- assert_equal(true, @user.reload.is_gold?)
- end
-
- should "upgrade a Member to Platinum" do
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade to Platinum" }
-
- assert_redirected_to user_upgrade_path
- assert_equal(true, @user.reload.is_platinum?)
- end
-
- should "upgrade a Gold user to Platinum" do
- @user.update!(level: User::Levels::GOLD)
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade Gold to Platinum" }
-
- assert_redirected_to user_upgrade_path
- assert_equal(true, @user.reload.is_platinum?)
- end
-
- should "log an account upgrade modaction" do
- assert_difference("ModAction.user_account_upgrade.count") do
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade to Gold" }
- end
- end
-
- should "send the user a dmail" do
- assert_difference("@user.dmails.received.count") do
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade to Gold" }
- end
- end
- end
-
- context "a gifted upgrade" do
- should "upgrade the user to Gold" do
- @other_user = create(:user)
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade to Gold", user_id: @other_user.id }
-
- assert_redirected_to user_upgrade_path(user_id: @other_user.id)
- assert_equal(true, @other_user.reload.is_gold?)
- assert_equal(false, @user.reload.is_gold?)
- end
- end
-
- context "an upgrade for a user above Platinum level" do
- should "not demote the user" do
- @builder = create(:builder_user)
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade to Gold", user_id: @builder.id }
-
- assert_response 403
- assert_equal(true, @builder.reload.is_builder?)
- end
- end
-
- context "an upgrade with a missing Stripe token" do
- should "not upgrade the user" do
- post_auth user_upgrade_path, @user, params: { desc: "Upgrade to Gold" }
+ context "for a self upgrade to Gold" do
+ should "redirect the user to the Stripe checkout page" do
+ user = create(:member_user)
+ post_auth user_upgrade_path(user_id: user.id), user, params: { level: User::Levels::GOLD }, xhr: true
assert_response :success
- assert_equal(true, @user.reload.is_member?)
end
end
- context "an upgrade with an invalid Stripe token" do
- should "not upgrade the user" do
- post_auth user_upgrade_path, @user, params: { stripeToken: "garbage", desc: "Upgrade to Gold" }
+ context "for a gifted upgrade to Gold" do
+ should "redirect the user to the Stripe checkout page" do
+ recipient = create(:member_user)
+ purchaser = create(:member_user)
+ post_auth user_upgrade_path(user_id: recipient.id), purchaser, params: { level: User::Levels::GOLD }, xhr: true
- assert_redirected_to user_upgrade_path
- assert_equal(true, @user.reload.is_member?)
- end
- end
-
- context "an upgrade with an credit card that is declined" do
- should "not upgrade the user" do
- StripeMock.prepare_card_error(:card_declined)
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade to Gold" }
-
- assert_redirected_to user_upgrade_path
- assert_equal(true, @user.reload.is_member?)
- end
- end
-
- context "an upgrade with an credit card that is expired" do
- should "not upgrade the user" do
- StripeMock.prepare_card_error(:expired_card)
- post_auth user_upgrade_path, @user, params: { stripeToken: @token, desc: "Upgrade to Gold" }
-
- assert_redirected_to user_upgrade_path
- assert_equal(true, @user.reload.is_member?)
+ assert_response :success
end
end
end
diff --git a/test/functional/webhooks_controller_test.rb b/test/functional/webhooks_controller_test.rb
new file mode 100644
index 000000000..afb596be4
--- /dev/null
+++ b/test/functional/webhooks_controller_test.rb
@@ -0,0 +1,167 @@
+require 'test_helper'
+
+class WebhooksControllerTest < ActionDispatch::IntegrationTest
+ mock_stripe!
+
+ def post_webhook(*args, **metadata)
+ event = StripeMock.mock_webhook_event(*args, metadata: metadata)
+ signature = generate_stripe_signature(event)
+ headers = { "Stripe-Signature": signature }
+
+ post receive_webhooks_path(source: "stripe"), headers: headers, params: event, as: :json
+ end
+
+ # https://github.com/stripe-ruby-mock/stripe-ruby-mock/issues/467#issuecomment-634674913
+ # https://stripe.com/docs/webhooks/signatures
+ def generate_stripe_signature(event)
+ time = Time.now
+ secret = UserUpgrade.stripe_webhook_secret
+ signature = Stripe::Webhook::Signature.compute_signature(time, event.to_json, secret)
+ Stripe::Webhook::Signature.generate_header(time, signature, scheme: Stripe::Webhook::Signature::EXPECTED_SCHEME)
+ end
+
+ context "The webhooks controller" do
+ context "receive action" do
+ context "for a request from an unrecognized source" do
+ should "fail" do
+ post receive_webhooks_path(source: "blah")
+ assert_response 400
+ end
+ end
+
+ context "for a Stripe webhook" do
+ context "with a missing signature" do
+ should "fail" do
+ event = StripeMock.mock_webhook_event("payment_intent.created")
+ post receive_webhooks_path(source: "stripe"), params: event, as: :json
+
+ assert_response 400
+ end
+ end
+
+ context "with an invalid signature" do
+ should "fail" do
+ event = StripeMock.mock_webhook_event("payment_intent.created")
+ headers = { "Stripe-Signature": "blah" }
+ post receive_webhooks_path(source: "stripe"), headers: headers, params: event, as: :json
+
+ assert_response 400
+ end
+ end
+
+ context "for a payment_intent.created event" do
+ should "work" do
+ post_webhook("payment_intent.created")
+
+ assert_response 200
+ end
+ end
+
+ context "for a checkout.session.completed event" do
+ context "for a self upgrade" do
+ context "of a Member to Gold" do
+ should "upgrade the user" do
+ @user = create(:member_user)
+
+ post_webhook("checkout.session.completed", {
+ recipient_id: @user.id,
+ purchaser_id: @user.id,
+ upgrade_type: "gold_upgrade",
+ level: User::Levels::GOLD,
+ })
+
+ assert_response 200
+ assert_equal(User::Levels::GOLD, @user.reload.level)
+ end
+ end
+
+ context "of a Member to Platinum" do
+ should "upgrade the user" do
+ @user = create(:member_user)
+
+ post_webhook("checkout.session.completed", {
+ recipient_id: @user.id,
+ purchaser_id: @user.id,
+ upgrade_type: "platinum_upgrade",
+ level: User::Levels::PLATINUM,
+ })
+
+ assert_response 200
+ assert_equal(User::Levels::PLATINUM, @user.reload.level)
+ end
+ end
+
+ context "of a Gold user to Platinum" do
+ should "upgrade the user" do
+ @user = create(:gold_user)
+
+ post_webhook("checkout.session.completed", {
+ recipient_id: @user.id,
+ purchaser_id: @user.id,
+ upgrade_type: "gold_to_platinum_upgrade",
+ level: User::Levels::PLATINUM,
+ })
+
+ assert_response 200
+ assert_equal(User::Levels::PLATINUM, @user.reload.level)
+ end
+ end
+ end
+
+ context "for a gifted upgrade" do
+ context "of a Member to Gold" do
+ should "upgrade the user" do
+ @recipient = create(:member_user)
+ @purchaser = create(:member_user)
+
+ post_webhook("checkout.session.completed", {
+ recipient_id: @recipient.id,
+ purchaser_id: @purchaser.id,
+ upgrade_type: "gold_upgrade",
+ level: User::Levels::GOLD,
+ })
+
+ assert_response 200
+ assert_equal(User::Levels::GOLD, @recipient.reload.level)
+ end
+ end
+
+ context "of a Member to Platinum" do
+ should "upgrade the user" do
+ @recipient = create(:member_user)
+ @purchaser = create(:member_user)
+
+ post_webhook("checkout.session.completed", {
+ recipient_id: @recipient.id,
+ purchaser_id: @purchaser.id,
+ upgrade_type: "platinum_upgrade",
+ level: User::Levels::PLATINUM,
+ })
+
+ assert_response 200
+ assert_equal(User::Levels::PLATINUM, @recipient.reload.level)
+ end
+ end
+
+ context "of a Gold user to Platinum" do
+ should "upgrade the user" do
+ @recipient = create(:gold_user)
+ @purchaser = create(:member_user)
+
+ post_webhook("checkout.session.completed", {
+ recipient_id: @recipient.id,
+ purchaser_id: @purchaser.id,
+ upgrade_type: "gold_to_platinum_upgrade",
+ level: User::Levels::PLATINUM,
+ })
+
+ assert_response 200
+ assert_equal(User::Levels::PLATINUM, @recipient.reload.level)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 1af778120..ca6a4b4f2 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -26,6 +26,7 @@ class ActiveSupport::TestCase
include DownloadTestHelper
include IqdbTestHelper
include UploadTestHelper
+ extend StripeTestHelper
mock_post_version_service!
mock_pool_version_service!
diff --git a/test/test_helpers/stripe_test_helper.rb b/test/test_helpers/stripe_test_helper.rb
new file mode 100644
index 000000000..d1f0f7126
--- /dev/null
+++ b/test/test_helpers/stripe_test_helper.rb
@@ -0,0 +1,13 @@
+StripeMock.webhook_fixture_path = "test/fixtures/stripe-webhooks"
+
+module StripeTestHelper
+ def mock_stripe!
+ setup do
+ StripeMock.start
+ end
+
+ teardown do
+ StripeMock.stop
+ end
+ end
+end
diff --git a/test/unit/user_upgrade_test.rb b/test/unit/user_upgrade_test.rb
new file mode 100644
index 000000000..b6d228246
--- /dev/null
+++ b/test/unit/user_upgrade_test.rb
@@ -0,0 +1,41 @@
+require 'test_helper'
+
+class UserUpgradeTest < ActiveSupport::TestCase
+ context "UserUpgrade:" do
+ context "the #process_upgrade! method" do
+ setup do
+ @user = create(:user)
+ @user_upgrade = UserUpgrade.new(recipient: @user, purchaser: @user, level: User::Levels::GOLD)
+ end
+
+ should "update the user's level" do
+ @user_upgrade.process_upgrade!
+ assert_equal(User::Levels::GOLD, @user.reload.level)
+ end
+
+ should "log an account upgrade modaction" do
+ assert_difference("ModAction.user_account_upgrade.count") do
+ @user_upgrade.process_upgrade!
+ end
+ end
+
+ should "send the user a dmail" do
+ assert_difference("@user.dmails.received.count") do
+ @user_upgrade.process_upgrade!
+ end
+ end
+
+ context "for an upgrade for a user above Platinum level" do
+ should "not demote the user" do
+ @user.update!(level: User::Levels::BUILDER)
+
+ assert_raise(User::PrivilegeError) do
+ @user_upgrade.process_upgrade!
+ end
+
+ assert_equal(true, @user.reload.is_builder?)
+ end
+ end
+ end
+ end
+end