1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

stripe card update + cancel subscription

This commit is contained in:
Sylvain 2021-02-09 15:44:56 +01:00
parent b0ef9e097d
commit ff0c69fc58
12 changed files with 94 additions and 6 deletions

View File

@ -3,7 +3,7 @@
# API Controller for resources of PaymentSchedule
class API::PaymentSchedulesController < API::ApiController
before_action :authenticate_user!
before_action :set_payment_schedule, only: %i[download]
before_action :set_payment_schedule, only: %i[download cancel]
before_action :set_payment_schedule_item, only: %i[cash_check refresh_item pay_item]
def list
@ -37,7 +37,7 @@ class API::PaymentSchedulesController < API::ApiController
def refresh_item
authorize @payment_schedule_item.payment_schedule
PaymentScheduleItemWorker.new.perform(params[:id])
PaymentScheduleItemWorker.new.perform(@payment_schedule_item.id)
render json: { state: 'refreshed' }, status: :ok
end
@ -47,15 +47,24 @@ class API::PaymentSchedulesController < API::ApiController
stripe_key = Setting.get('stripe_secret_key')
stp_invoice = Stripe::Invoice.pay(@payment_schedule_item.stp_invoice_id, {}, { api_key: stripe_key })
PaymentScheduleItemWorker.new.perform(@payment_schedule_item.id)
render json: { status: stp_invoice.status }, status: :ok
rescue Stripe::StripeError => e
stripe_key = Setting.get('stripe_secret_key')
stp_invoice = Stripe::Invoice.retrieve(@payment_schedule_item.stp_invoice_id, api_key: stripe_key)
PaymentScheduleItemWorker.new.perform(@payment_schedule_item.id)
render json: { status: stp_invoice.status, error: e }, status: :unprocessable_entity
end
def cancel
authorize @payment_schedule
canceled_at = PaymentScheduleService.cancel(@payment_schedule)
render json: { canceled_at: canceled_at }, status: :ok
end
private
def set_payment_schedule

View File

@ -1,6 +1,7 @@
import apiClient from './api-client';
import { AxiosResponse } from 'axios';
import {
CancelScheduleResponse,
CashCheckResponse, PayItemResponse,
PaymentSchedule,
PaymentScheduleIndexRequest, RefreshItemResponse
@ -28,6 +29,11 @@ export default class PaymentScheduleAPI {
return res?.data;
}
async cancel (paymentScheduleId: number): Promise<CancelScheduleResponse> {
const res: AxiosResponse = await apiClient.put(`/api/payment_schedules/${paymentScheduleId}/cancel`);
return res?.data;
}
static list (query: PaymentScheduleIndexRequest): IWrapPromise<Array<PaymentSchedule>> {
const api = new PaymentScheduleAPI();
return wrapPromise(api.list(query));

View File

@ -41,6 +41,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
const [tempSchedule, setTempSchedule] = useState<PaymentSchedule>(null);
const [canSubmitUpdateCard, setCanSubmitUpdateCard] = useState<boolean>(true);
const [errors, setErrors] = useState<string>(null);
const [showCancelSubscription, setShowCancelSubscription] = useState<boolean>(false);
/**
* Check if the requested payment schedule is displayed with its deadlines (PaymentScheduleItem) or without them
@ -158,6 +159,13 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
{t('app.admin.invoices.schedules_table.update_card')}
</FabButton>
);
case PaymentScheduleItemState.Error:
return (
<FabButton onClick={handleCancelSubscription(schedule)}
icon={<i className="fas fa-times" />}>
{t('app.admin.invoices.schedules_table.cancel_subscription')}
</FabButton>
)
default:
return <span />
}
@ -289,6 +297,34 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
setCanSubmitUpdateCard(true);
}
/**
* Callback triggered when the user clicks on the "cancel subscription" button
*/
const handleCancelSubscription = (schedule: PaymentSchedule): ReactEventHandler => {
return (): void => {
setTempSchedule(schedule);
toggleCancelSubscriptionModal();
}
}
/**
* Show/hide the modal dialog to cancel the current subscription
*/
const toggleCancelSubscriptionModal = (): void => {
setShowCancelSubscription(!showCancelSubscription);
}
/**
* When the user has confirmed the cancellation, we transfer the request to the API
*/
const onCancelSubscriptionConfirmed = (): void => {
const api = new PaymentScheduleAPI();
api.cancel(tempSchedule.id).then(() => {
refreshList();
toggleCancelSubscriptionModal();
});
}
return (
<div>
<table className="schedules-table">
@ -360,6 +396,14 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
})}
</span>}
</FabModal>
<FabModal title={t('app.admin.invoices.schedules_table.cancel_subscription')}
isOpen={showCancelSubscription}
toggleModal={toggleCancelSubscriptionModal}
onConfirm={onCancelSubscriptionConfirmed}
closeButton={true}
confirmButton={t('app.admin.invoices.schedules_table.confirm_button')}>
{t('app.admin.invoices.schedules_table.confirm_cancel_subscription')}
</FabModal>
<StripeElements>
<FabModal title={t('app.admin.invoices.schedules_table.resolve_action')}
isOpen={showResolveAction}

View File

@ -77,3 +77,7 @@ export interface PayItemResponse {
status: 'draft' | 'open' | 'paid' | 'uncollectible' | 'void',
error?: string
}
export interface CancelScheduleResponse {
canceled_at: Date
}

View File

@ -38,7 +38,6 @@ class Subscription < ApplicationRecord
end
def cancel
# TODO, currently unused, refactor to use with PaymentSchedule
update_columns(canceled_at: DateTime.current)
end

View File

@ -315,7 +315,9 @@ class PDF::Invoice < Prawn::Document
# if the invoice was 100% payed with the wallet ...
payment_verbose = I18n.t('invoices.settlement_by_wallet') if total.zero? && wallet_amount
payment_verbose += ' ' + I18n.t('invoices.on_DATE_at_TIME', DATE: I18n.l(invoice.created_at.to_date), TIME:I18n.l(invoice.created_at, format: :hour_minute))
payment_verbose += ' ' + I18n.t('invoices.on_DATE_at_TIME',
DATE: I18n.l(invoice.created_at.to_date),
TIME: I18n.l(invoice.created_at, format: :hour_minute))
if total.positive? || !invoice.wallet_amount
payment_verbose += ' ' + I18n.t('invoices.for_an_amount_of_AMOUNT', AMOUNT: number_to_currency(total))
end

View File

@ -99,7 +99,10 @@ class PDF::PaymentSchedule < Prawn::Document
# payment method
move_down 20
payment_verbose = _t('payment_schedules.settlement_by_METHOD', METHOD: payment_schedule.payment_method)
payment_verbose = I18n.t('payment_schedules.settlement_by_wallet', AMOUNT: payment_schedule.wallet_amount / 100.00) if payment_schedule.wallet_amount
if payment_schedule.wallet_amount
payment_verbose += I18n.t('payment_schedules.settlement_by_wallet',
AMOUNT: number_to_currency(payment_schedule.wallet_amount / 100.00))
end
text payment_verbose
# important information

View File

@ -2,7 +2,7 @@
# Check the access policies for API::PaymentSchedulesController
class PaymentSchedulePolicy < ApplicationPolicy
%w[list? cash_check?].each do |action|
%w[list? cash_check? cancel?].each do |action|
define_method action do
user.admin? || user.manager?
end

View File

@ -138,6 +138,20 @@ class PaymentScheduleService
ps
end
def self.cancel(payment_schedule)
# cancel all item where state != paid
payment_schedule.ordered_items.each do |item|
next if item.state == 'paid'
item.update_attributes(state: 'canceled')
end
# cancel subscription
subscription = Subscription.find(payment_schedule.payment_schedule_items.first.details['subscription_id'])
subscription.cancel
subscription.canceled_at
end
private
##

View File

@ -659,6 +659,7 @@ en:
state_requires_action: "Action required"
state_paid: "Paid"
state_error: "Error"
state_canceled: "Canceled"
method_stripe: "by card"
method_check: "by check"
confirm_payment: "Confirm payment"
@ -670,6 +671,8 @@ en:
resolve_action: "Resolve the action"
ok_button: "OK"
validate_button: "Validate the new card"
cancel_subscription: "Cancel the subscription"
confirm_cancel_subscription: "You're about to cancel this payment schedule and the related subscription. Are you sure?"
document_filters:
reference: "Reference"
customer: "Customer"

View File

@ -659,6 +659,7 @@ fr:
state_requires_action: "Action requise"
state_paid: "Payée"
state_error: "Erreur"
state_canceled: "Annulée"
method_stripe: "par carte"
method_check: "par chèque"
confirm_payment: "Confirmer l'encaissement"
@ -670,6 +671,8 @@ fr:
resolve_action: "Résoudre l'action"
ok_button: "OK"
validate_button: "Valider la nouvelle carte"
cancel_subscription: "Annuler l'abonnement"
confirm_cancel_subscription: "Vous êtes sur le point d'annuler cet échéancier de paiement ainsi que l'abonnement lié. Êtes-vous sur ?"
document_filters:
reference: "Référence"
customer: "Client"

View File

@ -113,6 +113,7 @@ Rails.application.routes.draw do
resources :payment_schedules, only: %i[show] do
post 'list', action: 'list', on: :collection
put 'cancel', on: :member
get 'download', on: :member
post 'items/:id/cash_check', action: 'cash_check', on: :collection
post 'items/:id/refresh_item', action: 'refresh_item', on: :collection