Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/controllers/stripe/invoices_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Stripe
class InvoicesController < ApplicationController
before_action :authenticate_user!

def index
pagy, items = Invoices::Index.run!(user: current_user, pagy_params:)

render json: StripeInvoiceSerializer.new(items, meta: page_info(pagy)).serializable_hash
end
end
end
6 changes: 6 additions & 0 deletions app/errors/api_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,10 @@ def initialize
super(code: 1012, status: 422, message: "stripe price not found or credit_quota is invalid")
end
end

class StripeDuplicateSubscriptionPriceError < ApiError
def initialize
super(code: 1013, status: 400, message: "new subscription price cannot be the same as the current one")
end
end
end
13 changes: 13 additions & 0 deletions app/interactions/invoices/index.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Invoices
class Index < ActiveInteraction::Base
include Pagy::Backend

object :user
hash :pagy_params, default: {}, strip: false

def execute
scope = user.stripe_invoices.order(created: :desc)
pagy(scope, **pagy_params.symbolize_keys)
end
end
end
7 changes: 5 additions & 2 deletions app/interactions/subscriptions/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ class Update < ActiveInteraction::Base

def execute
data = user.stripe_subscriptions.effective
subscription_uid = data.subscription_uid
raise ApiError::StripeDuplicateSubscriptionPriceError if price.eql?(data.price_uid)

subscription_uid = data.subscription_uid
subscription = Stripe::Subscription.retrieve(subscription_uid)
subscription_item_uid = subscription.items.data[0].id

# allow_incomplete: 允许未完成支付状态(incomplete),发票创建后无需立即付款。Stripe 会尝试扣款,失败则订阅进入 incomplete
# always_invoice: 始终创建一张新的账单(invoice)来结算这次变更。区别是一定会有 invoice(即使金额是 0)
Stripe::SubscriptionItem.update(
subscription_item_uid, {
price:,
payment_behavior: "default_incomplete",
payment_behavior: "allow_incomplete",
proration_behavior: "always_invoice",
}
)
Expand Down
8 changes: 8 additions & 0 deletions app/interactions/webhooks/subscription.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ def handle_subscription_updated(obj)
raise "Subscription #{obj.id} not found" unless subscription

item = obj.items.data[0]
old_price_uid = subscription.price_uid
new_price_uid = item.price.id

attributes = {
current_period_start: item.current_period_start,
Expand All @@ -59,6 +61,12 @@ def handle_subscription_updated(obj)
}

subscription.update!(attributes)

# 如果 price 发生变化并且订阅为 active,则重置用户 credits
if obj.status == "active" && old_price_uid != new_price_uid
price = StripePrice.find_by(price_uid: new_price_uid)
CreditsService.new(subscription.user).reset_credits!(price.credit_quota) if price
end
end

def handle_subscription_deleted(obj)
Expand Down
1 change: 1 addition & 0 deletions app/models/stripe_customer.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class StripeCustomer < ApplicationRecord
belongs_to :user
has_many :stripe_invoices, foreign_key: :customer_uid, primary_key: :customer_uid
end

# == Schema Information
Expand Down
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class User < ApplicationRecord
has_many :stripe_customers
has_many :stripe_invoices, through: :stripe_customers
has_many :stripe_checkout_sessions
has_many :api_calls
has_many :stripe_subscriptions do
Expand Down
6 changes: 6 additions & 0 deletions app/serializers/stripe_invoice_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class StripeInvoiceSerializer
include JSONAPI::Serializer

set_id :invoice_uid
attributes :amount_due, :billing_reason, :created, :hosted_invoice_url, :status, :subscription_uid
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
resources :checkout_sessions, param: :session_uid, only: %i[index create destroy]
resource :subscription, only: %i[show update destroy]
resources :products, only: :index
resources :invoices, only: :index
resources :webhooks do
post :callback, on: :collection
end
Expand Down
Loading