# frozen_string_literal: true class UserUpgrade < ApplicationRecord belongs_to :recipient, class_name: "User" belongs_to :purchaser, class_name: "User" enum upgrade_type: { gold: 0, platinum: 10, gold_to_platinum: 20, }, _suffix: "upgrade" enum status: { pending: 0, processing: 10, complete: 20, refunded: 30, } scope :gifted, -> { where("recipient_id != purchaser_id") } scope :self_upgrade, -> { where("recipient_id = purchaser_id") } def self.enabled? stripe_secret_key.present? && stripe_publishable_key.present? && stripe_webhook_secret.present? end def self.stripe_secret_key Danbooru.config.stripe_secret_key end 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 if Danbooru.config.is_promotion? 1500 else 2000 end end def self.platinum_price 2 * gold_price end def self.gold_to_platinum_price platinum_price - gold_price end def level case upgrade_type when "gold" User::Levels::GOLD when "platinum" User::Levels::PLATINUM when "gold_to_platinum" User::Levels::PLATINUM else raise NotImplementedError end end def previous_level case upgrade_type when "gold" User::Levels::MEMBER when "platinum" User::Levels::MEMBER when "gold_to_platinum" User::Levels::GOLD else raise NotImplementedError end end def level_string User.level_string(level) end def is_gift? recipient != purchaser end def self.visible(user) if user.is_owner? all else where(recipient: user).or(where(purchaser: user)) end end def self.search(params) q = search_attributes(params, :id, :created_at, :updated_at, :upgrade_type, :status, :stripe_id, :recipient, :purchaser) if params[:is_gifted].to_s.truthy? q = q.gifted elsif params[:is_gifted].to_s.falsy? q = q.self_upgrade end q.apply_default_order(params) end concerning :UpgradeMethods do def process_upgrade!(payment_status) recipient.with_lock do return unless pending? || processing? if payment_status == "paid" upgrade_recipient! dmail_recipient! dmail_purchaser! update!(status: :complete) else update!(status: :processing) end end end def upgrade_recipient! recipient.update!(level: level) end def dmail_recipient! if is_gift? body = "Congratulations, your account has been upgraded to #{level_string} by <@#{purchaser.name}>. Enjoy!" else body = "You are now a #{level_string} user. Thanks for supporting #{Danbooru.config.canonical_app_name}!" end title = "You have been upgraded to #{level_string}!" Dmail.create_automated(to: recipient, title: title, body: body) end def dmail_purchaser! return unless is_gift? title = "#{recipient.name} has been upgraded to #{level_string}!" body = "<@#{recipient.name}> is now a #{level_string} user. Thanks for supporting #{Danbooru.config.canonical_app_name}!" Dmail.create_automated(to: purchaser, title: title, body: body) end end concerning :StripeMethods do def create_checkout!(country: "US", allow_promotion_codes: false) methods = payment_method_types(country) currency = preferred_currency(country) price_id = upgrade_price_id(currency) checkout = Stripe::Checkout::Session.create( mode: "payment", success_url: Routes.user_upgrade_url(self), cancel_url: Routes.new_user_upgrade_url(user_id: recipient.id), client_reference_id: "user_upgrade_#{id}", customer_email: purchaser.email_address&.address, payment_method_types: methods, allow_promotion_codes: allow_promotion_codes.presence, line_items: [{ price: price_id, quantity: 1, }], discounts: [{ coupon: promotion_discount_id, }], metadata: { user_upgrade_id: id, purchaser_id: purchaser.id, recipient_id: recipient.id, purchaser_name: purchaser.name, recipient_name: recipient.name, upgrade_type: upgrade_type, country: country, is_gift: is_gift?, level: level, } ) update!(stripe_id: checkout.id) checkout end def refund!(reason: nil) with_lock do return if refunded? Stripe::Refund.create(payment_intent: payment_intent.id, reason: reason) recipient.update!(level: previous_level) update!(status: "refunded") end end def receipt_url return nil if pending? || stripe_id.nil? charge.receipt_url end def payment_url return nil if pending? || stripe_id.nil? "https://dashboard.stripe.com/payments/#{payment_intent.id}" end def checkout_session @checkout_session ||= Stripe::Checkout::Session.retrieve(stripe_id) end def payment_intent @payment_intent ||= Stripe::PaymentIntent.retrieve(checkout_session.payment_intent) end def charge payment_intent.charges.data.first end def has_receipt? !pending? end def has_payment? !pending? end def promotion_discount_id if Danbooru.config.is_promotion? Danbooru.config.stripe_promotion_discount_id end end def upgrade_price_id(currency) case [upgrade_type, currency] when ["gold", "usd"] Danbooru.config.stripe_gold_usd_price_id when ["gold", "eur"] Danbooru.config.stripe_gold_eur_price_id when ["platinum", "usd"] Danbooru.config.stripe_platinum_usd_price_id when ["platinum", "eur"] Danbooru.config.stripe_platinum_eur_price_id when ["gold_to_platinum", "usd"] Danbooru.config.stripe_gold_to_platinum_usd_price_id when ["gold_to_platinum", "eur"] Danbooru.config.stripe_gold_to_platinum_eur_price_id else raise NotImplementedError end end def payment_method_types(country) case country.to_s.upcase # Austria, https://stripe.com/docs/payments/bancontact when "AT" ["card", "eps"] # Belgium, https://stripe.com/docs/payments/eps when "BE" ["card", "bancontact"] # Germany, https://stripe.com/docs/payments/giropay when "DE" ["card", "giropay"] # Netherlands, https://stripe.com/docs/payments/ideal when "NL" ["card", "ideal"] # Poland, https://stripe.com/docs/payments/p24 when "PL" ["card", "p24"] else ["card"] end end def preferred_currency(country) case country.to_s.upcase # Austria, Belgium, Germany, Netherlands, Poland when "AT", "BE", "DE", "NL", "PL" "eur" else "usd" end 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.data.object) 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(checkout) user_upgrade = UserUpgrade.find(checkout.metadata.user_upgrade_id) user_upgrade.process_upgrade!(checkout.payment_status) end end end end