1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-28 09:24:24 +01:00

WIP: array of items

Migration from cart_items:{reservation:{}, subscription:{}, ...}
to cart_items:{items:[{reservation:{}, ...}], ...}
This commit is contained in:
Sylvain 2021-05-19 18:12:52 +02:00
parent dd1d05cc3a
commit 66f81a975e
47 changed files with 773 additions and 619 deletions

View File

@ -12,7 +12,9 @@
- Fix a bug: unable to cancel the upgrade before it begins
- Fix a bug: unable to use run.fab.mn
- Fix a bug: typo in allow/prevent booking overlapping slots
- Fix a bug: in the admin calendar, the trainings' info panel shows "duration: null minutes"
- `SUPERADMIN_EMAIL` renamed to `ADMINSYS_EMAIL`
- `scripts/run-tests.sh` renamed to `scripts/tests.sh`
- [BREAKING CHANGE] GET `open_api/v1/invoices` won't return `stp_invoice_id` OR `stp_payment_intent_id` anymore. The new field `payment_gateway_object` will contain some similar data if the invoice was paid online by card.
- [TODO DEPLOY] `rails fablab:stripe:set_gateway`
- [TODO DEPLOY] `rails fablab:maintenance:rebuild_stylesheet`

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
# API Controller for handling local payments (at the reception) or when the amount = 0
class API::LocalPaymentController < API::PaymentsController
def confirm_payment
cart = shopping_cart
price = debit_amount(cart)
authorize LocalPaymentContext.new(cart, price[:amount])
if cart.reservation
res = on_reservation_success(nil, nil, price[:details], cart)
elsif cart.subscription
res = on_subscription_success(nil, nil, price[:details], cart)
end
render res
end
protected
def shopping_cart
cs = CartService.new(current_user)
cs.from_hash(params)
end
end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
# Abstract API Controller to be extended by each gateway, for handling the payments processes in the front-end
# Abstract API Controller to be extended by each payment gateway/mean, for handling the payments processes in the front-end
class API::PaymentsController < API::ApiController
before_action :authenticate_user!
@ -21,9 +21,7 @@ class API::PaymentsController < API::ApiController
wallet_amount >= total_amount ? total_amount : wallet_amount
end
def card_amount
cs = CartService.new(current_user)
cart = cs.from_hash(params[:cart_items])
def debit_amount(cart)
price_details = cart.total
# Subtract wallet amount from total
@ -32,30 +30,33 @@ class API::PaymentsController < API::ApiController
{ amount: total - wallet_debit, details: price_details }
end
def check_coupon
return if coupon_params[:coupon_code].nil?
coupon = Coupon.find_by(code: coupon_params[:coupon_code])
raise InvalidCouponError if coupon.nil? || coupon.status(current_user.id) != 'active'
def shopping_cart
cs = CartService.new(current_user)
cs.from_hash(params[:cart_items])
end
def check_plan
plan_id = (cart_items_params[:subscription][:plan_id] if cart_items_params[:subscription])
# @param cart {ShoppingCart}
def check_coupon(cart)
return if cart.coupon.nil?
return unless plan_id
cart.coupon.coupon
end
plan = Plan.find(plan_id)
# @param cart {ShoppingCart}
def check_plan(cart)
return unless cart.subscription
plan = cart.subscription.plan
raise InvalidGroupError if plan.group_id != current_user.group_id
end
def on_reservation_success(gateway_item_id, gateway_item_type, details)
@reservation = Reservation.new(reservation_params)
if params[:cart_items][:subscription] && params[:cart_items][:subscription][:plan_id]
@reservation.plan_id = params[:cart_items][:subscription][:plan_id]
end
payment_method = params[:cart_items][:reservation][:payment_method] || 'card'
def on_reservation_success(gateway_item_id, gateway_item_type, details, cart)
@reservation = cart.reservation.to_reservation
@reservation.plan_id = cart.subscription.plan.id if cart.subscription
payment_method = cart.payment_method || 'card'
user_id = if current_user.admin? || current_user.manager?
params[:cart_items][:reservation][:user_id]
cart.customer.id
else
current_user.id
end
@ -64,7 +65,7 @@ class API::PaymentsController < API::ApiController
payment_details: details,
payment_id: gateway_item_id,
payment_type: gateway_item_type,
schedule: params[:cart_items][:payment_schedule],
schedule: cart.payment_schedule.requested,
payment_method: payment_method)
post_reservation_save(gateway_item_id, gateway_item_type)
@ -77,10 +78,10 @@ class API::PaymentsController < API::ApiController
end
end
def on_subscription_success(gateway_item_id, gateway_item_type, details)
@subscription = Subscription.new(subscription_params)
def on_subscription_success(gateway_item_id, gateway_item_type, details, cart)
@subscription = cart.subscription.to_subscription
user_id = if current_user.admin? || current_user.manager?
params[:cart_items][:customer_id]
cart.customer.id
else
current_user.id
end
@ -89,8 +90,8 @@ class API::PaymentsController < API::ApiController
payment_details: details,
payment_id: gateway_item_id,
payment_type: gateway_item_type,
schedule: params[:cart_items][:subscription][:payment_schedule],
payment_method: 'card')
schedule: cart.payment_schedule.requested,
payment_method: cart.payment_method || 'card')
post_subscription_save(gateway_item_id, gateway_item_type)
@ -100,27 +101,4 @@ class API::PaymentsController < API::ApiController
{ json: @subscription.errors, status: :unprocessable_entity }
end
end
def reservation_params
params[:cart_items].require(:reservation).permit(:reservable_id, :reservable_type, :nb_reserve_places,
tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered])
end
def subscription_params
params[:cart_items].require(:subscription).permit(:plan_id)
end
def cart_items_params
params.require(:cart_items).permit(subscription: :plan_id,
reservation: [
:reservable_id, :reservable_type, :nb_reserve_places,
tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered]
])
end
def coupon_params
params.require(:cart_items).permit(:coupon_code)
end
end

View File

@ -18,8 +18,9 @@ class API::PayzenController < API::PaymentsController
end
def create_payment
amount = card_amount
@id = PayZen::Helper.generate_ref(cart_items_params, params[:customer_id])
cart = shopping_cart
amount = debit_amount(cart)
@id = PayZen::Helper.generate_ref(params[:cart_items], params[:customer_id])
client = PayZen::Charge.new
@result = client.create_payment(amount: amount[:amount],
@ -29,7 +30,7 @@ class API::PayzenController < API::PaymentsController
end
def create_token
@id = PayZen::Helper.generate_ref(cart_items_params, params[:customer_id])
@id = PayZen::Helper.generate_ref(params[:cart_items], params[:customer_id])
client = PayZen::Charge.new
@result = client.create_token(order_id: @id,
customer: PayZen::Helper.generate_customer(params[:customer_id], current_user.id, params[:cart_items]))
@ -46,13 +47,14 @@ class API::PayzenController < API::PaymentsController
client = PayZen::Order.new
order = client.get(params[:order_id], operation_type: 'DEBIT')
amount = card_amount
cart = shopping_cart
amount = debit_amount(cart)
if order['answer']['transactions'].first['status'] == 'PAID'
if params[:cart_items][:reservation]
res = on_reservation_success(params[:order_id], amount[:details])
elsif params[:cart_items][:subscription]
res = on_subscription_success(params[:order_id], amount[:details])
if cart.reservation
res = on_reservation_success(params[:order_id], amount[:details], cart)
elsif cart.subscription
res = on_subscription_success(params[:order_id], amount[:details], cart)
end
end
@ -63,12 +65,12 @@ class API::PayzenController < API::PaymentsController
private
def on_reservation_success(order_id, details)
super(order_id, 'PayZen::Order', details)
def on_reservation_success(order_id, details, cart)
super(order_id, 'PayZen::Order', details, cart)
end
def on_subscription_success(order_id, details)
super(order_id, 'PayZen::Order', details)
def on_subscription_success(order_id, details, cart)
super(order_id, 'PayZen::Order', details, cart)
end
def error_handling

View File

@ -47,18 +47,4 @@ class API::PricesController < API::ApiController
def price_params
params.require(:price).permit(:amount)
end
def compute_reservation_price_params
params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places, :payment_schedule,
tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered])
end
def compute_subscription_price_params
params.require(:subscription).permit(:plan_id, :user_id, :payment_schedule)
end
def coupon_params
params.permit(:coupon_code)
end
end

View File

@ -24,35 +24,6 @@ class API::ReservationsController < API::ApiController
def show; end
# Admins can create any reservations. Members can directly create reservations if total = 0,
# otherwise, they must use payments_controller#confirm_payment.
# Managers can create reservations for other users
def create
user_id = current_user.admin? || current_user.manager? ? params[:customer_id] : current_user.id
price = transaction_amount(user_id)
authorize ReservationContext.new(Reservation, price[:amount], user_id)
@reservation = Reservation.new(reservation_params)
@reservation.plan_id = params[:subscription][:plan_id] if params[:subscription] && params[:subscription][:plan_id]
is_reserve = Reservations::Reserve.new(user_id, current_user.invoicing_profile.id)
.pay_and_save(@reservation,
payment_details: price[:price_details],
schedule: params[:payment_schedule],
payment_method: params[:payment_method])
if is_reserve
SubscriptionExtensionAfterReservation.new(@reservation).extend_subscription_if_eligible
render :show, status: :created, location: @reservation
else
render json: @reservation.errors, status: :unprocessable_entity
end
rescue InvalidCouponError
render json: { coupon_code: 'wrong coupon code or expired' }, status: :unprocessable_entity
end
def update
authorize @reservation
if @reservation.update(reservation_params)
@ -64,30 +35,6 @@ class API::ReservationsController < API::ApiController
private
def transaction_amount(user_id)
user = User.find(user_id)
cs = CartService.new(current_user)
cart = cs.from_hash(customer_id: user_id,
subscription: {
plan_id: params[:subscription] ? params[:subscription][:plan_id] : nil
},
reservation: reservation_params,
coupon_code: coupon_params[:coupon_code],
payment_schedule: params[:payment_schedule])
price_details = cart.total
# Subtract wallet amount from total
total = price_details[:total]
wallet_debit = get_wallet_debit(user, total)
{ price_details: price_details, amount: (total - wallet_debit) }
end
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 set_reservation
@reservation = Reservation.find(params[:id])
end
@ -97,8 +44,4 @@ class API::ReservationsController < API::ApiController
tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered])
end
def coupon_params
params.permit(:coupon_code)
end
end

View File

@ -16,11 +16,12 @@ class API::StripeController < API::PaymentsController
intent = nil # stripe's payment intent
res = nil # json of the API answer
cart = shopping_cart
begin
amount = card_amount
amount = debit_amount(cart)
if params[:payment_method_id].present?
check_coupon
check_plan
check_coupon(cart)
check_plan(cart)
# Create the PaymentIntent
intent = Stripe::PaymentIntent.create(
@ -46,10 +47,10 @@ class API::StripeController < API::PaymentsController
end
if intent&.status == 'succeeded'
if params[:cart_items][:reservation]
res = on_reservation_success(intent, amount[:details])
elsif params[:cart_items][:subscription]
res = on_subscription_success(intent, amount[:details])
if cart.reservation
res = on_reservation_success(intent, amount[:details], cart)
elsif cart.subscription
res = on_subscription_success(intent, amount[:details], cart)
end
end
@ -79,12 +80,13 @@ class API::StripeController < API::PaymentsController
key = Setting.get('stripe_secret_key')
intent = Stripe::SetupIntent.retrieve(params[:setup_intent_id], api_key: key)
amount = card_amount
cart = shopping_cart
amount = debit_amount(cart)
if intent&.status == 'succeeded'
if params[:cart_items][:reservation]
res = on_reservation_success(intent, amount[:details])
elsif params[:cart_items][:subscription]
res = on_subscription_success(intent, amount[:details])
if cart.reservation
res = on_reservation_success(intent, amount[:details], cart)
elsif cart.subscription
res = on_subscription_success(intent, amount[:details], cart)
end
end
@ -126,12 +128,12 @@ class API::StripeController < API::PaymentsController
)
end
def on_reservation_success(intent, details)
super(intent.id, intent.class.name, details)
def on_reservation_success(intent, details, cart)
super(intent.id, intent.class.name, details, cart)
end
def on_subscription_success(intent, details)
super(intent.id, intent.class.name, details)
def on_subscription_success(intent, details, cart)
super(intent.id, intent.class.name, details, cart)
end
def generate_payment_response(intent, res = nil)

View File

@ -9,28 +9,6 @@ class API::SubscriptionsController < API::ApiController
authorize @subscription
end
# Admins can create any subscriptions. Members can directly create subscriptions if total = 0,
# otherwise, they must use payments_controller#confirm_payment.
# Managers can create subscriptions for other users
def create
user_id = current_user.admin? || current_user.manager? ? params[:customer_id] : current_user.id
transaction = transaction_amount(user_id)
authorize SubscriptionContext.new(Subscription, transaction[:amount], user_id)
@subscription = Subscription.new(subscription_params)
is_subscribe = Subscriptions::Subscribe.new(current_user.invoicing_profile.id, user_id)
.pay_and_save(@subscription, payment_details: transaction[:details],
schedule: params[:subscription][:payment_schedule],
payment_method: params[:subscription][:payment_method])
if is_subscribe
render :show, status: :created, location: @subscription
else
render json: @subscription.errors, status: :unprocessable_entity
end
end
def update
authorize @subscription
@ -50,42 +28,11 @@ class API::SubscriptionsController < API::ApiController
private
def transaction_amount(user_id)
cs = CartService.new(current_user)
cart = cs.from_hash(customer_id: user_id,
subscription: {
plan_id: subscription_params[:plan_id]
},
coupon_code: coupon_params[:coupon_code],
payment_schedule: params[:payment_schedule])
price_details = cart.total
user = User.find(user_id)
# Subtract wallet amount from total
total = price_details[:total]
wallet_debit = get_wallet_debit(user, total)
{ amount: total - wallet_debit, details: price_details }
end
def get_wallet_debit(user, total_amount)
wallet_amount = (user.wallet.amount * 100).to_i
wallet_amount >= total_amount ? total_amount : wallet_amount
end
# Use callbacks to share common setup or constraints between actions.
def set_subscription
@subscription = Subscription.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def subscription_params
params.require(:subscription).permit(:plan_id)
end
def coupon_params
params.permit(:coupon_code)
end
def subscription_update_params
params.require(:subscription).permit(:expired_at)
end

View File

@ -657,12 +657,12 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* @param reservation {Object} as returned by mkReservation()
* @param coupon {Object} Coupon as returned from the API
* @param paymentMethod {string} 'card' | ''
* @return {{reservation:Object, coupon_code:string}}
* @return {CartItems}
*/
const mkCartItems = function (reservation, coupon, paymentMethod = '') {
return {
customer_id: $scope.ctrl.member.id,
reservation,
items: [reservation],
coupon_code: ((coupon ? coupon.code : undefined)),
payment_method: paymentMethod,
};
@ -733,8 +733,8 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
return mkCartItems(reservation, $scope.coupon.applied);
},
},
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems',
function ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon, cartItems) {
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'LocalPayment', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems',
function ($scope, $uibModalInstance, $state, reservation, price, Auth, LocalPayment, wallet, helpers, $filter, coupon, cartItems) {
// User's wallet amount
$scope.wallet = wallet;
@ -767,7 +767,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
// Callback to validate the payment
$scope.ok = function () {
$scope.attempting = true;
return Reservation.save(mkCartItems($scope.reservation, coupon), function (reservation) {
return LocalPayment.confirm(cartItems, function (reservation) {
$uibModalInstance.close(reservation);
return $scope.attempting = true;
}

View File

@ -687,16 +687,13 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
* @return {CartItems}
*/
const mkCartItems = function (items, paymentMethod = '') {
const cartItems = {
return {
customer_id: $scope.user.id,
items,
payment_schedule: $scope.schedule.requested_schedule,
payment_method: paymentMethod,
coupon_code: (($scope.coupon.applied ? $scope.coupon.applied.code : undefined))
};
for (const item of items) {
Object.assign(cartItems, item);
}
return cartItems;
};
/**
@ -745,8 +742,8 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
return $scope.settings;
}
},
controller: ['$scope', '$uibModalInstance', '$state', 'price', 'Auth', 'Reservation', 'Subscription', 'wallet', 'helpers', '$filter', 'coupon', 'selectedPlan', 'schedule', 'cartItems', 'user', 'settings',
function ($scope, $uibModalInstance, $state, price, Auth, Reservation, Subscription, wallet, helpers, $filter, coupon, selectedPlan, schedule, cartItems, user, settings) {
controller: ['$scope', '$uibModalInstance', '$state', 'price', 'Auth', 'LocalPayment', 'wallet', 'helpers', '$filter', 'coupon', 'selectedPlan', 'schedule', 'cartItems', 'user', 'settings',
function ($scope, $uibModalInstance, $state, price, Auth, LocalPayment, wallet, helpers, $filter, coupon, selectedPlan, schedule, cartItems, user, settings) {
// user wallet amount
$scope.wallet = wallet;
@ -783,6 +780,30 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
// the customer
$scope.user = user;
/**
* Check if the shopping cart contains a reservation
* @return {Reservation|boolean}
*/
$scope.reservation = (function () {
const item = cartItems.items.find(i => i.reservation);
if (item && item.reservation.slots_attributes.length > 0) {
return item.reservation;
}
return false;
})();
/**
* Check if the shopping cart contains a subscription
* @return {Subscription|boolean}
*/
$scope.subscription = (function () {
const item = cartItems.items.find(i => i.subscription);
if (item && item.subscription.plan_id) {
return item.subscription;
}
return false;
})();
/**
* Callback to process the local payment, triggered on button click
*/
@ -796,22 +817,7 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
}
}
$scope.attempting = true;
// save subscription (if there's only a subscription selected)
if ((!$scope.cartItems.reservation || $scope.cartItems.reservation.slots_attributes.length === 0) && selectedPlan) {
const sub = mkSubscription(selectedPlan.id);
return Subscription.save(mkCartItems([sub], $scope.method.payment_method),
function (subscription) {
$uibModalInstance.close(subscription);
$scope.attempting = true;
}, function (response) {
$scope.alerts = [];
$scope.alerts.push({ msg: _t('app.shared.cart.a_problem_occurred_during_the_payment_process_please_try_again_later'), type: 'danger' });
$scope.attempting = false;
});
}
// otherwise, save the reservation (may include a subscription)
Reservation.save(cartItems, function (reservation) {
LocalPayment.confirm(cartItems, function (reservation) {
$uibModalInstance.close(reservation);
$scope.attempting = true;
}, function (response) {

View File

@ -21,8 +21,7 @@ export enum PaymentMethod {
export interface CartItems {
customer_id: number,
reservation?: Reservation,
subscription?: SubscriptionRequest,
items: Array<Reservation|SubscriptionRequest>,
coupon_code?: string,
payment_schedule?: boolean,
payment_method: PaymentMethod

View File

@ -0,0 +1,13 @@
'use strict';
Application.Services.factory('LocalPayment', ['$resource', function ($resource) {
return $resource('/api/local_payment',
{}, {
confirm: {
method: 'POST',
url: '/api/local_payment/confirm_payment',
isArray: false
}
}
);
}]);

View File

@ -58,12 +58,12 @@
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
</div>
<div class="widget panel b-a m m-t-lg" ng-if="availability" ng-hide="availability.available_type == 'event'">
<div class="widget panel b-a m m-t-lg" ng-if="availability" ng-show="(availability.slot_duration || availability.tags.length > 0) && availability.available_type !== 'event'">
<div class="panel-heading b-b small">
<h3><i class="fa fa-info-circle m-r" aria-hidden="true"></i><span translate>{{ 'app.admin.calendar.info' }}</span></h3>
</div>
<div class="widget-content no-bg auto wrapper">
<div translate translate-values="{DURATION: availability.slot_duration}">{{ 'app.admin.calendar.slot_duration' }}</div>
<div translate translate-values="{DURATION: availability.slot_duration}" ng-show="availability.slot_duration">{{ 'app.admin.calendar.slot_duration' }}</div>
<div class="m-t-sm" ng-show="availability.tags.length > 0">
<span translate>{{ 'app.admin.calendar.tags' }}</span>
<ul>

View File

@ -1,19 +1,19 @@
<div class="modal-header">
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
<h1 translate ng-show="cartItems.reservation">{{ 'app.shared.valid_reservation_modal.booking_confirmation' }}</h1>
<h1 translate ng-show="!cartItems.reservation && cartItems.subscription">{{ 'app.shared.valid_reservation_modal.subscription_confirmation' }}</h1>
<h1 translate ng-show="reservation">{{ 'app.shared.valid_reservation_modal.booking_confirmation' }}</h1>
<h1 translate ng-show="!reservation && subscription">{{ 'app.shared.valid_reservation_modal.subscription_confirmation' }}</h1>
</div>
<div class="modal-body">
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
<div class="row">
<div ng-class="{'col-md-6': schedule, 'm-h-sm': !schedule}">
<div ng-if="cartItems.reservation.slots_attributes.length > 0">
<div ng-if="reservation">
<p translate>{{ 'app.shared.valid_reservation_modal.here_is_the_summary_of_the_slots_to_book_for_the_current_user' }}</p>
<ul ng-repeat="slot in cartItems.reservation.slots_attributes">
<ul ng-repeat="slot in reservation.slots_attributes">
<li><strong>{{slot.start_at | amDateFormat: 'LL'}} : {{slot.start_at | amDateFormat:'LT'}} - {{slot.end_at | amDateFormat:'LT'}}</strong></li>
</ul>
</div>
<div ng-if="cartItems.subscription">
<div ng-if="subscription">
<p translate>{{ 'app.shared.valid_reservation_modal.here_is_the_subscription_summary' }}</p>
<p>{{ plan | humanReadablePlanName }}</p>
</div>

View File

@ -34,7 +34,23 @@ class CartItem::EventReservation < CartItem::Reservation
{ elements: elements, amount: total }
end
def to_reservation
::Reservation.new(
reservable_id: @reservable.id,
reservable_type: Event.name,
slots_attributes: slots_params,
tickets_attributes: tickets_params,
nb_reserve_places: @normal_tickets
)
end
def name
@reservable.title
end
protected
def tickets_params
@other_tickets.map { |ticket| ticket.permit(:event_price_category_id, :booked) }
end
end

View File

@ -12,6 +12,14 @@ class CartItem::MachineReservation < CartItem::Reservation
@new_subscription = new_subscription
end
def to_reservation
::Reservation.new(
reservable_id: @reservable.id,
reservable_type: Machine.name,
slots_attributes: slots_params
)
end
protected
def credits

View File

@ -2,6 +2,8 @@
# A payment schedule applied to plan in the shopping cart
class CartItem::PaymentSchedule
attr_reader :requested
def initialize(plan, coupon, requested)
raise TypeError unless coupon.is_a? CartItem::Coupon

View File

@ -33,6 +33,10 @@ class CartItem::Reservation < CartItem::BaseItem
@reservable.name
end
def to_reservation
nil
end
protected
def credits
@ -84,4 +88,8 @@ class CartItem::Reservation < CartItem::BaseItem
end
hours_available
end
def slots_params
@slots.map { |slot| slot.permit(:id, :start_at, :end_at, :availability_id, :offered) }
end
end

View File

@ -12,6 +12,14 @@ class CartItem::SpaceReservation < CartItem::Reservation
@new_subscription = new_subscription
end
def to_reservation
::Reservation.new(
reservable_id: @reservable.id,
reservable_type: Space.name,
slots_attributes: slots_params
)
end
protected
def credits

View File

@ -2,6 +2,8 @@
# A subscription added to the shopping cart
class CartItem::Subscription < CartItem::BaseItem
attr_reader :plan
def initialize(plan)
raise TypeError unless plan.is_a? Plan
@ -18,4 +20,10 @@ class CartItem::Subscription < CartItem::BaseItem
def name
@plan.name
end
def to_subscription
Subscription.new(
plan_id: @plan.id
)
end
end

View File

@ -32,6 +32,14 @@ class CartItem::TrainingReservation < CartItem::Reservation
{ elements: elements, amount: amount }
end
def to_reservation
::Reservation.new(
reservable_id: @reservable.id,
reservable_type: Training.name,
slots_attributes: slots_params
)
end
protected
def credits

View File

@ -80,6 +80,6 @@ class Machine < ApplicationRecord
private
def update_gateway_product
PaymentGatewayService.create_or_update_product(Machine.name, id)
PaymentGatewayService.new.create_or_update_product(Machine.name, id)
end
end

View File

@ -131,6 +131,6 @@ class Plan < ApplicationRecord
end
def update_gateway_product
PaymentGatewayService.create_or_update_product(Plan.name, id)
PaymentGatewayService.new.create_or_update_product(Plan.name, id)
end
end

View File

@ -18,6 +18,14 @@ class ShoppingCart
@payment_schedule = payment_schedule
end
def subscription
@items.find { |item| item.is_a? CartItem::Subscription }
end
def reservation
@items.find { |item| item.is_a? CartItem::Reservation }
end
# compute the price details of the current shopping cart
def total
total_amount = 0

View File

@ -68,6 +68,6 @@ class Space < ApplicationRecord
private
def update_gateway_product
PaymentGatewayService.create_or_update_product(Space.name, id)
PaymentGatewayService.new.create_or_update_product(Space.name, id)
end
end

View File

@ -70,6 +70,6 @@ class Training < ApplicationRecord
end
def update_gateway_product
PaymentGatewayService.create_or_update_product(Training.name, id)
PaymentGatewayService.new.create_or_update_product(Training.name, id)
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
# Pundit Additional context to validate the price of a local payment
class LocalPaymentContext
attr_reader :shopping_cart, :price
def initialize(shopping_cart, price)
@shopping_cart = shopping_cart
@price = price
end
def policy_class
LocalPaymentPolicy
end
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
# Check the access policies for API::LocalPaymentsController
class LocalPaymentPolicy < ApplicationPolicy
def confirm_payment?
user.admin? || (user.manager? && record.shopping_cart.customer.id != user.id) || record.price.zero?
end
end

View File

@ -15,8 +15,13 @@ class CartService
plan_info = plan(cart_items)
items = []
items.push(CartItem::Subscription.new(plan_info[:plan])) if plan_info[:new_subscription]
items.push(reservable_from_hash(cart_items[:reservation], plan_info)) if cart_items[:reservation]
cart_items[:items].each do |item|
if item.keys.first == 'subscription'
items.push(CartItem::Subscription.new(plan_info[:plan])) if plan_info[:new_subscription]
elsif item.keys.first == 'reservation'
items.push(reservable_from_hash(item[:reservation], plan_info))
end
end
coupon = CartItem::Coupon.new(@customer, @operator, cart_items[:coupon_code])
schedule = CartItem::PaymentSchedule.new(plan_info[:plan], coupon, cart_items[:payment_schedule])
@ -33,14 +38,16 @@ class CartService
private
def plan(cart_items)
plan = if cart_items[:subscription] && cart_items[:subscription][:plan_id]
new_plan_being_bought = true
Plan.find(cart_items[:subscription][:plan_id])
new_plan_being_bought = false
plan = if cart_items[:items].any? { |item| item.keys.first == 'subscription' }
index = cart_items[:items].index { |item| item.keys.first == 'subscription' }
if cart_items[:items][index][:subscription][:plan_id]
new_plan_being_bought = true
Plan.find(cart_items[:items][index][:subscription][:plan_id])
end
elsif @customer.subscribed_plan
new_plan_being_bought = false
@customer.subscribed_plan
else
new_plan_being_bought = false
nil
end
{ plan: plan, new_subscription: new_plan_being_bought }

View File

@ -3,16 +3,17 @@
# create remote items on currently active payment gateway
class PaymentGatewayService
def initialize
@gateway = if Stripe::Helper.enabled?
require 'stripe/service'
Stripe::Service
elsif PayZen::Helper.enabled?
require 'pay_zen/service'
PayZen::Service
else
require 'payment/service'
Payment::Service
end
service = if Stripe::Helper.enabled?
require 'stripe/service'
Stripe::Service
elsif PayZen::Helper.enabled?
require 'pay_zen/service'
PayZen::Service
else
require 'payment/service'
Payment::Service
end
@gateway = service.new
end
def create_subscription(payment_schedule, gateway_object_id)

View File

@ -65,9 +65,13 @@ class Subscriptions::Subscribe
operator = InvoicingProfile.find(@operator_profile_id).user
cs = CartService.new(operator)
cart = cs.from_hash(customer_id: subscription.user.id,
subscription: {
plan_id: subscription.plan_id
},
items: [
{
subscription: {
plan_id: subscription.plan_id
}
}
],
payment_schedule: !schedule.nil?)
details = cart.total

View File

@ -57,7 +57,7 @@ Rails.application.routes.draw do
patch ':id/complete_tour', action: 'complete_tour', on: :collection
patch ':id/update_role', action: 'update_role', on: :collection
end
resources :reservations, only: %i[show create index update]
resources :reservations, only: %i[show index update]
resources :notifications, only: %i[index show update] do
match :update_all, path: '/', via: %i[put patch], on: :collection
get 'polling', action: 'polling', on: :collection
@ -95,7 +95,7 @@ Rails.application.routes.draw do
end
resources :groups, only: %i[index create update destroy]
resources :subscriptions, only: %i[show create update]
resources :subscriptions, only: %i[show update]
resources :plans, only: %i[index create update destroy show]
resources :slots, only: [:update] do
put 'cancel', on: :member
@ -186,6 +186,9 @@ Rails.application.routes.draw do
post 'payzen/check_hash' => 'payzen#check_hash'
post 'payzen/create_token' => 'payzen#create_token'
# local payments handling
post 'local_payment/confirm_payment' => 'local_payment#confirm_payment'
# FabAnalytics
get 'analytics/data' => 'analytics#data'

View File

@ -155,7 +155,7 @@ This procedure is not easy to follow so if you don't need to write some code for
<a name="tests"></a>
## Tests
Run the test suite with `./scripts/run-tests.sh`.
Run the test suite with `./scripts/tests.sh`.
Pleas note: If you haven't set the Stripe's API keys in your `.env` file, the script will ask for them.
You must provide valid Stripe API **test keys** for the test suite to run.

View File

@ -25,7 +25,7 @@ This document is listing common known issues.
To solve this issue copy `env.example` to `.env`.
This is required before the first start.
- When running the tests suite with `scripts/run-tests.sh`, all tests may fail with errors similar to the following:
- When running the tests suite with `scripts/tests.sh`, all tests may fail with errors similar to the following:
Error:
...

View File

@ -55,7 +55,7 @@ docker-compose up -d
- Install and configure the PostgreSQL extension [pgextwlist](https://github.com/dimitri/pgextwlist).
Please follow the instructions detailed on the extension website to whitelist `unaccent` and `trigram` for the user configured in `config/database.yml`.
- If you intend to contribute to the project code, you will need to run the test suite with `scripts/run-tests.sh`.
- If you intend to contribute to the project code, you will need to run the test suite with `scripts/tests.sh`.
This also requires your user to have the _SUPERUSER_ role.
Please see the [known issues](known-issues.md) documentation for more information about this.

View File

@ -9,7 +9,7 @@ invoice_1:
invoicing_profile_id: 3
statistic_profile_id: 3
reference: 1604001/VL
payment_method:
payment_method: card
avoir_date:
invoice_id:
type:

View File

@ -62,22 +62,26 @@ module Events
assert_equal 10, e.nb_free_places, 'Number of free places was not updated'
# Now, let's make a reservation on this event
post '/api/reservations',
post '/api/local_payment/confirm_payment',
params: {
customer_id: User.find_by(username: 'pdurand').id,
reservation: {
reservable_id: e.id,
reservable_type: 'Event',
nb_reserve_places: 2,
slots_attributes: [
{
start_at: e.availability.start_at,
end_at: e.availability.end_at,
availability_id: e.availability.id,
offered: false
items: [
{
reservation: {
reservable_id: e.id,
reservable_type: 'Event',
nb_reserve_places: 2,
slots_attributes: [
{
start_at: e.availability.start_at,
end_at: e.availability.end_at,
availability_id: e.availability.id,
offered: false
}
]
}
]
}
}
]
}.to_json,
headers: default_headers
@ -156,28 +160,32 @@ module Events
assert_equal 10, e.nb_free_places, 'Number of free places was not updated'
# Now, let's make a reservation on this event
post '/api/reservations',
post '/api/local_payment/confirm_payment',
params: {
customer_id: User.find_by(username: 'lseguin').id,
reservation: {
reservable_id: e.id,
reservable_type: 'Event',
nb_reserve_places: 4,
slots_attributes: [
{
start_at: e.availability.start_at,
end_at: e.availability.end_at,
availability_id: e.availability.id,
offered: false
items: [
{
reservation: {
reservable_id: e.id,
reservable_type: 'Event',
nb_reserve_places: 4,
slots_attributes: [
{
start_at: e.availability.start_at,
end_at: e.availability.end_at,
availability_id: e.availability.id,
offered: false
}
],
tickets_attributes: [
{
event_price_category_id: e.event_price_categories.first.id,
booked: 4
}
]
}
],
tickets_attributes: [
{
event_price_category_id: e.event_price_categories.first.id,
booked: 4
}
]
}
}
]
}.to_json,
headers: default_headers

View File

@ -34,29 +34,33 @@ class Events::AsUserTest < ActionDispatch::IntegrationTest
payment_method_id: stripe_payment_method,
cart_items: {
customer_id: User.find_by(username: 'vlonchamp').id,
reservation: {
reservable_id: radio.id,
reservable_type: 'Event',
nb_reserve_places: 2,
slots_attributes: [
{
start_at: availability.start_at,
end_at: availability.end_at,
availability_id: availability.id,
offered: false
items: [
{
reservation: {
reservable_id: radio.id,
reservable_type: 'Event',
nb_reserve_places: 2,
slots_attributes: [
{
start_at: availability.start_at,
end_at: availability.end_at,
availability_id: availability.id,
offered: false
}
],
tickets_attributes: [
{
event_price_category_id: radio.event_price_categories[0].id,
booked: 2
},
{
event_price_category_id: radio.event_price_categories[1].id,
booked: 2
}
]
}
],
tickets_attributes: [
{
event_price_category_id: radio.event_price_categories[0].id,
booked: 2
},
{
event_price_category_id: radio.event_price_categories[1].id,
booked: 2
}
]
},
}
],
coupon_code: 'SUNNYFABLAB'
}
}.to_json, headers: default_headers

View File

@ -15,18 +15,22 @@ module Prices
post '/api/prices/compute',
params: {
customer_id: user.id,
reservation: {
reservable_id: printer_training.id,
reservable_type: printer_training.class.name,
slots_attributes: [
{
availability_id: availability.id,
end_at: availability.end_at,
offered: false,
start_at: availability.start_at
items: [
{
reservation: {
reservable_id: printer_training.id,
reservable_type: printer_training.class.name,
slots_attributes: [
{
availability_id: availability.id,
end_at: availability.end_at,
offered: false,
start_at: availability.start_at
}
]
}
]
}
}
]
}.to_json,
headers: default_headers
@ -50,27 +54,33 @@ module Prices
post '/api/prices/compute',
params: {
customer_id: user.id,
reservation: {
reservable_id: laser.id,
reservable_type: laser.class.name,
slots_attributes: [
{
availability_id: availability.id,
end_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
offered: true,
start_at: availability.start_at.strftime('%Y-%m-%d %H:%M:%S.%9N Z')
},
{
availability_id: availability.id,
end_at: (availability.start_at + 2.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
offered: false,
start_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z')
items: [
{
reservation: {
reservable_id: laser.id,
reservable_type: laser.class.name,
slots_attributes: [
{
availability_id: availability.id,
end_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
offered: true,
start_at: availability.start_at.strftime('%Y-%m-%d %H:%M:%S.%9N Z')
},
{
availability_id: availability.id,
end_at: (availability.start_at + 2.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
offered: false,
start_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z')
}
]
}
]
},
subscription: {
plan_id: plan.id
}
},
{
subscription: {
plan_id: plan.id
}
}
]
}.to_json,
headers: default_headers

View File

@ -19,19 +19,23 @@ class Reservations::CreateAsAdminTest < ActionDispatch::IntegrationTest
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
post reservations_path, params: {
post '/api/local_payment/confirm_payment', params: {
customer_id: @user_without_subscription.id,
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}.to_json, headers: default_headers
# general assertions
@ -78,19 +82,21 @@ class Reservations::CreateAsAdminTest < ActionDispatch::IntegrationTest
invoice_count = Invoice.count
invoice_items_count = InvoiceItem.count
post reservations_path, params: {
post '/api/local_payment/confirm_payment', params: {
customer_id: @user_without_subscription.id,
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
items: [
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}.to_json, headers: default_headers
# general assertions
@ -137,24 +143,28 @@ class Reservations::CreateAsAdminTest < ActionDispatch::IntegrationTest
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
post reservations_path, params: {
post '/api/local_payment/confirm_payment', params: {
customer_id: @user_with_subscription.id,
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
},
{
start_at: (availability.start_at + 1.hour).to_s(:iso8601),
end_at: (availability.start_at + 2.hours).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
},
{
start_at: (availability.start_at + 1.hour).to_s(:iso8601),
end_at: (availability.start_at + 2.hours).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}.to_json, headers: default_headers
# general assertions
@ -212,19 +222,23 @@ class Reservations::CreateAsAdminTest < ActionDispatch::IntegrationTest
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
post reservations_path, params: {
post '/api/local_payment/confirm_payment', params: {
customer_id: @vlonchamp.id,
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}.to_json, headers: default_headers
# general assertions
@ -284,22 +298,28 @@ class Reservations::CreateAsAdminTest < ActionDispatch::IntegrationTest
users_credit_count = UsersCredit.count
wallet_transactions_count = WalletTransaction.count
post reservations_path, params: {
post '/api/local_payment/confirm_payment', params: {
customer_id: @vlonchamp.id,
subscription: {
plan_id: plan.id,
},
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
subscription: {
plan_id: plan.id,
}
]
}
},
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
}
]
}.to_json, headers: default_headers
# general assertions
@ -355,19 +375,23 @@ class Reservations::CreateAsAdminTest < ActionDispatch::IntegrationTest
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
post reservations_path, params: {
post '/api/local_payment/confirm_payment', params: {
customer_id: @vlonchamp.id,
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}.to_json, headers: default_headers
# general assertions
@ -400,23 +424,29 @@ class Reservations::CreateAsAdminTest < ActionDispatch::IntegrationTest
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
post reservations_path, params: {
post '/api/local_payment/confirm_payment', params: {
customer_id: @user_without_subscription.id,
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
offered: false,
availability_id: availability.id
items: [
{
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
offered: false,
availability_id: availability.id
}
]
}
]
},
subscription: {
plan_id: plan.id,
}
},
{
subscription: {
plan_id: plan.id,
}
}
]
}.to_json, headers: default_headers
# general assertions
@ -481,24 +511,30 @@ class Reservations::CreateAsAdminTest < ActionDispatch::IntegrationTest
plan = Plan.find_by(group_id: @user_without_subscription.group.id, type: 'Plan', base_name: 'Abonnement mensualisable')
VCR.use_cassette('reservations_admin_training_subscription_with_payment_schedule') do
post reservations_path, params: {
post '/api/local_payment/confirm_payment', params: {
payment_method: 'check',
payment_schedule: true,
customer_id: @user_without_subscription.id,
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
},
subscription: {
plan_id: plan.id
}
},
{
subscription: {
plan_id: plan.id
}
}
]
}.to_json, headers: default_headers
end

View File

@ -25,17 +25,21 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}
}.to_json, headers: default_headers
end
@ -95,17 +99,21 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method(error: :card_declined),
cart_items: {
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}
}.to_json, headers: default_headers
end
@ -143,17 +151,21 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: availability.end_at.to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: availability.end_at.to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}
}.to_json, headers: default_headers
end
@ -212,22 +224,26 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
},
{
start_at: (availability.start_at + 1.hour).to_s(:iso8601),
end_at: (availability.start_at + 2.hours).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
},
{
start_at: (availability.start_at + 1.hour).to_s(:iso8601),
end_at: (availability.start_at + 2.hours).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}
}.to_json, headers: default_headers
end
@ -292,19 +308,23 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
invoice_items_count = InvoiceItem.count
VCR.use_cassette('reservations_create_for_training_with_subscription_success') do
post '/api/reservations',
post '/api/local_payment/confirm_payment',
params: {
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: availability.end_at.to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: availability.end_at.to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}.to_json, headers: default_headers
end
@ -368,17 +388,21 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
payment_method_id: stripe_payment_method,
cart_items: {
customer_id: @vlonchamp.id,
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
}
}
]
}
}.to_json, headers: default_headers
end
@ -450,20 +474,26 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: availability.end_at.to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: availability.end_at.to_s(:iso8601),
availability_id: availability.id
}
]
}
]
},
subscription: {
plan_id: plan.id
}
},
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end
@ -530,20 +560,26 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
},
subscription: {
plan_id: plan.id
},
},
{
subscription: {
plan_id: plan.id
}
}
],
coupon_code: 'SUNNYFABLAB'
}
}.to_json, headers: default_headers
@ -624,18 +660,22 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
payment_method_id: stripe_payment_method,
cart_items: {
customer_id: @user_without_subscription.id,
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
card_token: stripe_payment_method,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
card_token: stripe_payment_method,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
]
},
}
],
coupon_code: 'XMAS10'
}
}.to_json, headers: default_headers
@ -700,20 +740,26 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
setup_intent_id: setup_intent[:id],
cart_items: {
payment_schedule: true,
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
reservation: {
reservable_id: training.id,
reservable_type: training.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
],
},
subscription: {
plan_id: plan.id,
}
},
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end
@ -797,20 +843,26 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
cart_items: {
coupon_code: 'GIME3EUR',
payment_schedule: true,
subscription: {
plan_id: plan.id,
},
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
items: [
{
subscription: {
plan_id: plan.id
}
]
}
},
{
reservation: {
reservable_id: machine.id,
reservable_type: machine.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
}
]
}
}.to_json, headers: default_headers
end

View File

@ -13,12 +13,16 @@ class Subscriptions::CreateAsAdminTest < ActionDispatch::IntegrationTest
plan = Plan.find_by(group_id: user.group.id, type: 'Plan', base_name: 'Mensuel')
VCR.use_cassette('subscriptions_admin_create_success') do
post '/api/subscriptions',
post '/api/local_payment/confirm_payment',
params: {
customer_id: user.id,
subscription: {
plan_id: plan.id
}
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}.to_json, headers: default_headers
end
@ -95,11 +99,15 @@ class Subscriptions::CreateAsAdminTest < ActionDispatch::IntegrationTest
setup_intent_id: setup_intent[:id],
cart_items: {
customer_id: user.id,
subscription: {
plan_id: plan.id,
payment_schedule: true,
payment_method: 'stripe'
}
payment_schedule: true,
payment_method: 'card',
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end

View File

@ -16,9 +16,13 @@ class Subscriptions::CreateAsUserTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
subscription: {
plan_id: plan.id
}
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end
@ -76,9 +80,13 @@ class Subscriptions::CreateAsUserTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
subscription: {
plan_id: plan.id
}
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end
@ -104,9 +112,13 @@ class Subscriptions::CreateAsUserTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
subscription: {
plan_id: plan.id
}
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end
@ -204,10 +216,14 @@ class Subscriptions::CreateAsUserTest < ActionDispatch::IntegrationTest
params: {
setup_intent_id: setup_intent[:id],
cart_items: {
subscription: {
plan_id: plan.id,
payment_schedule: true
}
payment_schedule: true,
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end

View File

@ -13,12 +13,16 @@ class Subscriptions::RenewAsAdminTest < ActionDispatch::IntegrationTest
plan = Plan.find_by(base_name: 'Mensuel tarif réduit')
VCR.use_cassette('subscriptions_admin_renew_success') do
post '/api/subscriptions',
post '/api/local_payment/confirm_payment',
params: {
customer_id: user.id,
subscription: {
plan_id: plan.id
}
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}.to_json, headers: default_headers
end

View File

@ -16,9 +16,13 @@ class Subscriptions::RenewAsUserTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method,
cart_items: {
subscription: {
plan_id: plan.id
}
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end
@ -80,9 +84,13 @@ class Subscriptions::RenewAsUserTest < ActionDispatch::IntegrationTest
params: {
payment_method_id: stripe_payment_method(error: :card_declined),
cart_items: {
subscription: {
plan_id: plan.id
}
items: [
{
subscription: {
plan_id: plan.id
}
}
]
}
}.to_json, headers: default_headers
end

View File

@ -19,7 +19,7 @@ VCR.configure do |config|
end
Sidekiq::Testing.fake!
Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(color: true)]
Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(color: true)] unless ENV['RM_INFO']
class ActiveSupport::TestCase
# Add more helper methods to be used by all tests here...