mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-11 05:54:15 +01:00
WIP: using <stripe-modal> in cart directive
This commit is contained in:
parent
9813c5d27b
commit
4ca2299776
14
app/frontend/src/javascript/api/payment.ts
Normal file
14
app/frontend/src/javascript/api/payment.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import apiClient from './api-client';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
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`, {
|
||||||
|
payment_method_id: stp_payment_method_id,
|
||||||
|
cart_items
|
||||||
|
});
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
18
app/frontend/src/javascript/api/price.ts
Normal file
18
app/frontend/src/javascript/api/price.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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> {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
17
app/frontend/src/javascript/api/wallet.ts
Normal file
17
app/frontend/src/javascript/api/wallet.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import apiClient from './api-client';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
|
||||||
|
import { Wallet } from '../models/wallet';
|
||||||
|
|
||||||
|
export default class WalletAPI {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,26 @@
|
|||||||
import React, { FormEvent } from 'react';
|
import React, { FormEvent } from 'react';
|
||||||
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js';
|
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js';
|
||||||
import { PaymentMethod } from "@stripe/stripe-js";
|
import { PaymentMethod } from "@stripe/stripe-js";
|
||||||
|
import PaymentAPI from '../api/payment';
|
||||||
|
import { CartItems, PaymentConfirmation } from '../models/payment';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface StripeFormProps {
|
interface StripeFormProps {
|
||||||
onSubmit: () => void,
|
onSubmit: () => void,
|
||||||
onSuccess: (paymentMethod: PaymentMethod) => void,
|
onSuccess: (paymentMethod: PaymentMethod) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
className?: string,
|
className?: string,
|
||||||
|
processPayment?: boolean,
|
||||||
|
cartItems?: CartItems
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A form component to collect the credit card details and to create the payment method on Stripe.
|
* A form component to collect the credit card details and to create the payment method on Stripe.
|
||||||
* The form validation button must be created elsewhere, using the attribute form="stripe-form".
|
* The form validation button must be created elsewhere, using the attribute form="stripe-form".
|
||||||
*/
|
*/
|
||||||
export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onError, children, className }) => {
|
export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onError, children, className, processPayment , cartItems}) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('shared');
|
||||||
|
|
||||||
const stripe = useStripe();
|
const stripe = useStripe();
|
||||||
const elements = useElements();
|
const elements = useElements();
|
||||||
@ -37,11 +44,49 @@ export const StripeForm: React.FC<StripeFormProps> = ({ onSubmit, onSuccess, onE
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
onError(error.message);
|
onError(error.message);
|
||||||
|
} else {
|
||||||
|
if (processPayment) {
|
||||||
|
// process the full payment pipeline, including SCA validation
|
||||||
|
const res = await PaymentAPI.confirm(paymentMethod.id, cartItems);
|
||||||
|
await handleServerConfirmation(res, paymentMethod);
|
||||||
|
} else {
|
||||||
|
// we don't want to process the payment, only return the payment method
|
||||||
|
onSuccess(paymentMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the server response about the Strong-customer authentication (SCA)
|
||||||
|
*/
|
||||||
|
const handleServerConfirmation = async (response: PaymentConfirmation, paymentMethod: PaymentMethod) => {
|
||||||
|
if (response.error) {
|
||||||
|
if (response.error.statusText) {
|
||||||
|
onError(response.error.statusText);
|
||||||
|
} else {
|
||||||
|
onError(`${t('app.shared.messages.payment_card_error')} ${response.error}`);
|
||||||
|
}
|
||||||
|
} else if (response.requires_action) {
|
||||||
|
// Use Stripe.js to handle required card action
|
||||||
|
const result = await stripe.handleCardAction(response.payment_intent_client_secret);
|
||||||
|
if (result.error) {
|
||||||
|
onError(result.error.message);
|
||||||
|
} else {
|
||||||
|
// The card action has been handled
|
||||||
|
// The PaymentIntent can be confirmed again on the server
|
||||||
|
try {
|
||||||
|
const confirmation = await PaymentAPI.confirm(result.paymentIntent.id, cartItems);
|
||||||
|
await handleServerConfirmation(confirmation, paymentMethod);
|
||||||
|
} catch (e) {
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onSuccess(paymentMethod);
|
onSuccess(paymentMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for the Stripe's card input
|
* Options for the Stripe's card input
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* This component enables the user to input his card data.
|
* This component enables the user to input his card data or process payments.
|
||||||
|
* Supports Strong-Customer Authentication (SCA).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ChangeEvent, ReactNode, useEffect, useState } from 'react';
|
import React, { ChangeEvent, ReactNode, useEffect, useState } from 'react';
|
||||||
@ -11,9 +12,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { FabModal, ModalSize } from './fab-modal';
|
import { FabModal, ModalSize } from './fab-modal';
|
||||||
import { PaymentMethod } from '@stripe/stripe-js';
|
import { PaymentMethod } from '@stripe/stripe-js';
|
||||||
import { WalletInfo } from './wallet-info';
|
import { WalletInfo } from './wallet-info';
|
||||||
import { Reservation } from '../models/reservation';
|
|
||||||
import { User } from '../models/user';
|
import { User } from '../models/user';
|
||||||
import { Wallet } from '../models/wallet';
|
|
||||||
import CustomAssetAPI from '../api/custom-asset';
|
import CustomAssetAPI from '../api/custom-asset';
|
||||||
import { CustomAssetName } from '../models/custom-asset';
|
import { CustomAssetName } from '../models/custom-asset';
|
||||||
import { PaymentSchedule } from '../models/payment-schedule';
|
import { PaymentSchedule } from '../models/payment-schedule';
|
||||||
@ -24,6 +23,9 @@ import { StripeForm } from './stripe-form';
|
|||||||
import stripeLogo from '../../../images/powered_by_stripe.png';
|
import stripeLogo from '../../../images/powered_by_stripe.png';
|
||||||
import mastercardLogo from '../../../images/mastercard.png';
|
import mastercardLogo from '../../../images/mastercard.png';
|
||||||
import visaLogo from '../../../images/visa.png';
|
import visaLogo from '../../../images/visa.png';
|
||||||
|
import { CartItems } from '../models/payment';
|
||||||
|
import WalletAPI from '../api/wallet';
|
||||||
|
import PriceAPI from '../api/price';
|
||||||
|
|
||||||
declare var Application: IApplication;
|
declare var Application: IApplication;
|
||||||
declare var Fablab: IFablab;
|
declare var Fablab: IFablab;
|
||||||
@ -32,34 +34,37 @@ interface StripeModalProps {
|
|||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
toggleModal: () => void,
|
toggleModal: () => void,
|
||||||
afterSuccess: (paymentMethod: PaymentMethod) => void,
|
afterSuccess: (paymentMethod: PaymentMethod) => void,
|
||||||
reservation: Reservation,
|
cartItems: CartItems,
|
||||||
currentUser: User,
|
currentUser: User,
|
||||||
wallet: Wallet,
|
schedule: PaymentSchedule,
|
||||||
price: number,
|
processPayment?: boolean,
|
||||||
schedule: PaymentSchedule
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cgvFile = CustomAssetAPI.get(CustomAssetName.CgvFile);
|
const cgvFile = CustomAssetAPI.get(CustomAssetName.CgvFile);
|
||||||
|
|
||||||
const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, reservation, currentUser, wallet, price, schedule }) => {
|
const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, cartItems, currentUser, schedule , processPayment = true }) => {
|
||||||
const [remainingPrice, setRemainingPrice] = useState(0);
|
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();
|
||||||
|
|
||||||
|
const [errors, setErrors] = useState(null);
|
||||||
|
const [submitState, setSubmitState] = useState(false);
|
||||||
|
const [tos, setTos] = useState(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the remaining price on each display
|
* Refresh the remaining price on each display
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const wLib = new WalletLib(wallet);
|
const wLib = new WalletLib(wallet);
|
||||||
setRemainingPrice(wLib.computeRemainingPrice(price));
|
setRemainingPrice(wLib.computeRemainingPrice(price.price));
|
||||||
})
|
})
|
||||||
|
|
||||||
const { t } = useTranslation('shared');
|
|
||||||
|
|
||||||
const cgv = cgvFile.read();
|
|
||||||
|
|
||||||
const [errors, setErrors] = useState(null);
|
|
||||||
const [submitState, setSubmitState] = useState(false);
|
|
||||||
const [tos, setTos] = useState(false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if there is currently an error to display
|
* Check if there is currently an error to display
|
||||||
*/
|
*/
|
||||||
@ -68,12 +73,15 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the Terms of Sales document is set
|
* Check if the user accepts the Terms of Sales document
|
||||||
*/
|
*/
|
||||||
const hasCgv = (): boolean => {
|
const hasCgv = (): boolean => {
|
||||||
return cgv != null;
|
return cgv != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when the user accepts or declines the Terms of Sales
|
||||||
|
*/
|
||||||
const toggleTos = (event: ChangeEvent): void => {
|
const toggleTos = (event: ChangeEvent): void => {
|
||||||
setTos(!tos);
|
setTos(!tos);
|
||||||
}
|
}
|
||||||
@ -110,16 +118,32 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
|||||||
setSubmitState(true);
|
setSubmitState(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFormSuccess = (paymentMethod: PaymentMethod): void => {
|
/**
|
||||||
|
* After sending the form with success, process the resulting payment method
|
||||||
|
*/
|
||||||
|
const handleFormSuccess = async (paymentMethod: PaymentMethod): Promise<void> => {
|
||||||
setSubmitState(false);
|
setSubmitState(false);
|
||||||
afterSuccess(paymentMethod);
|
afterSuccess(paymentMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When stripe-form raise an error, it is handled by this callback which display it in the modal.
|
||||||
|
*/
|
||||||
const handleFormError = (message: string): void => {
|
const handleFormError = (message: string): void => {
|
||||||
setSubmitState(false);
|
setSubmitState(false);
|
||||||
setErrors(message);
|
setErrors(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the form can be submitted.
|
||||||
|
* => We're not currently already submitting the form, and, if the terms of service are enabled, the user agrees with them.
|
||||||
|
*/
|
||||||
|
const canSubmit = (): boolean => {
|
||||||
|
let terms = true;
|
||||||
|
if (hasCgv()) { terms = tos; }
|
||||||
|
return !submitState && terms;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FabModal title={t('app.shared.stripe.online_payment')}
|
<FabModal title={t('app.shared.stripe.online_payment')}
|
||||||
@ -129,9 +153,14 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
|||||||
closeButton={false}
|
closeButton={false}
|
||||||
customFooter={logoFooter()}
|
customFooter={logoFooter()}
|
||||||
className="stripe-modal">
|
className="stripe-modal">
|
||||||
<WalletInfo reservation={reservation} currentUser={currentUser} wallet={wallet} price={price} />
|
<WalletInfo reservation={cartItems.reservation} currentUser={currentUser} wallet={wallet} price={price.price} />
|
||||||
<StripeElements>
|
<StripeElements>
|
||||||
<StripeForm onSubmit={handleSubmit} onSuccess={handleFormSuccess} onError={handleFormError} className="stripe-form">
|
<StripeForm onSubmit={handleSubmit}
|
||||||
|
onSuccess={handleFormSuccess}
|
||||||
|
onError={handleFormError}
|
||||||
|
className="stripe-form"
|
||||||
|
cartItems={cartItems}
|
||||||
|
processPayment={processPayment}>
|
||||||
{hasErrors() && <div className="stripe-errors">
|
{hasErrors() && <div className="stripe-errors">
|
||||||
{errors}
|
{errors}
|
||||||
</div>}
|
</div>}
|
||||||
@ -149,7 +178,7 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
|||||||
</div>}
|
</div>}
|
||||||
</StripeForm>
|
</StripeForm>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
disabled={submitState}
|
disabled={!canSubmit()}
|
||||||
form="stripe-form"
|
form="stripe-form"
|
||||||
className="validate-btn">
|
className="validate-btn">
|
||||||
{t('app.shared.stripe.confirm_payment_of_', { AMOUNT: formatPrice(remainingPrice) })}
|
{t('app.shared.stripe.confirm_payment_of_', { AMOUNT: formatPrice(remainingPrice) })}
|
||||||
@ -159,12 +188,12 @@ const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuc
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const StripeModalWrapper: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, reservation, currentUser, wallet, price, schedule }) => {
|
const StripeModalWrapper: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, currentUser, schedule , cartItems, processPayment}) => {
|
||||||
return (
|
return (
|
||||||
<Loader>
|
<Loader>
|
||||||
<StripeModal isOpen={isOpen} toggleModal={toggleModal} afterSuccess={afterSuccess} reservation={reservation} currentUser={currentUser} wallet={wallet} price={price} schedule={schedule} />
|
<StripeModal isOpen={isOpen} toggleModal={toggleModal} afterSuccess={afterSuccess} currentUser={currentUser} schedule={schedule} processPayment={processPayment} cartItems={cartItems}/>
|
||||||
</Loader>
|
</Loader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Application.Components.component('stripeModal', react2angular(StripeModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess', 'reservation', 'currentUser', 'wallet', 'price', 'schedule']));
|
Application.Components.component('stripeModal', react2angular(StripeModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess','currentUser', 'schedule', 'cartItems', 'processPayment']));
|
||||||
|
@ -73,6 +73,12 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
|||||||
payment_schedule: null // the effective computed payment schedule
|
payment_schedule: null // the effective computed payment schedule
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// online payments (stripe)
|
||||||
|
$scope.stripe = {
|
||||||
|
showModal: false,
|
||||||
|
cartItems: null
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the provided slot to the shopping cart (state transition from free to 'about to be reserved')
|
* 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.
|
* and increment the total amount of the cart if needed.
|
||||||
@ -303,6 +309,24 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
|||||||
}, 50);
|
}, 50);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will open/close the stripe payment modal
|
||||||
|
*/
|
||||||
|
$scope.toggleStripeModal = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
$scope.stripe.showModal = !$scope.stripe.showModal;
|
||||||
|
$scope.$apply();
|
||||||
|
}, 50);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked atfer a successful Stripe payment
|
||||||
|
*/
|
||||||
|
$scope.afterStripeSuccess = () => {
|
||||||
|
$scope.toggleStripeModal();
|
||||||
|
afterPayment($scope.reservation);
|
||||||
|
};
|
||||||
|
|
||||||
/* PRIVATE SCOPE */
|
/* PRIVATE SCOPE */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -669,77 +693,8 @@ 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.
|
* Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||||
*/
|
*/
|
||||||
const payByStripe = function (reservation) {
|
const payByStripe = function (reservation) {
|
||||||
$uibModal.open({
|
$scope.stripe.cartItems = mkRequestParams({ reservation }, $scope.coupon.applied);
|
||||||
templateUrl: '/stripe/payment_modal.html',
|
$scope.toggleStripeModal();
|
||||||
size: 'md',
|
|
||||||
resolve: {
|
|
||||||
reservation () {
|
|
||||||
return reservation;
|
|
||||||
},
|
|
||||||
price () {
|
|
||||||
return Price.compute(mkRequestParams({ reservation }, $scope.coupon.applied)).$promise;
|
|
||||||
},
|
|
||||||
wallet () {
|
|
||||||
return Wallet.getWalletByUser({ user_id: reservation.user_id }).$promise;
|
|
||||||
},
|
|
||||||
cgv () {
|
|
||||||
return CustomAsset.get({ name: 'cgv-file' }).$promise;
|
|
||||||
},
|
|
||||||
coupon () {
|
|
||||||
return $scope.coupon.applied;
|
|
||||||
},
|
|
||||||
cartItems () {
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
return mkRequestParams(request, $scope.coupon.applied);
|
|
||||||
},
|
|
||||||
schedule () {
|
|
||||||
return $scope.schedule.payment_schedule;
|
|
||||||
},
|
|
||||||
stripeKey: ['Setting', function (Setting) { return Setting.get({ name: 'stripe_public_key' }).$promise; }]
|
|
||||||
},
|
|
||||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems', 'stripeKey', 'schedule',
|
|
||||||
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon, cartItems, stripeKey, schedule) {
|
|
||||||
// user wallet amount
|
|
||||||
$scope.wallet = wallet;
|
|
||||||
|
|
||||||
// Global price (total of all items)
|
|
||||||
$scope.price = price.price;
|
|
||||||
|
|
||||||
// Price
|
|
||||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
|
|
||||||
|
|
||||||
// Cart items
|
|
||||||
$scope.cartItems = cartItems;
|
|
||||||
|
|
||||||
// CGV
|
|
||||||
$scope.cgv = cgv.custom_asset;
|
|
||||||
|
|
||||||
// Reservation
|
|
||||||
$scope.reservation = reservation;
|
|
||||||
|
|
||||||
// Used in wallet info template to interpolate some translations
|
|
||||||
$scope.numberFilter = $filter('number');
|
|
||||||
|
|
||||||
// stripe publishable key
|
|
||||||
$scope.stripeKey = stripeKey.setting.value;
|
|
||||||
|
|
||||||
// Shows the schedule info in the modal
|
|
||||||
$scope.schedule = schedule;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback to handle the post-payment and reservation
|
|
||||||
*/
|
|
||||||
$scope.onPaymentSuccess = function (response) {
|
|
||||||
$uibModalInstance.close(response);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}).result.finally(null).then(function (reservation) { afterPayment(reservation); });
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
* Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||||
@ -755,6 +710,9 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
|||||||
price () {
|
price () {
|
||||||
return Price.compute(mkRequestParams({ reservation }, $scope.coupon.applied)).$promise;
|
return Price.compute(mkRequestParams({ reservation }, $scope.coupon.applied)).$promise;
|
||||||
},
|
},
|
||||||
|
cartItems () {
|
||||||
|
return mkRequestParams({ reservation }, $scope.coupon.applied);
|
||||||
|
},
|
||||||
wallet () {
|
wallet () {
|
||||||
return Wallet.getWalletByUser({ user_id: reservation.user_id }).$promise;
|
return Wallet.getWalletByUser({ user_id: reservation.user_id }).$promise;
|
||||||
},
|
},
|
||||||
@ -768,8 +726,8 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
|||||||
return $scope.schedule;
|
return $scope.schedule;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'Subscription', 'wallet', 'helpers', '$filter', 'coupon', 'selectedPlan', 'schedule',
|
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'Subscription', 'wallet', 'helpers', '$filter', 'coupon', 'selectedPlan', 'schedule', 'cartItems',
|
||||||
function ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, Subscription, wallet, helpers, $filter, coupon, selectedPlan, schedule) {
|
function ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, Subscription, wallet, helpers, $filter, coupon, selectedPlan, schedule, cartItems) {
|
||||||
// user wallet amount
|
// user wallet amount
|
||||||
$scope.wallet = wallet;
|
$scope.wallet = wallet;
|
||||||
|
|
||||||
@ -779,8 +737,9 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
|||||||
// Price to pay (wallet deducted)
|
// Price to pay (wallet deducted)
|
||||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
|
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
|
||||||
|
|
||||||
// Reservation
|
// Reservation (simple & cartItems format)
|
||||||
$scope.reservation = reservation;
|
$scope.reservation = reservation;
|
||||||
|
$scope.cartItems = cartItems;
|
||||||
|
|
||||||
// Subscription
|
// Subscription
|
||||||
$scope.plan = selectedPlan;
|
$scope.plan = selectedPlan;
|
||||||
|
26
app/frontend/src/javascript/models/payment.ts
Normal file
26
app/frontend/src/javascript/models/payment.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Reservation } from './reservation';
|
||||||
|
|
||||||
|
export interface PaymentConfirmation {
|
||||||
|
requires_action?: boolean,
|
||||||
|
payment_intent_client_secret?: string,
|
||||||
|
success?: boolean,
|
||||||
|
error?: {
|
||||||
|
statusText: string
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PaymentMethod {
|
||||||
|
Stripe = 'stripe',
|
||||||
|
Other = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CartItems {
|
||||||
|
reservation: Reservation,
|
||||||
|
subscription: {
|
||||||
|
plan_id: number,
|
||||||
|
user_id: number,
|
||||||
|
payment_schedule: boolean,
|
||||||
|
payment_method: PaymentMethod
|
||||||
|
},
|
||||||
|
coupon_code: string
|
||||||
|
}
|
@ -1,8 +1,27 @@
|
|||||||
export interface Price {
|
export interface Price {
|
||||||
id: number,
|
id: number,
|
||||||
group_id: number,
|
group_id: number,
|
||||||
plan_id: number,
|
plan_id: number,
|
||||||
priceable_type: string,
|
priceable_type: string,
|
||||||
priceable_id: number,
|
priceable_id: number,
|
||||||
amount: number
|
amount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputePriceResult {
|
||||||
|
price: number,
|
||||||
|
price_without_coupon: number,
|
||||||
|
details?: {
|
||||||
|
slots: Array<{
|
||||||
|
start_at: Date,
|
||||||
|
price: number,
|
||||||
|
promo: boolean
|
||||||
|
}>
|
||||||
|
plan?: number
|
||||||
|
},
|
||||||
|
schedule?: {
|
||||||
|
items: Array<{
|
||||||
|
amount: number,
|
||||||
|
due_date: Date
|
||||||
|
}>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export interface ReservationSlot {
|
export interface ReservationSlot {
|
||||||
|
id?: number,
|
||||||
start_at: Date,
|
start_at: Date,
|
||||||
end_at: Date,
|
end_at: Date,
|
||||||
availability_id: number,
|
availability_id: number,
|
||||||
@ -10,6 +11,11 @@ export interface Reservation {
|
|||||||
reservable_id: number,
|
reservable_id: number,
|
||||||
reservable_type: string,
|
reservable_type: string,
|
||||||
slots_attributes: Array<ReservationSlot>,
|
slots_attributes: Array<ReservationSlot>,
|
||||||
plan_id: number
|
plan_id?: number,
|
||||||
payment_schedule: boolean
|
nb_reserve_places?: number,
|
||||||
|
payment_schedule?: boolean,
|
||||||
|
tickets_attributes?: {
|
||||||
|
event_price_category_id: number,
|
||||||
|
booked: boolean,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -199,3 +199,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div ng-if="stripe.showModal">
|
||||||
|
<stripe-modal is-open="stripe.showModal"
|
||||||
|
toggle-modal="toggleStripeModal"
|
||||||
|
after-success="afterStripeSuccess"
|
||||||
|
cartItems="stripe.cartItems"
|
||||||
|
current-user="currentUser"
|
||||||
|
schedule="schedule.payment_schedule"/>
|
||||||
|
</div>
|
||||||
|
@ -52,9 +52,8 @@
|
|||||||
<stripe-modal is-open="isOpenStripeModal"
|
<stripe-modal is-open="isOpenStripeModal"
|
||||||
toggle-modal="toggleStripeModal"
|
toggle-modal="toggleStripeModal"
|
||||||
after-success="afterCreatePaymentMethod"
|
after-success="afterCreatePaymentMethod"
|
||||||
reservation="reservation"
|
cart-items="cartItems"
|
||||||
current-user="currentUser"
|
current-user="currentUser"
|
||||||
wallet="wallet"
|
schedule="schedule"
|
||||||
price="price"
|
processPayment="false"/>
|
||||||
schedule="schedule" />
|
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user