mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-30 19:52:20 +01:00
payment of cart by stripe/payzen/local
This commit is contained in:
parent
5ec541d854
commit
193c21a583
@ -10,7 +10,10 @@ class API::CartController < API::ApiController
|
|||||||
def create
|
def create
|
||||||
authorize :cart, :create?
|
authorize :cart, :create?
|
||||||
@order = Order.find_by(token: order_token)
|
@order = Order.find_by(token: order_token)
|
||||||
@order = Order.find_by(statistic_profile_id: current_user.statistic_profile.id, state: 'cart') if @order.nil? && current_user&.member?
|
if @order.nil? && current_user&.member?
|
||||||
|
@order = Order.where(statistic_profile_id: current_user.statistic_profile.id,
|
||||||
|
state: 'cart').last
|
||||||
|
end
|
||||||
if @order
|
if @order
|
||||||
@order.update(statistic_profile_id: current_user.statistic_profile.id) if @order.statistic_profile_id.nil? && current_user&.member?
|
@order.update(statistic_profile_id: current_user.statistic_profile.id) if @order.statistic_profile_id.nil? && current_user&.member?
|
||||||
@order.update(operator_id: current_user.id) if @order.operator_id.nil? && current_user&.privileged?
|
@order.update(operator_id: current_user.id) if @order.operator_id.nil? && current_user&.privileged?
|
||||||
@ -37,13 +40,15 @@ class API::CartController < API::ApiController
|
|||||||
render 'api/orders/show'
|
render 'api/orders/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_customer
|
||||||
|
authorize @current_order, policy_class: CartPolicy
|
||||||
|
@order = Cart::SetCustomerService.new.call(@current_order, cart_params[:user_id])
|
||||||
|
render 'api/orders/show'
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def orderable
|
def orderable
|
||||||
Product.find(cart_params[:orderable_id])
|
Product.find(cart_params[:orderable_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def cart_params
|
|
||||||
params.permit(:order_token, :orderable_id, :quantity)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -3,4 +3,21 @@
|
|||||||
# API Controller for cart checkout
|
# API Controller for cart checkout
|
||||||
class API::CheckoutController < API::ApiController
|
class API::CheckoutController < API::ApiController
|
||||||
include ::API::OrderConcern
|
include ::API::OrderConcern
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :current_order
|
||||||
|
before_action :ensure_order
|
||||||
|
|
||||||
|
def payment
|
||||||
|
res = Checkout::PaymentService.new.payment(@current_order, current_user, params[:payment_id])
|
||||||
|
render json: res
|
||||||
|
rescue StandardError => e
|
||||||
|
render json: e, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_payment
|
||||||
|
res = Checkout::PaymentService.new.confirm_payment(@current_order, current_user, params[:payment_id])
|
||||||
|
render json: res
|
||||||
|
rescue StandardError => e
|
||||||
|
render json: e, status: :unprocessable_entity
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -15,4 +15,8 @@ module API::OrderConcern
|
|||||||
def ensure_order
|
def ensure_order
|
||||||
raise ActiveRecord::RecordNotFound if @current_order.nil?
|
raise ActiveRecord::RecordNotFound if @current_order.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cart_params
|
||||||
|
params.permit(:order_token, :orderable_id, :quantity, :user_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -22,4 +22,9 @@ export default class CartAPI {
|
|||||||
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/set_quantity', { order_token: order.token, orderable_id: orderableId, quantity });
|
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/set_quantity', { order_token: order.token, orderable_id: orderableId, quantity });
|
||||||
return res?.data;
|
return res?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async setCustomer (order: Order, userId: number): Promise<Order> {
|
||||||
|
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/set_customer', { order_token: order.token, user_id: userId });
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
21
app/frontend/src/javascript/api/checkout.ts
Normal file
21
app/frontend/src/javascript/api/checkout.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import apiClient from './clients/api-client';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { OrderPayment } from '../models/order';
|
||||||
|
|
||||||
|
export default class CheckoutAPI {
|
||||||
|
static async payment (token: string, paymentId?: string): Promise<OrderPayment> {
|
||||||
|
const res: AxiosResponse<OrderPayment> = await apiClient.post('/api/checkout/payment', {
|
||||||
|
order_token: token,
|
||||||
|
payment_id: paymentId
|
||||||
|
});
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async confirmPayment (token: string, paymentId: string): Promise<OrderPayment> {
|
||||||
|
const res: AxiosResponse<OrderPayment> = await apiClient.post('/api/checkout/confirm_payment', {
|
||||||
|
order_token: token,
|
||||||
|
payment_id: paymentId
|
||||||
|
});
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,16 @@ export default class MemberAPI {
|
|||||||
return res?.data;
|
return res?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async search (name: string): Promise<Array<User>> {
|
||||||
|
const res: AxiosResponse<Array<User>> = await apiClient.get(`/api/members/search/${name}`);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async get (id: number): Promise<User> {
|
||||||
|
const res: AxiosResponse<User> = await apiClient.get(`/api/members/${id}`);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
static async create (user: User): Promise<User> {
|
static async create (user: User): Promise<User> {
|
||||||
const data = serialize({ user });
|
const data = serialize({ user });
|
||||||
if (user.profile_attributes?.user_avatar_attributes?.attachment_files[0]) {
|
if (user.profile_attributes?.user_avatar_attributes?.attachment_files[0]) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../base/loader';
|
||||||
@ -8,12 +8,16 @@ import useCart from '../../hooks/use-cart';
|
|||||||
import FormatLib from '../../lib/format';
|
import FormatLib from '../../lib/format';
|
||||||
import CartAPI from '../../api/cart';
|
import CartAPI from '../../api/cart';
|
||||||
import { User } from '../../models/user';
|
import { User } from '../../models/user';
|
||||||
|
import { PaymentModal } from '../payment/stripe/payment-modal';
|
||||||
|
import { PaymentMethod } from '../../models/payment';
|
||||||
|
import { Order } from '../../models/order';
|
||||||
|
import { MemberSelect } from '../user/member-select';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
interface StoreCartProps {
|
interface StoreCartProps {
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
currentUser: User,
|
currentUser?: User,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,6 +27,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onError, currentUser }) => {
|
|||||||
const { t } = useTranslation('public');
|
const { t } = useTranslation('public');
|
||||||
|
|
||||||
const { cart, setCart, reloadCart } = useCart();
|
const { cart, setCart, reloadCart } = useCart();
|
||||||
|
const [paymentModal, setPaymentModal] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
@ -58,7 +63,38 @@ const StoreCart: React.FC<StoreCartProps> = ({ onError, currentUser }) => {
|
|||||||
* Checkout cart
|
* Checkout cart
|
||||||
*/
|
*/
|
||||||
const checkout = () => {
|
const checkout = () => {
|
||||||
console.log('checkout .....');
|
setPaymentModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open/closes the payment modal
|
||||||
|
*/
|
||||||
|
const togglePaymentModal = (): void => {
|
||||||
|
setPaymentModal(!paymentModal);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open/closes the payment modal
|
||||||
|
*/
|
||||||
|
const handlePaymentSuccess = (data: Order): void => {
|
||||||
|
console.log(data);
|
||||||
|
setPaymentModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change cart's customer by admin/manger
|
||||||
|
*/
|
||||||
|
const handleChangeMember = (userId: number): void => {
|
||||||
|
CartAPI.setCustomer(cart, userId).then(data => {
|
||||||
|
setCart(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current operator has administrative rights or is a normal member
|
||||||
|
*/
|
||||||
|
const isPrivileged = (): boolean => {
|
||||||
|
return (currentUser?.role === 'admin' || currentUser?.role === 'manager');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -79,10 +115,24 @@ const StoreCart: React.FC<StoreCartProps> = ({ onError, currentUser }) => {
|
|||||||
</FabButton>
|
</FabButton>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{cart && <p>Totale: {FormatLib.price(cart.amount)}</p>}
|
{cart && cart.order_items_attributes.length > 0 && <p>Totale: {FormatLib.price(cart.amount)}</p>}
|
||||||
<FabButton className="checkout-btn" onClick={checkout}>
|
{cart && isPrivileged() && <MemberSelect defaultUser={cart.user} onSelected={handleChangeMember} />}
|
||||||
{t('app.public.store_cart.checkout')}
|
{cart &&
|
||||||
</FabButton>
|
<FabButton className="checkout-btn" onClick={checkout} disabled={!cart.user || cart.order_items_attributes.length === 0}>
|
||||||
|
{t('app.public.store_cart.checkout')}
|
||||||
|
</FabButton>
|
||||||
|
}
|
||||||
|
{cart && cart.order_items_attributes.length > 0 && cart.user && <div>
|
||||||
|
<PaymentModal isOpen={paymentModal}
|
||||||
|
toggleModal={togglePaymentModal}
|
||||||
|
afterSuccess={handlePaymentSuccess}
|
||||||
|
onError={onError}
|
||||||
|
cart={{ customer_id: currentUser.id, items: [], payment_method: PaymentMethod.Card }}
|
||||||
|
order={cart}
|
||||||
|
operator={currentUser}
|
||||||
|
customer={cart.user}
|
||||||
|
updateCart={() => console.log('success')} />
|
||||||
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,16 +18,18 @@ import { GoogleTagManager } from '../../models/gtm';
|
|||||||
import { ComputePriceResult } from '../../models/price';
|
import { ComputePriceResult } from '../../models/price';
|
||||||
import { Wallet } from '../../models/wallet';
|
import { Wallet } from '../../models/wallet';
|
||||||
import FormatLib from '../../lib/format';
|
import FormatLib from '../../lib/format';
|
||||||
|
import { Order } from '../../models/order';
|
||||||
|
|
||||||
export interface GatewayFormProps {
|
export interface GatewayFormProps {
|
||||||
onSubmit: () => void,
|
onSubmit: () => void,
|
||||||
onSuccess: (result: Invoice|PaymentSchedule) => void,
|
onSuccess: (result: Invoice|PaymentSchedule|Order) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
customer: User,
|
customer: User,
|
||||||
operator: User,
|
operator: User,
|
||||||
className?: string,
|
className?: string,
|
||||||
paymentSchedule?: PaymentSchedule,
|
paymentSchedule?: PaymentSchedule,
|
||||||
cart?: ShoppingCart,
|
cart?: ShoppingCart,
|
||||||
|
order?: Order,
|
||||||
updateCart?: (cart: ShoppingCart) => void,
|
updateCart?: (cart: ShoppingCart) => void,
|
||||||
formId: string,
|
formId: string,
|
||||||
}
|
}
|
||||||
@ -35,9 +37,10 @@ export interface GatewayFormProps {
|
|||||||
interface AbstractPaymentModalProps {
|
interface AbstractPaymentModalProps {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
toggleModal: () => void,
|
toggleModal: () => void,
|
||||||
afterSuccess: (result: Invoice|PaymentSchedule) => void,
|
afterSuccess: (result: Invoice|PaymentSchedule|Order) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
cart: ShoppingCart,
|
cart: ShoppingCart,
|
||||||
|
order?: Order,
|
||||||
updateCart?: (cart: ShoppingCart) => void,
|
updateCart?: (cart: ShoppingCart) => void,
|
||||||
currentUser: User,
|
currentUser: User,
|
||||||
schedule?: PaymentSchedule,
|
schedule?: PaymentSchedule,
|
||||||
@ -61,7 +64,7 @@ declare const GTM: GoogleTagManager;
|
|||||||
* This component must not be called directly but must be extended for each implemented payment gateway.
|
* This component must not be called directly but must be extended for each implemented payment gateway.
|
||||||
* @see https://reactjs.org/docs/composition-vs-inheritance.html
|
* @see https://reactjs.org/docs/composition-vs-inheritance.html
|
||||||
*/
|
*/
|
||||||
export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName, title, preventCgv, preventScheduleInfo, modalSize }) => {
|
export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, currentUser, schedule, customer, logoFooter, GatewayForm, formId, className, formClassName, title, preventCgv, preventScheduleInfo, modalSize, order }) => {
|
||||||
// customer's wallet
|
// customer's wallet
|
||||||
const [wallet, setWallet] = useState<Wallet>(null);
|
const [wallet, setWallet] = useState<Wallet>(null);
|
||||||
// server-computed price with all details
|
// server-computed price with all details
|
||||||
@ -108,16 +111,25 @@ export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOp
|
|||||||
* - Refresh the remaining price
|
* - Refresh the remaining price
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!cart) return;
|
if (order && order?.user?.id) {
|
||||||
WalletAPI.getByUser(cart.customer_id).then((wallet) => {
|
WalletAPI.getByUser(order.user.id).then((wallet) => {
|
||||||
setWallet(wallet);
|
setWallet(wallet);
|
||||||
PriceAPI.compute(cart).then((res) => {
|
const p = { price: order.amount, price_without_coupon: order.amount };
|
||||||
setPrice(res);
|
setPrice(p);
|
||||||
setRemainingPrice(new WalletLib(wallet).computeRemainingPrice(res.price));
|
setRemainingPrice(new WalletLib(wallet).computeRemainingPrice(p.price));
|
||||||
setReady(true);
|
setReady(true);
|
||||||
});
|
});
|
||||||
});
|
} else if (cart && cart.customer_id) {
|
||||||
}, [cart]);
|
WalletAPI.getByUser(cart.customer_id).then((wallet) => {
|
||||||
|
setWallet(wallet);
|
||||||
|
PriceAPI.compute(cart).then((res) => {
|
||||||
|
setPrice(res);
|
||||||
|
setRemainingPrice(new WalletLib(wallet).computeRemainingPrice(res.price));
|
||||||
|
setReady(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [cart, order]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if there is currently an error to display
|
* Check if there is currently an error to display
|
||||||
@ -157,7 +169,7 @@ export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOp
|
|||||||
/**
|
/**
|
||||||
* After sending the form with success, process the resulting payment method
|
* After sending the form with success, process the resulting payment method
|
||||||
*/
|
*/
|
||||||
const handleFormSuccess = async (result: Invoice|PaymentSchedule): Promise<void> => {
|
const handleFormSuccess = async (result: Invoice|PaymentSchedule|Order): Promise<void> => {
|
||||||
setSubmitState(false);
|
setSubmitState(false);
|
||||||
GTM.trackPurchase(result.id, result.total);
|
GTM.trackPurchase(result.id, result.total);
|
||||||
afterSuccess(result);
|
afterSuccess(result);
|
||||||
@ -213,6 +225,7 @@ export const AbstractPaymentModal: React.FC<AbstractPaymentModalProps> = ({ isOp
|
|||||||
className={`gateway-form ${formClassName || ''}`}
|
className={`gateway-form ${formClassName || ''}`}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
updateCart={updateCart}
|
updateCart={updateCart}
|
||||||
customer={customer}
|
customer={customer}
|
||||||
paymentSchedule={schedule}>
|
paymentSchedule={schedule}>
|
||||||
|
@ -11,15 +11,17 @@ import { Setting, SettingName } from '../../models/setting';
|
|||||||
import { Invoice } from '../../models/invoice';
|
import { Invoice } from '../../models/invoice';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Order } from '../../models/order';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
interface CardPaymentModalProps {
|
interface CardPaymentModalProps {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
toggleModal: () => void,
|
toggleModal: () => void,
|
||||||
afterSuccess: (result: Invoice|PaymentSchedule) => void,
|
afterSuccess: (result: Invoice|PaymentSchedule|Order) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
cart: ShoppingCart,
|
cart: ShoppingCart,
|
||||||
|
order?: Order,
|
||||||
currentUser: User,
|
currentUser: User,
|
||||||
schedule?: PaymentSchedule,
|
schedule?: PaymentSchedule,
|
||||||
customer: User
|
customer: User
|
||||||
@ -29,7 +31,7 @@ interface CardPaymentModalProps {
|
|||||||
* This component open a modal dialog for the configured payment gateway, allowing the user to input his card data
|
* This component open a modal dialog for the configured payment gateway, allowing the user to input his card data
|
||||||
* to process an online payment.
|
* to process an online payment.
|
||||||
*/
|
*/
|
||||||
const CardPaymentModal: React.FC<CardPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule, cart, customer }) => {
|
const CardPaymentModal: React.FC<CardPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule, cart, customer, order }) => {
|
||||||
const { t } = useTranslation('shared');
|
const { t } = useTranslation('shared');
|
||||||
|
|
||||||
const [gateway, setGateway] = useState<Setting>(null);
|
const [gateway, setGateway] = useState<Setting>(null);
|
||||||
@ -49,6 +51,7 @@ const CardPaymentModal: React.FC<CardPaymentModalProps> = ({ isOpen, toggleModal
|
|||||||
afterSuccess={afterSuccess}
|
afterSuccess={afterSuccess}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
schedule={schedule}
|
schedule={schedule}
|
||||||
customer={customer} />;
|
customer={customer} />;
|
||||||
@ -63,6 +66,7 @@ const CardPaymentModal: React.FC<CardPaymentModalProps> = ({ isOpen, toggleModal
|
|||||||
afterSuccess={afterSuccess}
|
afterSuccess={afterSuccess}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
schedule={schedule}
|
schedule={schedule}
|
||||||
customer={customer} />;
|
customer={customer} />;
|
||||||
@ -99,4 +103,4 @@ const CardPaymentModalWrapper: React.FC<CardPaymentModalProps> = (props) => {
|
|||||||
|
|
||||||
export { CardPaymentModalWrapper as CardPaymentModal };
|
export { CardPaymentModalWrapper as CardPaymentModal };
|
||||||
|
|
||||||
Application.Components.component('cardPaymentModal', react2angular(CardPaymentModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess', 'onError', 'currentUser', 'schedule', 'cart', 'customer']));
|
Application.Components.component('cardPaymentModal', react2angular(CardPaymentModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess', 'onError', 'currentUser', 'schedule', 'cart', 'customer', 'order']));
|
||||||
|
@ -9,6 +9,7 @@ import { SettingName } from '../../../models/setting';
|
|||||||
import { CardPaymentModal } from '../card-payment-modal';
|
import { CardPaymentModal } from '../card-payment-modal';
|
||||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||||
import { HtmlTranslate } from '../../base/html-translate';
|
import { HtmlTranslate } from '../../base/html-translate';
|
||||||
|
import CheckoutAPI from '../../../api/checkout';
|
||||||
|
|
||||||
const ALL_SCHEDULE_METHODS = ['card', 'check', 'transfer'] as const;
|
const ALL_SCHEDULE_METHODS = ['card', 'check', 'transfer'] as const;
|
||||||
type scheduleMethod = typeof ALL_SCHEDULE_METHODS[number];
|
type scheduleMethod = typeof ALL_SCHEDULE_METHODS[number];
|
||||||
@ -24,7 +25,7 @@ type selectOption = { value: scheduleMethod, label: string };
|
|||||||
* This is intended for use by privileged users.
|
* This is intended for use by privileged users.
|
||||||
* The form validation button must be created elsewhere, using the attribute form={formId}.
|
* The form validation button must be created elsewhere, using the attribute form={formId}.
|
||||||
*/
|
*/
|
||||||
export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule, cart, updateCart, customer, operator, formId }) => {
|
export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule, cart, updateCart, customer, operator, formId, order }) => {
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
const [method, setMethod] = useState<scheduleMethod>('check');
|
const [method, setMethod] = useState<scheduleMethod>('check');
|
||||||
@ -86,8 +87,13 @@ export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSucce
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const document = await LocalPaymentAPI.confirmPayment(cart);
|
let res;
|
||||||
onSuccess(document);
|
if (order) {
|
||||||
|
res = await CheckoutAPI.payment(order.token);
|
||||||
|
} else {
|
||||||
|
res = await LocalPaymentAPI.confirmPayment(cart);
|
||||||
|
}
|
||||||
|
onSuccess(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
onError(e);
|
onError(e);
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,17 @@ import { ModalSize } from '../../base/fab-modal';
|
|||||||
import { Loader } from '../../base/loader';
|
import { Loader } from '../../base/loader';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { IApplication } from '../../../models/application';
|
import { IApplication } from '../../../models/application';
|
||||||
|
import { Order } from '../../../models/order';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
interface LocalPaymentModalProps {
|
interface LocalPaymentModalProps {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
toggleModal: () => void,
|
toggleModal: () => void,
|
||||||
afterSuccess: (result: Invoice|PaymentSchedule) => void,
|
afterSuccess: (result: Invoice|PaymentSchedule|Order) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
cart: ShoppingCart,
|
cart: ShoppingCart,
|
||||||
|
order?: Order,
|
||||||
updateCart: (cart: ShoppingCart) => void,
|
updateCart: (cart: ShoppingCart) => void,
|
||||||
currentUser: User,
|
currentUser: User,
|
||||||
schedule?: PaymentSchedule,
|
schedule?: PaymentSchedule,
|
||||||
@ -28,7 +30,7 @@ interface LocalPaymentModalProps {
|
|||||||
/**
|
/**
|
||||||
* This component enables a privileged user to confirm a local payments.
|
* This component enables a privileged user to confirm a local payments.
|
||||||
*/
|
*/
|
||||||
const LocalPaymentModal: React.FC<LocalPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, currentUser, schedule, customer }) => {
|
const LocalPaymentModal: React.FC<LocalPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, currentUser, schedule, customer, order }) => {
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,7 +56,7 @@ const LocalPaymentModal: React.FC<LocalPaymentModalProps> = ({ isOpen, toggleMod
|
|||||||
/**
|
/**
|
||||||
* Integrates the LocalPaymentForm into the parent AbstractPaymentModal
|
* Integrates the LocalPaymentForm into the parent AbstractPaymentModal
|
||||||
*/
|
*/
|
||||||
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cart, updateCart, customer, paymentSchedule, children }) => {
|
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cart, updateCart, customer, paymentSchedule, children, order }) => {
|
||||||
return (
|
return (
|
||||||
<LocalPaymentForm onSubmit={onSubmit}
|
<LocalPaymentForm onSubmit={onSubmit}
|
||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
@ -63,6 +65,7 @@ const LocalPaymentModal: React.FC<LocalPaymentModalProps> = ({ isOpen, toggleMod
|
|||||||
className={className}
|
className={className}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
updateCart={updateCart}
|
updateCart={updateCart}
|
||||||
customer={customer}
|
customer={customer}
|
||||||
paymentSchedule={paymentSchedule}>
|
paymentSchedule={paymentSchedule}>
|
||||||
@ -81,6 +84,7 @@ const LocalPaymentModal: React.FC<LocalPaymentModalProps> = ({ isOpen, toggleMod
|
|||||||
formClassName="local-payment-form"
|
formClassName="local-payment-form"
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
updateCart={updateCart}
|
updateCart={updateCart}
|
||||||
customer={customer}
|
customer={customer}
|
||||||
afterSuccess={afterSuccess}
|
afterSuccess={afterSuccess}
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
} from '../../../models/payzen';
|
} from '../../../models/payzen';
|
||||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||||
import { Invoice } from '../../../models/invoice';
|
import { Invoice } from '../../../models/invoice';
|
||||||
|
import CheckoutAPI from '../../../api/checkout';
|
||||||
|
import { Order } from '../../../models/order';
|
||||||
|
|
||||||
// we use these two additional parameters to update the card, if provided
|
// we use these two additional parameters to update the card, if provided
|
||||||
interface PayzenFormProps extends GatewayFormProps {
|
interface PayzenFormProps extends GatewayFormProps {
|
||||||
@ -22,7 +24,7 @@ interface PayzenFormProps extends GatewayFormProps {
|
|||||||
* 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={formId}.
|
* The form validation button must be created elsewhere, using the attribute form={formId}.
|
||||||
*/
|
*/
|
||||||
export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule, updateCard = false, cart, customer, formId }) => {
|
export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule, updateCard = false, cart, customer, formId, order }) => {
|
||||||
const PayZenKR = useRef<KryptonClient>(null);
|
const PayZenKR = useRef<KryptonClient>(null);
|
||||||
const [loadingClass, setLoadingClass] = useState<'hidden' | 'loader' | 'loader-overlay'>('loader');
|
const [loadingClass, setLoadingClass] = useState<'hidden' | 'loader' | 'loader-overlay'>('loader');
|
||||||
|
|
||||||
@ -44,7 +46,7 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
|||||||
.catch(error => onError(error));
|
.catch(error => onError(error));
|
||||||
}).catch(error => onError(error));
|
}).catch(error => onError(error));
|
||||||
});
|
});
|
||||||
}, [cart, paymentSchedule, customer]);
|
}, [cart, paymentSchedule, customer, order]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ask the API to create the form token.
|
* Ask the API to create the form token.
|
||||||
@ -55,6 +57,9 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
|||||||
return await PayzenAPI.updateToken(paymentSchedule?.id);
|
return await PayzenAPI.updateToken(paymentSchedule?.id);
|
||||||
} else if (paymentSchedule) {
|
} else if (paymentSchedule) {
|
||||||
return await PayzenAPI.chargeCreateToken(cart, customer);
|
return await PayzenAPI.chargeCreateToken(cart, customer);
|
||||||
|
} else if (order) {
|
||||||
|
const res = await CheckoutAPI.payment(order.token);
|
||||||
|
return res.payment as CreateTokenResponse;
|
||||||
} else {
|
} else {
|
||||||
return await PayzenAPI.chargeCreatePayment(cart, customer);
|
return await PayzenAPI.chargeCreatePayment(cart, customer);
|
||||||
}
|
}
|
||||||
@ -88,9 +93,12 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
|||||||
/**
|
/**
|
||||||
* Confirm the payment, depending on the current type of payment (single shot or recurring)
|
* Confirm the payment, depending on the current type of payment (single shot or recurring)
|
||||||
*/
|
*/
|
||||||
const confirmPayment = async (event: ProcessPaymentAnswer, transaction: PaymentTransaction): Promise<Invoice|PaymentSchedule> => {
|
const confirmPayment = async (event: ProcessPaymentAnswer, transaction: PaymentTransaction): Promise<Invoice|PaymentSchedule|Order> => {
|
||||||
if (paymentSchedule) {
|
if (paymentSchedule) {
|
||||||
return await PayzenAPI.confirmPaymentSchedule(event.clientAnswer.orderDetails.orderId, transaction.uuid, cart);
|
return await PayzenAPI.confirmPaymentSchedule(event.clientAnswer.orderDetails.orderId, transaction.uuid, cart);
|
||||||
|
} else if (order) {
|
||||||
|
const res = await CheckoutAPI.confirmPayment(order.token, event.clientAnswer.orderDetails.orderId);
|
||||||
|
return res.order;
|
||||||
} else {
|
} else {
|
||||||
return await PayzenAPI.confirm(event.clientAnswer.orderDetails.orderId, cart);
|
return await PayzenAPI.confirm(event.clientAnswer.orderDetails.orderId, cart);
|
||||||
}
|
}
|
||||||
@ -132,7 +140,9 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
|
|||||||
try {
|
try {
|
||||||
const { result } = await PayZenKR.current.validateForm();
|
const { result } = await PayZenKR.current.validateForm();
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
await PayzenAPI.checkCart(cart, customer);
|
if (!order) {
|
||||||
|
await PayzenAPI.checkCart(cart, customer);
|
||||||
|
}
|
||||||
await PayZenKR.current.onSubmit(onPaid);
|
await PayZenKR.current.onSubmit(onPaid);
|
||||||
await PayZenKR.current.onError(handleError);
|
await PayZenKR.current.onError(handleError);
|
||||||
await PayZenKR.current.submit();
|
await PayZenKR.current.submit();
|
||||||
|
@ -9,13 +9,15 @@ import payzenLogo from '../../../../../images/payzen-secure.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 { PayzenForm } from './payzen-form';
|
import { PayzenForm } from './payzen-form';
|
||||||
|
import { Order } from '../../../models/order';
|
||||||
|
|
||||||
interface PayzenModalProps {
|
interface PayzenModalProps {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
toggleModal: () => void,
|
toggleModal: () => void,
|
||||||
afterSuccess: (result: Invoice|PaymentSchedule) => void,
|
afterSuccess: (result: Invoice|PaymentSchedule|Order) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
cart: ShoppingCart,
|
cart: ShoppingCart,
|
||||||
|
order?: Order,
|
||||||
currentUser: User,
|
currentUser: User,
|
||||||
schedule?: PaymentSchedule,
|
schedule?: PaymentSchedule,
|
||||||
customer: User
|
customer: User
|
||||||
@ -28,7 +30,7 @@ interface PayzenModalProps {
|
|||||||
* This component should not be called directly. Prefer using <CardPaymentModal> which can handle the configuration
|
* This component should not be called directly. Prefer using <CardPaymentModal> which can handle the configuration
|
||||||
* of a different payment gateway.
|
* of a different payment gateway.
|
||||||
*/
|
*/
|
||||||
export const PayzenModal: React.FC<PayzenModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, currentUser, schedule, customer }) => {
|
export const PayzenModal: React.FC<PayzenModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, currentUser, schedule, customer, order }) => {
|
||||||
/**
|
/**
|
||||||
* Return the logos, shown in the modal footer.
|
* Return the logos, shown in the modal footer.
|
||||||
*/
|
*/
|
||||||
@ -45,7 +47,7 @@ export const PayzenModal: React.FC<PayzenModalProps> = ({ isOpen, toggleModal, a
|
|||||||
/**
|
/**
|
||||||
* Integrates the PayzenForm into the parent PaymentModal
|
* Integrates the PayzenForm into the parent PaymentModal
|
||||||
*/
|
*/
|
||||||
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cart, customer, paymentSchedule, children }) => {
|
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cart, customer, paymentSchedule, children, order }) => {
|
||||||
return (
|
return (
|
||||||
<PayzenForm onSubmit={onSubmit}
|
<PayzenForm onSubmit={onSubmit}
|
||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
@ -54,6 +56,7 @@ export const PayzenModal: React.FC<PayzenModalProps> = ({ isOpen, toggleModal, a
|
|||||||
operator={operator}
|
operator={operator}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
className={className}
|
className={className}
|
||||||
paymentSchedule={paymentSchedule}>
|
paymentSchedule={paymentSchedule}>
|
||||||
{children}
|
{children}
|
||||||
@ -70,6 +73,7 @@ export const PayzenModal: React.FC<PayzenModalProps> = ({ isOpen, toggleModal, a
|
|||||||
className="payzen-modal"
|
className="payzen-modal"
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
customer={customer}
|
customer={customer}
|
||||||
afterSuccess={afterSuccess}
|
afterSuccess={afterSuccess}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
|
@ -11,13 +11,15 @@ import { LocalPaymentModal } from '../local-payment/local-payment-modal';
|
|||||||
import { CardPaymentModal } from '../card-payment-modal';
|
import { CardPaymentModal } from '../card-payment-modal';
|
||||||
import PriceAPI from '../../../api/price';
|
import PriceAPI from '../../../api/price';
|
||||||
import { ComputePriceResult } from '../../../models/price';
|
import { ComputePriceResult } from '../../../models/price';
|
||||||
|
import { Order } from '../../../models/order';
|
||||||
|
|
||||||
interface PaymentModalProps {
|
interface PaymentModalProps {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
toggleModal: () => void,
|
toggleModal: () => void,
|
||||||
afterSuccess: (result: Invoice|PaymentSchedule) => void,
|
afterSuccess: (result: Invoice|PaymentSchedule|Order) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
cart: ShoppingCart,
|
cart: ShoppingCart,
|
||||||
|
order?: Order,
|
||||||
updateCart: (cart: ShoppingCart) => void,
|
updateCart: (cart: ShoppingCart) => void,
|
||||||
operator: User,
|
operator: User,
|
||||||
schedule?: PaymentSchedule,
|
schedule?: PaymentSchedule,
|
||||||
@ -27,7 +29,7 @@ interface PaymentModalProps {
|
|||||||
/**
|
/**
|
||||||
* This component is responsible for rendering the payment modal.
|
* This component is responsible for rendering the payment modal.
|
||||||
*/
|
*/
|
||||||
export const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, operator, schedule, customer }) => {
|
export const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, operator, schedule, customer, order }) => {
|
||||||
// the user's wallet
|
// the user's wallet
|
||||||
const [wallet, setWallet] = useState<Wallet>(null);
|
const [wallet, setWallet] = useState<Wallet>(null);
|
||||||
// the price of the cart
|
// the price of the cart
|
||||||
@ -44,10 +46,14 @@ export const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal,
|
|||||||
|
|
||||||
// refresh the price when the cart changes
|
// refresh the price when the cart changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
PriceAPI.compute(cart).then(price => {
|
if (order) {
|
||||||
setPrice(price);
|
setPrice({ price: order.amount, price_without_coupon: order.amount });
|
||||||
});
|
} else {
|
||||||
}, [cart]);
|
PriceAPI.compute(cart).then(price => {
|
||||||
|
setPrice(price);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [cart, order]);
|
||||||
|
|
||||||
// refresh the remaining price when the cart price was computed and the wallet was retrieved
|
// refresh the remaining price when the cart price was computed and the wallet was retrieved
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -73,6 +79,7 @@ export const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal,
|
|||||||
afterSuccess={afterSuccess}
|
afterSuccess={afterSuccess}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
updateCart={updateCart}
|
updateCart={updateCart}
|
||||||
currentUser={operator}
|
currentUser={operator}
|
||||||
customer={customer}
|
customer={customer}
|
||||||
@ -86,6 +93,7 @@ export const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal,
|
|||||||
afterSuccess={afterSuccess}
|
afterSuccess={afterSuccess}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
currentUser={operator}
|
currentUser={operator}
|
||||||
customer={customer}
|
customer={customer}
|
||||||
schedule={schedule}
|
schedule={schedule}
|
||||||
|
@ -6,12 +6,14 @@ import { PaymentConfirmation } from '../../../models/payment';
|
|||||||
import StripeAPI from '../../../api/stripe';
|
import StripeAPI from '../../../api/stripe';
|
||||||
import { Invoice } from '../../../models/invoice';
|
import { Invoice } from '../../../models/invoice';
|
||||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||||
|
import CheckoutAPI from '../../../api/checkout';
|
||||||
|
import { Order } from '../../../models/order';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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={formId}.
|
* The form validation button must be created elsewhere, using the attribute form={formId}.
|
||||||
*/
|
*/
|
||||||
export const StripeForm: React.FC<GatewayFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule = false, cart, formId }) => {
|
export const StripeForm: React.FC<GatewayFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule = false, cart, formId, order }) => {
|
||||||
const { t } = useTranslation('shared');
|
const { t } = useTranslation('shared');
|
||||||
|
|
||||||
const stripe = useStripe();
|
const stripe = useStripe();
|
||||||
@ -41,9 +43,19 @@ export const StripeForm: React.FC<GatewayFormProps> = ({ onSubmit, onSuccess, on
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (!paymentSchedule) {
|
if (!paymentSchedule) {
|
||||||
// process the normal payment pipeline, including SCA validation
|
if (order) {
|
||||||
const res = await StripeAPI.confirmMethod(paymentMethod.id, cart);
|
const res = await CheckoutAPI.payment(order.token, paymentMethod.id);
|
||||||
await handleServerConfirmation(res);
|
if (res.payment) {
|
||||||
|
await handleServerConfirmation(res.payment as PaymentConfirmation);
|
||||||
|
} else {
|
||||||
|
res.order.total = res.order.amount;
|
||||||
|
await handleServerConfirmation(res.order);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// process the normal payment pipeline, including SCA validation
|
||||||
|
const res = await StripeAPI.confirmMethod(paymentMethod.id, cart);
|
||||||
|
await handleServerConfirmation(res);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const res = await StripeAPI.setupSubscription(paymentMethod.id, cart);
|
const res = await StripeAPI.setupSubscription(paymentMethod.id, cart);
|
||||||
await handleServerConfirmation(res, paymentMethod.id);
|
await handleServerConfirmation(res, paymentMethod.id);
|
||||||
@ -61,7 +73,7 @@ export const StripeForm: React.FC<GatewayFormProps> = ({ onSubmit, onSuccess, on
|
|||||||
* @param paymentMethodId ID of the payment method, required only when confirming a payment schedule
|
* @param paymentMethodId ID of the payment method, required only when confirming a payment schedule
|
||||||
* @see app/controllers/api/stripe_controller.rb#confirm_payment
|
* @see app/controllers/api/stripe_controller.rb#confirm_payment
|
||||||
*/
|
*/
|
||||||
const handleServerConfirmation = async (response: PaymentConfirmation|Invoice|PaymentSchedule, paymentMethodId?: string) => {
|
const handleServerConfirmation = async (response: PaymentConfirmation|Invoice|PaymentSchedule|Order, paymentMethodId?: string) => {
|
||||||
if ('error' in response) {
|
if ('error' in response) {
|
||||||
if (response.error.statusText) {
|
if (response.error.statusText) {
|
||||||
onError(response.error.statusText);
|
onError(response.error.statusText);
|
||||||
@ -78,8 +90,14 @@ export const StripeForm: React.FC<GatewayFormProps> = ({ onSubmit, onSuccess, on
|
|||||||
// The card action has been handled
|
// The card action has been handled
|
||||||
// The PaymentIntent can be confirmed again on the server
|
// The PaymentIntent can be confirmed again on the server
|
||||||
try {
|
try {
|
||||||
const confirmation = await StripeAPI.confirmIntent(result.paymentIntent.id, cart);
|
if (order) {
|
||||||
await handleServerConfirmation(confirmation);
|
const confirmation = await CheckoutAPI.confirmPayment(order.token, result.paymentIntent.id);
|
||||||
|
confirmation.order.total = confirmation.order.amount;
|
||||||
|
await handleServerConfirmation(confirmation.order);
|
||||||
|
} else {
|
||||||
|
const confirmation = await StripeAPI.confirmIntent(result.paymentIntent.id, cart);
|
||||||
|
await handleServerConfirmation(confirmation);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
onError(e);
|
onError(e);
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,15 @@ 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 { Invoice } from '../../../models/invoice';
|
import { Invoice } from '../../../models/invoice';
|
||||||
|
import { Order } from '../../../models/order';
|
||||||
|
|
||||||
interface StripeModalProps {
|
interface StripeModalProps {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
toggleModal: () => void,
|
toggleModal: () => void,
|
||||||
afterSuccess: (result: Invoice|PaymentSchedule) => void,
|
afterSuccess: (result: Invoice|PaymentSchedule|Order) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
cart: ShoppingCart,
|
cart: ShoppingCart,
|
||||||
|
order?: Order,
|
||||||
currentUser: User,
|
currentUser: User,
|
||||||
schedule?: PaymentSchedule,
|
schedule?: PaymentSchedule,
|
||||||
customer: User
|
customer: User
|
||||||
@ -29,7 +31,7 @@ interface StripeModalProps {
|
|||||||
* This component should not be called directly. Prefer using <CardPaymentModal> which can handle the configuration
|
* This component should not be called directly. Prefer using <CardPaymentModal> which can handle the configuration
|
||||||
* of a different payment gateway.
|
* of a different payment gateway.
|
||||||
*/
|
*/
|
||||||
export const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, currentUser, schedule, customer }) => {
|
export const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, currentUser, schedule, customer, order }) => {
|
||||||
/**
|
/**
|
||||||
* Return the logos, shown in the modal footer.
|
* Return the logos, shown in the modal footer.
|
||||||
*/
|
*/
|
||||||
@ -47,7 +49,7 @@ export const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, a
|
|||||||
/**
|
/**
|
||||||
* Integrates the StripeForm into the parent PaymentModal
|
* Integrates the StripeForm into the parent PaymentModal
|
||||||
*/
|
*/
|
||||||
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cart, customer, paymentSchedule, children }) => {
|
const renderForm: FunctionComponent<GatewayFormProps> = ({ onSubmit, onSuccess, onError, operator, className, formId, cart, customer, paymentSchedule, children, order }) => {
|
||||||
return (
|
return (
|
||||||
<StripeElements>
|
<StripeElements>
|
||||||
<StripeForm onSubmit={onSubmit}
|
<StripeForm onSubmit={onSubmit}
|
||||||
@ -57,6 +59,7 @@ export const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, a
|
|||||||
className={className}
|
className={className}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
customer={customer}
|
customer={customer}
|
||||||
paymentSchedule={paymentSchedule}>
|
paymentSchedule={paymentSchedule}>
|
||||||
{children}
|
{children}
|
||||||
@ -74,6 +77,7 @@ export const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, a
|
|||||||
formClassName="stripe-form"
|
formClassName="stripe-form"
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
cart={cart}
|
cart={cart}
|
||||||
|
order={order}
|
||||||
customer={customer}
|
customer={customer}
|
||||||
afterSuccess={afterSuccess}
|
afterSuccess={afterSuccess}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import AsyncSelect from 'react-select/async';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import MemberAPI from '../../api/member';
|
||||||
|
import { User } from '../../models/user';
|
||||||
|
|
||||||
|
interface MemberSelectProps {
|
||||||
|
defaultUser?: User,
|
||||||
|
onSelected?: (userId: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option format, expected by react-select
|
||||||
|
* @see https://github.com/JedWatson/react-select
|
||||||
|
*/
|
||||||
|
type selectOption = { value: number, label: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component renders the member select for manager.
|
||||||
|
*/
|
||||||
|
export const MemberSelect: React.FC<MemberSelectProps> = ({ defaultUser, onSelected }) => {
|
||||||
|
const { t } = useTranslation('public');
|
||||||
|
const [value, setValue] = useState<selectOption>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultUser) {
|
||||||
|
setValue({ value: defaultUser.id, label: defaultUser.name });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* search members by name
|
||||||
|
*/
|
||||||
|
const loadMembers = async (inputValue: string): Promise<Array<selectOption>> => {
|
||||||
|
if (!inputValue) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const data = await MemberAPI.search(inputValue);
|
||||||
|
return data.map(u => {
|
||||||
|
return { value: u.id, label: u.name };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback for handle select changed
|
||||||
|
*/
|
||||||
|
const onChange = (v: selectOption) => {
|
||||||
|
setValue(v);
|
||||||
|
onSelected(v.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="member-select">
|
||||||
|
<div className="member-select-header">
|
||||||
|
<h3 className="member-select-title">{t('app.public.member_select.select_a_member')}</h3>
|
||||||
|
</div>
|
||||||
|
<AsyncSelect placeholder={t('app.public.member_select.start_typing')}
|
||||||
|
cacheOptions
|
||||||
|
loadOptions={loadMembers}
|
||||||
|
defaultOptions
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,13 +1,18 @@
|
|||||||
import { TDateISO } from '../typings/date-iso';
|
import { TDateISO } from '../typings/date-iso';
|
||||||
|
import { PaymentConfirmation } from './payment';
|
||||||
|
import { CreateTokenResponse } from './payzen';
|
||||||
|
import { User } from './user';
|
||||||
|
|
||||||
export interface Order {
|
export interface Order {
|
||||||
id: number,
|
id: number,
|
||||||
token: string,
|
token: string,
|
||||||
statistic_profile_id?: number,
|
statistic_profile_id?: number,
|
||||||
|
user?: User,
|
||||||
operator_id?: number,
|
operator_id?: number,
|
||||||
reference?: string,
|
reference?: string,
|
||||||
state?: string,
|
state?: string,
|
||||||
amount?: number,
|
amount?: number,
|
||||||
|
total?: number,
|
||||||
created_at?: TDateISO,
|
created_at?: TDateISO,
|
||||||
order_items_attributes: Array<{
|
order_items_attributes: Array<{
|
||||||
id: number,
|
id: number,
|
||||||
@ -19,3 +24,8 @@ export interface Order {
|
|||||||
is_offered: boolean
|
is_offered: boolean
|
||||||
}>,
|
}>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OrderPayment {
|
||||||
|
order: Order,
|
||||||
|
payment?: PaymentConfirmation|CreateTokenResponse
|
||||||
|
}
|
||||||
|
@ -8,5 +8,8 @@ class Order < ApplicationRecord
|
|||||||
ALL_STATES = %w[cart].freeze
|
ALL_STATES = %w[cart].freeze
|
||||||
enum state: ALL_STATES.zip(ALL_STATES).to_h
|
enum state: ALL_STATES.zip(ALL_STATES).to_h
|
||||||
|
|
||||||
|
PAYMENT_STATES = %w[paid failed].freeze
|
||||||
|
enum payment_state: PAYMENT_STATES.zip(PAYMENT_STATES).to_h
|
||||||
|
|
||||||
validates :token, :state, presence: true
|
validates :token, :state, presence: true
|
||||||
end
|
end
|
||||||
|
@ -6,6 +6,10 @@ class CartPolicy < ApplicationPolicy
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_customer?
|
||||||
|
user.privileged?
|
||||||
|
end
|
||||||
|
|
||||||
%w[add_item remove_item set_quantity].each do |action|
|
%w[add_item remove_item set_quantity].each do |action|
|
||||||
define_method "#{action}?" do
|
define_method "#{action}?" do
|
||||||
return user.privileged? || (record.statistic_profile_id == user.statistic_profile.id) if user
|
return user.privileged? || (record.statistic_profile_id == user.statistic_profile.id) if user
|
||||||
|
10
app/services/cart/set_customer_service.rb
Normal file
10
app/services/cart/set_customer_service.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Provides methods for admin set customer to order
|
||||||
|
class Cart::SetCustomerService
|
||||||
|
def call(order, user_id)
|
||||||
|
user = User.find(user_id)
|
||||||
|
order.update(statistic_profile_id: user.statistic_profile.id)
|
||||||
|
order.reload
|
||||||
|
end
|
||||||
|
end
|
33
app/services/checkout/payment_service.rb
Normal file
33
app/services/checkout/payment_service.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Provides methods for pay cart
|
||||||
|
class Checkout::PaymentService
|
||||||
|
require 'pay_zen/helper'
|
||||||
|
require 'stripe/helper'
|
||||||
|
|
||||||
|
def payment(order, operator, payment_id = '')
|
||||||
|
if operator.member?
|
||||||
|
if Stripe::Helper.enabled?
|
||||||
|
Payments::StripeService.new.payment(order, payment_id)
|
||||||
|
elsif PayZen::Helper.enabled?
|
||||||
|
Payments::PayzenService.new.payment(order)
|
||||||
|
else
|
||||||
|
raise Error('Bad gateway or online payment is disabled')
|
||||||
|
end
|
||||||
|
elsif operator.privileged?
|
||||||
|
Payments::LocalService.new.payment(order)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_payment(order, operator, payment_id = '')
|
||||||
|
if operator.member?
|
||||||
|
if Stripe::Helper.enabled?
|
||||||
|
Payments::StripeService.new.confirm_payment(order, payment_id)
|
||||||
|
elsif PayZen::Helper.enabled?
|
||||||
|
Payments::PayzenService.new.confirm_payment(order, payment_id)
|
||||||
|
else
|
||||||
|
raise Error('Bad gateway or online payment is disabled')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
11
app/services/payments/local_service.rb
Normal file
11
app/services/payments/local_service.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Provides methods for pay cart by Local
|
||||||
|
class Payments::LocalService
|
||||||
|
include Payments::PaymentConcern
|
||||||
|
|
||||||
|
def payment(order)
|
||||||
|
o = payment_success(order)
|
||||||
|
{ order: o }
|
||||||
|
end
|
||||||
|
end
|
28
app/services/payments/payment_concern.rb
Normal file
28
app/services/payments/payment_concern.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Concern for Payment
|
||||||
|
module Payments::PaymentConcern
|
||||||
|
private
|
||||||
|
|
||||||
|
def get_wallet_debit(user, total_amount)
|
||||||
|
wallet_amount = (user.wallet.amount * 100).to_i
|
||||||
|
wallet_amount >= total_amount ? total_amount : wallet_amount
|
||||||
|
end
|
||||||
|
|
||||||
|
def debit_amount(order)
|
||||||
|
total = order.amount
|
||||||
|
wallet_debit = get_wallet_debit(order.statistic_profile.user, total)
|
||||||
|
total - wallet_debit
|
||||||
|
end
|
||||||
|
|
||||||
|
def payment_success(order)
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
WalletService.debit_user_wallet(order, order.statistic_profile.user)
|
||||||
|
order.update(payment_state: 'paid')
|
||||||
|
order.order_items.each do |item|
|
||||||
|
ProductService.update_stock(item.orderable, 'external', 'sold', -item.quantity)
|
||||||
|
end
|
||||||
|
order.reload
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
36
app/services/payments/payzen_service.rb
Normal file
36
app/services/payments/payzen_service.rb
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Provides methods for pay cart by PayZen
|
||||||
|
class Payments::PayzenService
|
||||||
|
require 'pay_zen/helper'
|
||||||
|
require 'pay_zen/order'
|
||||||
|
require 'pay_zen/charge'
|
||||||
|
require 'pay_zen/service'
|
||||||
|
include Payments::PaymentConcern
|
||||||
|
|
||||||
|
def payment(order)
|
||||||
|
amount = debit_amount(order)
|
||||||
|
|
||||||
|
id = PayZen::Helper.generate_ref(order, order.statistic_profile.user.id)
|
||||||
|
|
||||||
|
client = PayZen::Charge.new
|
||||||
|
result = client.create_payment(amount: PayZen::Service.new.payzen_amount(amount),
|
||||||
|
order_id: id,
|
||||||
|
customer: PayZen::Helper.generate_customer(order.statistic_profile.user.id,
|
||||||
|
order.statistic_profile.user.id, order))
|
||||||
|
{ order: order, payment: { formToken: result['answer']['formToken'], orderId: id } }
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_payment(order, payment_id)
|
||||||
|
client = PayZen::Order.new
|
||||||
|
payzen_order = client.get(payment_id, operation_type: 'DEBIT')
|
||||||
|
|
||||||
|
if payzen_order['answer']['transactions'].any? { |transaction| transaction['status'] == 'PAID' }
|
||||||
|
o = payment_success(order)
|
||||||
|
{ order: o }
|
||||||
|
else
|
||||||
|
order.update(payment_state: 'failed')
|
||||||
|
{ order: order, payment_error: payzen_order['answer'] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
43
app/services/payments/stripe_service.rb
Normal file
43
app/services/payments/stripe_service.rb
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Provides methods for pay cart by Stripe
|
||||||
|
class Payments::StripeService
|
||||||
|
require 'stripe/service'
|
||||||
|
include Payments::PaymentConcern
|
||||||
|
|
||||||
|
def payment(order, payment_id)
|
||||||
|
amount = debit_amount(order)
|
||||||
|
# Create the PaymentIntent
|
||||||
|
intent = Stripe::PaymentIntent.create(
|
||||||
|
{
|
||||||
|
payment_method: payment_id,
|
||||||
|
amount: Stripe::Service.new.stripe_amount(amount),
|
||||||
|
currency: Setting.get('stripe_currency'),
|
||||||
|
confirmation_method: 'manual',
|
||||||
|
confirm: true,
|
||||||
|
customer: order.statistic_profile.user.payment_gateway_object.gateway_object_id
|
||||||
|
}, { api_key: Setting.get('stripe_secret_key') }
|
||||||
|
)
|
||||||
|
|
||||||
|
if intent&.status == 'succeeded'
|
||||||
|
o = payment_success(order)
|
||||||
|
return { order: o }
|
||||||
|
end
|
||||||
|
|
||||||
|
if intent&.status == 'requires_action' && intent&.next_action&.type == 'use_stripe_sdk'
|
||||||
|
{ order: order, payment: { requires_action: true, payment_intent_client_secret: intent.client_secret,
|
||||||
|
type: 'payment' } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_payment(order, payment_id)
|
||||||
|
intent = Stripe::PaymentIntent.confirm(payment_id, {}, { api_key: Setting.get('stripe_secret_key') })
|
||||||
|
if intent&.status == 'succeeded'
|
||||||
|
o = payment_success(order)
|
||||||
|
{ order: o }
|
||||||
|
else
|
||||||
|
order.update(payment_state: 'failed')
|
||||||
|
{ order: order, payment_error: 'payment failed' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -22,4 +22,12 @@ class ProductService
|
|||||||
end
|
end
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.update_stock(product, stock_type, reason, quantity)
|
||||||
|
remaining_stock = product.stock[stock_type] + quantity
|
||||||
|
product.product_stock_movements.create(stock_type: stock_type, reason: reason, quantity: quantity, remaining_stock: remaining_stock,
|
||||||
|
date: DateTime.current)
|
||||||
|
product.stock[stock_type] = remaining_stock
|
||||||
|
product.save
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -82,6 +82,8 @@ class WalletService
|
|||||||
def self.wallet_amount_debit(payment, user, coupon = nil)
|
def self.wallet_amount_debit(payment, user, coupon = nil)
|
||||||
total = if payment.is_a? PaymentSchedule
|
total = if payment.is_a? PaymentSchedule
|
||||||
payment.payment_schedule_items.first.amount
|
payment.payment_schedule_items.first.amount
|
||||||
|
elsif payment.is_a? Order
|
||||||
|
payment.amount
|
||||||
else
|
else
|
||||||
payment.total
|
payment.total
|
||||||
end
|
end
|
||||||
@ -106,10 +108,9 @@ class WalletService
|
|||||||
# wallet debit success
|
# wallet debit success
|
||||||
raise DebitWalletError unless wallet_transaction
|
raise DebitWalletError unless wallet_transaction
|
||||||
|
|
||||||
payment.set_wallet_transaction(wallet_amount, wallet_transaction.id)
|
payment.set_wallet_transaction(wallet_amount, wallet_transaction.id) unless payment.is_a? Order
|
||||||
else
|
else
|
||||||
payment.set_wallet_transaction(wallet_amount, nil)
|
payment.set_wallet_transaction(wallet_amount, nil) unless payment.is_a? Order
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
json.extract! order, :id, :token, :statistic_profile_id, :operator_id, :reference, :state, :created_at
|
json.extract! order, :id, :token, :statistic_profile_id, :operator_id, :reference, :state, :created_at
|
||||||
json.amount order.amount / 100.0 if order.amount.present?
|
json.amount order.amount / 100.0 if order.amount.present?
|
||||||
|
json.user do
|
||||||
|
json.extract! order&.statistic_profile&.user, :id
|
||||||
|
json.role order&.statistic_profile&.user&.roles&.first&.name
|
||||||
|
json.name order&.statistic_profile&.user&.profile&.full_name
|
||||||
|
end
|
||||||
|
|
||||||
json.order_items_attributes order.order_items do |item|
|
json.order_items_attributes order.order_items do |item|
|
||||||
json.id item.id
|
json.id item.id
|
||||||
|
@ -159,6 +159,11 @@ Rails.application.routes.draw do
|
|||||||
put 'add_item', on: :collection
|
put 'add_item', on: :collection
|
||||||
put 'remove_item', on: :collection
|
put 'remove_item', on: :collection
|
||||||
put 'set_quantity', on: :collection
|
put 'set_quantity', on: :collection
|
||||||
|
put 'set_customer', on: :collection
|
||||||
|
end
|
||||||
|
resources :checkout, only: %i[] do
|
||||||
|
post 'payment', on: :collection
|
||||||
|
post 'confirm_payment', on: :collection
|
||||||
end
|
end
|
||||||
|
|
||||||
# for admin
|
# for admin
|
||||||
|
5
db/migrate/20220822081222_add_payment_state_to_order.rb
Normal file
5
db/migrate/20220822081222_add_payment_state_to_order.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class AddPaymentStateToOrder < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :orders, :payment_state, :string
|
||||||
|
end
|
||||||
|
end
|
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2022_08_18_160821) do
|
ActiveRecord::Schema.define(version: 2022_08_22_081222) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "fuzzystrmatch"
|
enable_extension "fuzzystrmatch"
|
||||||
@ -467,6 +467,7 @@ ActiveRecord::Schema.define(version: 2022_08_18_160821) do
|
|||||||
t.integer "amount"
|
t.integer "amount"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.string "payment_state"
|
||||||
t.index ["statistic_profile_id"], name: "index_orders_on_statistic_profile_id"
|
t.index ["statistic_profile_id"], name: "index_orders_on_statistic_profile_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -59,10 +59,24 @@ class PayZen::Helper
|
|||||||
def generate_shopping_cart(cart_items, customer, operator)
|
def generate_shopping_cart(cart_items, customer, operator)
|
||||||
cart = if cart_items.is_a? ShoppingCart
|
cart = if cart_items.is_a? ShoppingCart
|
||||||
cart_items
|
cart_items
|
||||||
|
elsif cart_items.is_a? Order
|
||||||
|
cart_items
|
||||||
else
|
else
|
||||||
cs = CartService.new(operator)
|
cs = CartService.new(operator)
|
||||||
cs.from_hash(cart_items)
|
cs.from_hash(cart_items)
|
||||||
end
|
end
|
||||||
|
if cart.is_a? Order
|
||||||
|
return {
|
||||||
|
cartItemInfo: cart.order_items.map do |item|
|
||||||
|
{
|
||||||
|
productAmount: item.amount.to_i.to_s,
|
||||||
|
productLabel: item.orderable_id,
|
||||||
|
productQty: item.quantity.to_s,
|
||||||
|
productType: customer.organization? ? 'SERVICE_FOR_BUSINESS' : 'SERVICE_FOR_INDIVIDUAL'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
{
|
{
|
||||||
cartItemInfo: cart.items.map do |item|
|
cartItemInfo: cart.items.map do |item|
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user