1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-17 06:52:27 +01:00

allow payment with stripe-modal react component

This commit is contained in:
Sylvain 2020-12-02 10:06:18 +01:00
parent 4ca2299776
commit 6c39191efa
7 changed files with 69 additions and 40 deletions

View File

@ -14,7 +14,7 @@ class API::PricesController < API::ApiController
@prices = @prices.where(priceable_id: params[:priceable_id]) if params[:priceable_id]
end
if params[:plan_id]
plan_id = if params[:plan_id] =~ /no|nil|null|undefined/i
plan_id = if /no|nil|null|undefined/i.match?(params[:plan_id])
nil
else
params[:plan_id]
@ -37,7 +37,11 @@ class API::PricesController < API::ApiController
end
def compute
price_parameters = compute_price_params
price_parameters = if params[:reservation]
compute_reservation_price_params
elsif params[:subscription]
compute_subscription_price_params
end
# user
user = User.find(price_parameters[:user_id])
# reservable
@ -74,12 +78,16 @@ class API::PricesController < API::ApiController
params.require(:price).permit(:amount)
end
def compute_price_params
def compute_reservation_price_params
params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places, :payment_schedule,
tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered])
end
def compute_subscription_price_params
params.require(:subscription).permit(:plan_id, :user_id, :payment_schedule)
end
def coupon_params
params.permit(:coupon_code)
end

View File

@ -4,7 +4,7 @@ import { CartItems, PaymentConfirmation } from '../models/payment';
export default class PaymentAPI {
static async confirm (stp_payment_method_id: string, cart_items: CartItems): Promise<PaymentConfirmation> {
const res: AxiosResponse = await apiClient.post(`/api/payment/confirm`, {
const res: AxiosResponse = await apiClient.post(`/api/payments/confirm_payment`, {
payment_method_id: stp_payment_method_id,
cart_items
});

View File

@ -1,18 +1,12 @@
import apiClient from './api-client';
import { AxiosResponse } from 'axios';
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
import { CartItems } from '../models/payment';
import { ComputePriceResult } from '../models/price';
export default class PriceAPI {
async compute (cartItems: CartItems): Promise<ComputePriceResult> {
static async compute (cartItems: CartItems): Promise<ComputePriceResult> {
const res: AxiosResponse = await apiClient.post(`/api/prices/compute`, cartItems);
return res?.data?.custom_asset;
}
static compute (cartItems: CartItems): IWrapPromise<ComputePriceResult> {
const api = new PriceAPI();
return wrapPromise(api.compute(cartItems));
return res?.data;
}
}

View File

@ -4,14 +4,9 @@ import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
import { Wallet } from '../models/wallet';
export default class WalletAPI {
async getByUser (user_id: number): Promise<Wallet> {
static async getByUser (user_id: number): Promise<Wallet> {
const res: AxiosResponse = await apiClient.get(`/api/wallet/by_user/${user_id}`);
return res?.data;
}
static getByUser (user_id: number): IWrapPromise<Wallet> {
const api = new WalletAPI();
return wrapPromise(api.getByUser(user_id));
}
}

View File

@ -3,7 +3,7 @@
* Supports Strong-Customer Authentication (SCA).
*/
import React, { ChangeEvent, ReactNode, useEffect, useState } from 'react';
import React, { ReactNode, useEffect, useState } from 'react';
import { react2angular } from 'react2angular';
import { Loader } from './loader';
import { IApplication } from '../models/application';
@ -43,27 +43,43 @@ interface StripeModalProps {
const cgvFile = CustomAssetAPI.get(CustomAssetName.CgvFile);
const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule , processPayment = true }) => {
// customer's wallet
const [wallet, setWallet] = useState(null);
// server-computed price with all details
const [price, setPrice] = useState(null);
// remaining price = total price - wallet amount
const [remainingPrice, setRemainingPrice] = useState(0);
const userWallet = WalletAPI.getByUser(cartItems.reservation?.user_id || cartItems.subscription?.user_id);
const priceInfo = PriceAPI.compute(cartItems);
const { t } = useTranslation('shared');
const cgv = cgvFile.read();
const wallet = userWallet.read();
const price = priceInfo.read();
// is the component ready to display?
const [ready, setReady] = useState(false);
// errors to display in the UI (stripe errors mainly)
const [errors, setErrors] = useState(null);
// are we currently processing the payment (ie. the form was submit, but the process is still running)?
const [submitState, setSubmitState] = useState(false);
// did the user accepts the terms of services (CGV)?
const [tos, setTos] = useState(false);
const { t } = useTranslation('shared');
const cgv = cgvFile.read();
/**
* Refresh the remaining price on each display
* On each display:
* - Refresh the wallet
* - Refresh the price
* - Refresh the remaining price
*/
useEffect(() => {
const wLib = new WalletLib(wallet);
setRemainingPrice(wLib.computeRemainingPrice(price.price));
})
if (!cartItems) return;
WalletAPI.getByUser(cartItems.reservation?.user_id || cartItems.subscription?.user_id).then((wallet) => {
setWallet(wallet);
PriceAPI.compute(cartItems).then((res) => {
setPrice(res);
const wLib = new WalletLib(wallet);
setRemainingPrice(wLib.computeRemainingPrice(res.price));
setReady(true);
})
})
}, [cartItems]);
/**
* Check if there is currently an error to display
@ -82,7 +98,7 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
/**
* Triggered when the user accepts or declines the Terms of Sales
*/
const toggleTos = (event: ChangeEvent): void => {
const toggleTos = (): void => {
setTos(!tos);
}
@ -114,6 +130,9 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
);
}
/**
* Set the component as 'currently submitting'
*/
const handleSubmit = (): void => {
setSubmitState(true);
}
@ -153,8 +172,8 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
closeButton={false}
customFooter={logoFooter()}
className="stripe-modal">
<WalletInfo reservation={cartItems.reservation} currentUser={currentUser} wallet={wallet} price={price.price} />
<StripeElements>
{ready && <StripeElements>
<WalletInfo reservation={cartItems?.reservation} currentUser={currentUser} wallet={wallet} price={price?.price} />
<StripeForm onSubmit={handleSubmit}
onSuccess={handleFormSuccess}
onError={handleFormError}
@ -183,7 +202,7 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
className="validate-btn">
{t('app.shared.stripe.confirm_payment_of_', { AMOUNT: formatPrice(remainingPrice) })}
</button>
</StripeElements>
</StripeElements>}
</FabModal>
);
}

View File

@ -79,6 +79,9 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
cartItems: null
};
// currently logged-in user
$scope.currentUser = $rootScope.currentUser;
/**
* Add the provided slot to the shopping cart (state transition from free to 'about to be reserved')
* and increment the total amount of the cart if needed.
@ -312,9 +315,12 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
/**
* This will open/close the stripe payment modal
*/
$scope.toggleStripeModal = () => {
$scope.toggleStripeModal = (beforeApply) => {
setTimeout(() => {
$scope.stripe.showModal = !$scope.stripe.showModal;
if (typeof beforeApply === 'function') {
beforeApply();
}
$scope.$apply();
}, 50);
};
@ -693,8 +699,15 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
* Open a modal window that allows the user to process a credit card payment for his current shopping cart.
*/
const payByStripe = function (reservation) {
$scope.stripe.cartItems = mkRequestParams({ reservation }, $scope.coupon.applied);
$scope.toggleStripeModal();
$scope.toggleStripeModal(() => {
let request = { reservation };
if (reservation.slots_attributes.length === 0 && reservation.plan_id) {
request = mkSubscription($scope.selectedPlan.id, reservation.user_id, $scope.schedule.requested_schedule, 'stripe');
} else {
request.reservation.payment_method = 'stripe';
}
$scope.stripe.cartItems = mkRequestParams(request, $scope.coupon.applied);
});
};
/**
* Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).

View File

@ -203,7 +203,7 @@
<stripe-modal is-open="stripe.showModal"
toggle-modal="toggleStripeModal"
after-success="afterStripeSuccess"
cartItems="stripe.cartItems"
cart-items="stripe.cartItems"
current-user="currentUser"
schedule="schedule.payment_schedule"/>
</div>