FEATURE: Implement refunds from dashboard (#27)
An implementation of refunds from the Admin dashboard. To refund, go to Plugins > Subscriptions > Subscriptions then click the `Cancel` button. You'll be presented with a modal. If you wish to refund only the most recent payment, check the box. This only implements refunds for Subscriptions, not One Time Payments. One Time Payments will still need to be handled manually at this time.
This commit is contained in:
parent
b950926538
commit
b92627677f
|
@ -26,7 +26,9 @@ module DiscourseSubscriptions
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
params.require(:id)
|
||||||
begin
|
begin
|
||||||
|
refund_subscription(params[:id]) if params[:refund]
|
||||||
subscription = ::Stripe::Subscription.delete(params[:id])
|
subscription = ::Stripe::Subscription.delete(params[:id])
|
||||||
|
|
||||||
customer = Customer.find_by(
|
customer = Customer.find_by(
|
||||||
|
@ -49,6 +51,18 @@ module DiscourseSubscriptions
|
||||||
render_json_error e.message
|
render_json_error e.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# this will only refund the most recent subscription payment
|
||||||
|
def refund_subscription(subscription_id)
|
||||||
|
subscription = ::Stripe::Subscription.retrieve(subscription_id)
|
||||||
|
invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[:latest_invoice]
|
||||||
|
payment_intent = invoice[:payment_intent] if invoice[:payment_intent]
|
||||||
|
refund = ::Stripe::Refund.create({
|
||||||
|
payment_intent: payment_intent,
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
export default Controller.extend({});
|
export default Controller.extend({
|
||||||
|
actions: {
|
||||||
|
showCancelModal(subscription) {
|
||||||
|
showModal("admin-cancel-subscription", {
|
||||||
|
model: subscription,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -19,9 +19,13 @@ const AdminSubscription = EmberObject.extend({
|
||||||
return getURL(`/admin/users/${metadata.user_id}/${metadata.username}`);
|
return getURL(`/admin/users/${metadata.user_id}/${metadata.username}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy() {
|
destroy(refund) {
|
||||||
|
const data = {
|
||||||
|
refund: refund,
|
||||||
|
};
|
||||||
return ajax(`/s/admin/subscriptions/${this.id}`, {
|
return ajax(`/s/admin/subscriptions/${this.id}`, {
|
||||||
method: "delete",
|
method: "delete",
|
||||||
|
data,
|
||||||
}).then((result) => AdminSubscription.create(result));
|
}).then((result) => AdminSubscription.create(result));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import I18n from "I18n";
|
||||||
import Route from "@ember/routing/route";
|
import Route from "@ember/routing/route";
|
||||||
import AdminSubscription from "discourse/plugins/discourse-subscriptions/discourse/models/admin-subscription";
|
import AdminSubscription from "discourse/plugins/discourse-subscriptions/discourse/models/admin-subscription";
|
||||||
import I18n from "I18n";
|
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
model() {
|
model() {
|
||||||
|
@ -8,29 +8,24 @@ export default Route.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
cancelSubscription(subscription) {
|
cancelSubscription(model) {
|
||||||
bootbox.confirm(
|
const subscription = model.subscription;
|
||||||
I18n.t(
|
const refund = model.refund;
|
||||||
"discourse_subscriptions.user.subscriptions.operations.destroy.confirm"
|
|
||||||
),
|
|
||||||
I18n.t("no_value"),
|
|
||||||
I18n.t("yes_value"),
|
|
||||||
(confirmed) => {
|
|
||||||
if (confirmed) {
|
|
||||||
subscription.set("loading", true);
|
subscription.set("loading", true);
|
||||||
|
|
||||||
subscription
|
subscription
|
||||||
.destroy()
|
.destroy(refund)
|
||||||
.then((result) => subscription.set("status", result.status))
|
.then((result) => {
|
||||||
|
subscription.set("status", result.status);
|
||||||
|
this.send("closeModal");
|
||||||
|
bootbox.alert(I18n.t("discourse_subscriptions.admin.canceled"));
|
||||||
|
})
|
||||||
.catch((data) =>
|
.catch((data) =>
|
||||||
bootbox.alert(data.jqXHR.responseJSON.errors.join("\n"))
|
bootbox.alert(data.jqXHR.responseJSON.errors.join("\n"))
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
subscription.set("loading", false);
|
subscription.set("loading", false);
|
||||||
|
this.refresh();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
{{#if subscription.loading}}
|
{{#if subscription.loading}}
|
||||||
{{loading-spinner size="small"}}
|
{{loading-spinner size="small"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{d-button disabled=subscription.canceled label="cancel" action=(route-action "cancelSubscription" subscription) icon="times"}}
|
{{d-button disabled=subscription.canceled label="cancel" action=(action "showCancelModal" subscription) icon="times"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div>
|
||||||
|
{{#d-modal-body rawTitle=(i18n "discourse_subscriptions.user.subscriptions.operations.destroy.confirm")}}
|
||||||
|
{{input type="checkbox" checked=refund}}
|
||||||
|
{{i18n "discourse_subscriptions.admin.ask_refund"}}
|
||||||
|
{{/d-modal-body}}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
{{#if model.loading}}
|
||||||
|
{{loading-spinner}}
|
||||||
|
{{else}}
|
||||||
|
{{d-button
|
||||||
|
label="yes_value"
|
||||||
|
action=(route-action "cancelSubscription" (hash subscription=model refund=refund))
|
||||||
|
icon="times"
|
||||||
|
class="btn-danger"
|
||||||
|
}}
|
||||||
|
{{d-button label="no_value" action=(route-action "closeModal")}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -148,3 +148,5 @@ en:
|
||||||
plan: Plan
|
plan: Plan
|
||||||
status: Status
|
status: Status
|
||||||
created_at: Created
|
created_at: Created
|
||||||
|
ask_refund: Refund the last payment made by the customer?
|
||||||
|
canceled: "The subscription is canceled."
|
||||||
|
|
|
@ -99,6 +99,21 @@ module DiscourseSubscriptions
|
||||||
delete "/s/admin/subscriptions/sub_12345.json"
|
delete "/s/admin/subscriptions/sub_12345.json"
|
||||||
}.not_to change { user.groups.count }
|
}.not_to change { user.groups.count }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "refunds if params[:refund] present" do
|
||||||
|
::Stripe::Subscription
|
||||||
|
.expects(:delete)
|
||||||
|
.with('sub_12345')
|
||||||
|
.returns(
|
||||||
|
plan: { product: 'pr_34578' },
|
||||||
|
customer: 'c_123'
|
||||||
|
)
|
||||||
|
::Stripe::Subscription.expects(:retrieve).with('sub_12345').returns(latest_invoice: 'in_123')
|
||||||
|
::Stripe::Invoice.expects(:retrieve).with('in_123').returns(payment_intent: 'pi_123')
|
||||||
|
::Stripe::Refund.expects(:create).with(payment_intent: 'pi_123')
|
||||||
|
|
||||||
|
delete "/s/admin/subscriptions/sub_12345.json", params: { refund: true }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue