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

Merge branch 'cash' into dev

This commit is contained in:
Sylvain 2016-11-28 12:05:18 +01:00
commit aa7748f8eb
79 changed files with 1897 additions and 420 deletions

View File

@ -1 +1 @@
2.4.4
2.4.5-dev

View File

@ -1,5 +1,9 @@
# Changelog Fab Manager
## next release
- Ability to create coupons with cash amounts (previously only percentages were allowed)
- Fix a bug: misconfigured Twitter's ENV variables results in HTTP error 500
## v2.4.4 2016 November 24
- Fix a bug: unable to rollback migration 20160906145713

View File

@ -227,6 +227,7 @@ See https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname-
TWITTER_NAME
Identifier of the Twitter account, from witch the last tweet will be fetched and displayed on the home page.
This value can be graphically overridden during the application's lifecycle in Admin/Customization/Home page/Twitter Feed.
It will also be used for [Twitter Card analytics](https://dev.twitter.com/cards/analytics).
TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, TWITTER_ACCESS_TOKEN & TWITTER_ACCESS_TOKEN_SECRET

View File

@ -8,12 +8,13 @@ userValidities = ['once', 'forever']
##
# Controller used in the coupon creation page
##
Application.Controllers.controller "NewCouponController", ["$scope", "$state",'Coupon', 'growl', '_t'
Application.Controllers.controller "NewCouponController", ["$scope", "$state", 'Coupon', 'growl', '_t'
, ($scope, $state, Coupon, growl, _t) ->
## Values for the coupon currently created
$scope.coupon =
active: true
type: 'percent_off'
## Options for the validity per user
$scope.validities = userValidities
@ -57,7 +58,7 @@ Application.Controllers.controller "NewCouponController", ["$scope", "$state",'C
##
# Controller used in the coupon edition page
##
Application.Controllers.controller "EditCouponController", ["$scope", "$state", 'Coupon', 'couponPromise', '_t'
Application.Controllers.controller "EditCouponController", ["$scope", "$state", 'Coupon', 'couponPromise', '_t'
, ($scope, $state, Coupon, couponPromise, _t) ->
### PUBLIC SCOPE ###

View File

@ -382,8 +382,8 @@ Application.Controllers.controller "ShowEventReservationsController", ["$scope",
##
# Controller used in the event creation page
##
Application.Controllers.controller "NewEventController", ["$scope", "$state", "$locale", 'CSRF', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t'
, ($scope, $state, $locale, CSRF, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t) ->
Application.Controllers.controller "NewEventController", ["$scope", "$state", 'CSRF', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t'
, ($scope, $state, CSRF, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t) ->
CSRF.setMetaTags()
## API URL where the form will be posted
@ -425,10 +425,6 @@ Application.Controllers.controller "NewEventController", ["$scope", "$state", "$
{label: _t('every_year'), value: 'year'}
]
## currency symbol for the current locale (cf. angular-i18n)
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
## Using the EventsController
new EventsController($scope, $state)
]
@ -438,8 +434,8 @@ Application.Controllers.controller "NewEventController", ["$scope", "$state", "$
##
# Controller used in the events edition page
##
Application.Controllers.controller "EditEventController", ["$scope", "$state", "$stateParams", "$locale", 'CSRF', 'eventPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise'
, ($scope, $state, $stateParams, $locale, CSRF, eventPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise) ->
Application.Controllers.controller "EditEventController", ["$scope", "$state", "$stateParams", 'CSRF', 'eventPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise'
, ($scope, $state, $stateParams, CSRF, eventPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise) ->
### PUBLIC SCOPE ###
@ -454,9 +450,6 @@ Application.Controllers.controller "EditEventController", ["$scope", "$state", "
## Retrieve the event details, in case of error the user is redirected to the events listing
$scope.event = eventPromise
## currency symbol for the current locale (cf. angular-i18n)
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
## List of categories for the events
$scope.categories = categoriesPromise

View File

@ -421,10 +421,7 @@ Application.Controllers.controller "EditMemberController", ["$scope", "$state",
modalInstance = $uibModal.open
animation: true,
templateUrl: '<%= asset_path "wallet/credit_modal.html" %>'
controller: ['$scope', '$uibModalInstance', 'Wallet', '$locale', ($scope, $uibModalInstance, Wallet, $locale) ->
## currency symbol for the current locale (cf. angular-i18n)
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
controller: ['$scope', '$uibModalInstance', 'Wallet', ($scope, $uibModalInstance, Wallet) ->
##
# Modal dialog validation callback

View File

@ -78,8 +78,8 @@ class PlanController
##
# Controller used in the plan creation form
##
Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal', 'groups', 'plans', 'machines', 'prices', 'partners', 'CSRF', '$state', 'growl', '_t', '$locale'
, ($scope, $uibModal, groups, plans, machines, prices, partners, CSRF, $state, growl, _t, $locale) ->
Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal', 'groups', 'plans', 'machines', 'prices', 'partners', 'CSRF', '$state', 'growl', '_t'
, ($scope, $uibModal, groups, plans, machines, prices, partners, CSRF, $state, growl, _t) ->
@ -119,10 +119,6 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
$scope.method = 'POST'
## currency symbol for the current locale (cf. angular-i18n)
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
##
# Checks if the partner contact is a valid data. Used in the form validation process
@ -187,8 +183,8 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
##
# Controller used in the plan edition form
##
Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', '$locale', 'Plan'
, ($scope, groups, plans, planPromise, machines, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, $locale, Plan) ->
Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', 'Plan'
, ($scope, groups, plans, planPromise, machines, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, Plan) ->
@ -208,10 +204,6 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
$scope.method = 'PATCH'
## currency symbol for the current locale (cf. angular-i18n)
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
##
# If a parent plan was set ($scope.plan.parent), the prices will be copied from this parent plan into

View File

@ -3,8 +3,8 @@
##
# Controller used in the prices edition page
##
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', 'TrainingsPricing', '$filter', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', '_t'
, ($scope, $state, $uibModal, TrainingsPricing, $filter, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, _t) ->
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', '_t'
, ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, _t) ->
### PUBLIC SCOPE ###
## List of machines prices (not considering any plan)

View File

@ -1,7 +1,7 @@
'use strict'
Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "$window", "Session", "AuthService", "Auth", "$uibModal", "$state", 'growl', 'Notification', '$interval', "Setting", '_t', 'Version'
, ($rootScope, $scope, $window, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t, Version) ->
Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "$window", '$locale', "Session", "AuthService", "Auth", "$uibModal", "$state", 'growl', 'Notification', '$interval', "Setting", '_t', 'Version'
, ($rootScope, $scope, $window, $locale, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t, Version) ->
@ -18,6 +18,10 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
$scope.version =
version: ''
## currency symbol for the current locale (cf. angular-i18n)
$rootScope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
##
# Set the current user to the provided value and initialize the session
# @param user {Object} Rails/Devise user

View File

@ -154,6 +154,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
tickets: {}
toReserve: false
amountTotal : 0
totalNoCoupon: 0
totalSeats: 0
## Discount coupon to apply to the basket, if any
@ -400,6 +401,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
r = mkReservation($scope.ctrl.member, $scope.reserve, $scope.event)
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
$scope.reserve.amountTotal = res.price
$scope.reserve.totalNoCoupon = res.price_without_coupon
else
$scope.reserve.amountTotal = null
@ -560,9 +562,9 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
member: $scope.ctrl.member
coupon: ->
$scope.coupon.applied
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', 'wallet', 'helpers', '$locale', '$filter', 'coupon',
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl, wallet, helpers, $locale, $filter, coupon) ->
# user wallet amount
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', 'wallet', 'helpers', '$filter', 'coupon',
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl, wallet, helpers, $filter, coupon) ->
# User's wallet amount
$scope.walletAmount = wallet.amount
# Price
@ -574,8 +576,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
# Reservation
$scope.reservation = reservation
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
# Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number')
# Callback for the stripe payment authorization
@ -616,9 +617,9 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
coupon: ->
$scope.coupon.applied
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', '$locale', 'helpers', '$filter', 'coupon',
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, $locale, helpers, $filter, coupon) ->
# user wallet amount
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon) ->
# User's wallet amount
$scope.walletAmount = wallet.amount
# Price
@ -630,8 +631,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
# Reservation
$scope.reservation = reservation
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
# Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number')
# Button label

View File

@ -315,6 +315,9 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
## total amount of the bill to pay
$scope.amountTotal = 0
## total amount of the elements in the cart, without considering any coupon
$scope.totalNoCoupon = 0
## Discount coupon to apply to the basket, if any
$scope.coupon =
applied: null
@ -661,6 +664,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
r = mkReservation($scope.ctrl.member, $scope.eventsReserved, $scope.selectedPlan)
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
$scope.amountTotal = res.price
$scope.totalNoCoupon = res.price_without_coupon
setSlotsDetails(res.details)
else
# otherwise we alert, this error musn't occur when the current user is not admin
@ -782,8 +786,8 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
CustomAsset.get({name: 'cgv-file'}).$promise
coupon: ->
$scope.coupon.applied
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$locale', '$filter', 'coupon',
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $locale, $filter, coupon) ->
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon) ->
# user wallet amount
$scope.walletAmount = wallet.amount
@ -796,9 +800,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
# Reservation
$scope.reservation = reservation
# Currency symbol or abreviation for the current locale
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
# Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number')
@ -842,8 +843,8 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
coupon: ->
$scope.coupon.applied
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', '$locale', 'coupon',
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, $locale, coupon) ->
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon) ->
# user wallet amount
$scope.walletAmount = wallet.amount
@ -857,9 +858,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
# Reservation
$scope.reservation = reservation
# Currency symbol or abreviation for the current locale
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
# Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number')

View File

@ -177,11 +177,14 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
updateCartPrice = ->
# first we check that a user was selected
if Object.keys($scope.ctrl.member).length > 0
$scope.cart.total = $scope.selectedPlan.amount
# apply the coupon if any
if $scope.coupon.applied
$scope.cart.total = $scope.selectedPlan.amount
# apply the coupon if any
if $scope.coupon.applied
if $scope.coupon.applied.type == 'percent_off'
discount = $scope.cart.total * $scope.coupon.applied.percent_off / 100
$scope.cart.total -= discount
else if $scope.coupon.applied.type == 'amount_off'
discount = $scope.coupon.applied.amount_off
$scope.cart.total -= discount
else
$scope.reserve.amountTotal = null
@ -200,9 +203,9 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
wallet: ->
Wallet.getWalletByUser({user_id: $scope.ctrl.member.id}).$promise
coupon: -> $scope.coupon.applied
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'CustomAsset', 'wallet', 'helpers', '$locale', '$filter', 'coupon',
($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, CustomAsset, wallet, helpers, $locale, $filter, coupon) ->
# user wallet amount
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'CustomAsset', 'wallet', 'helpers', '$filter', 'coupon',
($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, CustomAsset, wallet, helpers, $filter, coupon) ->
# User's wallet amount
$scope.walletAmount = wallet.amount
# Final price to pay by the user
@ -211,9 +214,6 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
# The plan that the user is about to subscribe
$scope.selectedPlan = selectedPlan
# Currency symbol or abreviation for the current locale
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
# Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number')
@ -266,8 +266,8 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
wallet: ->
Wallet.getWalletByUser({user_id: $scope.ctrl.member.id}).$promise
coupon: -> $scope.coupon.applied
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'wallet', 'helpers', '$locale', '$filter', 'coupon',
($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, wallet, helpers, $locale, $filter, coupon) ->
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'wallet', 'helpers', '$filter', 'coupon',
($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, wallet, helpers, $filter, coupon) ->
# user wallet amount
$scope.walletAmount = wallet.amount
@ -277,9 +277,6 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
# price to pay
$scope.amount = helpers.getAmountToPay($scope.price, wallet.amount)
# Currency symbol or abreviation for the current locale
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
# Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number')

View File

@ -217,10 +217,25 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
# Number of projects added to the page when the user clicks on 'load more projects'
PROJECTS_PER_PAGE = 16
$scope.openlabAppId = Fablab.openlabAppId
### PUBLIC SCOPE ###
$scope.search = { q: ($location.$$search.q || ""), from: ($location.$$search.from || undefined), machine_id: (parseInt($location.$$search.machine_id) || undefined), component_id: (parseInt($location.$$search.component_id) || undefined), theme_id: (parseInt($location.$$search.theme_id) || undefined) }
## Fab-manager's instance ID in the openLab network
$scope.openlabAppId = Fablab.openlabAppId
## Is openLab enabled on the instance?
$scope.openlab =
projectsActive: Fablab.openlabProjectsActive
searchOverWholeNetwork: false
## default search parameters
$scope.search =
q: ($location.$$search.q || "")
from: ($location.$$search.from || undefined)
machine_id: (parseInt($location.$$search.machine_id) || undefined)
component_id: (parseInt($location.$$search.component_id) || undefined)
theme_id: (parseInt($location.$$search.theme_id) || undefined)
## list of projects to display
$scope.projects = []
@ -234,32 +249,14 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
## list of components / used for filtering
$scope.components = componentsPromise
$scope.openlab = {}
$scope.openlab.projectsActive = Fablab.openlabProjectsActive
if $location.$$search.whole_network is 'f'
$scope.openlab.searchOverWholeNetwork = false
else
$scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive || false
normalizeProjectsAttrs = (projects)->
projects.map((project)->
project.project_image = project.image_url
return project
)
$scope.searchOverWholeNetworkChanged = ->
setTimeout ->
$scope.resetFiltersAndTriggerSearch()
, 150
loadMoreCallback = (projectsPromise)->
$scope.projects = $scope.projects.concat(projectsPromise.projects)
updateUrlParam('page', $scope.projectsPagination.currentPage)
loadMoreOpenlabCallback = (projectsPromise)->
$scope.projects = $scope.projects.concat(normalizeProjectsAttrs(projectsPromise.projects))
updateUrlParam('page', $scope.projectsPagination.currentPage)
$scope.loadMore = ->
if $scope.openlab.searchOverWholeNetwork is true
@ -268,6 +265,7 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
$scope.projectsPagination.loadMore(search: $scope.search)
$scope.resetFiltersAndTriggerSearch = ->
$scope.search.q = ""
$scope.search.from = undefined
@ -277,6 +275,8 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
$scope.setUrlQueryParams($scope.search)
$scope.triggerSearch()
$scope.triggerSearch = ->
currentPage = parseInt($location.$$search.page) || 1
if $scope.openlab.searchOverWholeNetwork is true
@ -298,6 +298,8 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
$scope.projectsPagination.totalCount = projectsPromise.meta.total
$scope.projects = projectsPromise.projects
##
# Callback to switch the user's view to the detailled project page
# @param project {{slug:string}} The project to display
@ -309,6 +311,8 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
else
$state.go('app.public.projects_show', {id: project.slug})
##
# function to set all url query search parameters from search object
##
@ -320,6 +324,21 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
updateUrlParam('component_id', search.component_id)
updateUrlParam('machine_id', search.machine_id)
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
if $location.$$search.whole_network is 'f'
$scope.openlab.searchOverWholeNetwork = false
else
$scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive || false
$scope.triggerSearch()
##
# function to update url query param, little hack to turn off reloadOnSearch and re-enable it after setting the params
# params example: 'q' , 'presse-purée'
@ -330,9 +349,30 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
$timeout ->
$state.current.reloadOnSearch = undefined
## initialization
$scope.triggerSearch()
loadMoreCallback = (projectsPromise)->
$scope.projects = $scope.projects.concat(projectsPromise.projects)
updateUrlParam('page', $scope.projectsPagination.currentPage)
loadMoreOpenlabCallback = (projectsPromise)->
$scope.projects = $scope.projects.concat(normalizeProjectsAttrs(projectsPromise.projects))
updateUrlParam('page', $scope.projectsPagination.currentPage)
normalizeProjectsAttrs = (projects)->
projects.map((project)->
project.project_image = project.image_url
return project
)
## !!! MUST BE CALLED AT THE END of the controller
initialize()
]

View File

@ -137,6 +137,12 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
$scope.coupon =
applied: null
## Total price of the cart, that the user will pay
$scope.amountTotal = 0
## Total amount of the elements in the cart, without considering any coupon
$scope.totalNoCoupon = 0
## fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig
minTime: moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss'))
@ -353,6 +359,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
r = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan)
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
$scope.amountTotal = res.price
$scope.totalNoCoupon = res.price_without_coupon
else
$scope.amountTotal = null
@ -426,7 +433,6 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
if $scope.ctrl.member
# reserve a training if this training will not be reserved and is not about to move and not is completed
if !event.is_reserved && !$scope.slotToModify && !event.is_completed
$scope.coupon.applied = null
if event != $scope.selectedTraining
$scope.selectedTraining = event
$scope.selectedTraining.offered = false
@ -533,9 +539,9 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
CustomAsset.get({name: 'cgv-file'}).$promise
coupon: ->
$scope.coupon.applied
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'wallet', 'cgv', 'Auth', 'Reservation', '$locale', 'helpers', '$filter', 'coupon'
($scope, $uibModalInstance, $state, reservation, price, wallet, cgv, Auth, Reservation, $locale, helpers, $filter, coupon) ->
# user wallet amount
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'wallet', 'cgv', 'Auth', 'Reservation', 'helpers', '$filter', 'coupon'
($scope, $uibModalInstance, $state, reservation, price, wallet, cgv, Auth, Reservation, helpers, $filter, coupon) ->
# User's wallet amount
$scope.walletAmount = wallet.amount
# Price
@ -547,8 +553,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
# Reservation
$scope.reservation = reservation
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
# Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number')
##
@ -594,9 +599,9 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
coupon: ->
$scope.coupon.applied
controller: ['$scope', '$uibModalInstance', '$state', '$filter', 'reservation', 'price', 'wallet', 'Auth', 'Reservation', '$locale', 'helpers', 'coupon'
($scope, $uibModalInstance, $state, $filter, reservation, price, wallet, Auth, Reservation, $locale, helpers, coupon) ->
# user wallet amount
controller: ['$scope', '$uibModalInstance', '$state', '$filter', 'reservation', 'price', 'wallet', 'Auth', 'Reservation', 'helpers', 'coupon'
($scope, $uibModalInstance, $state, $filter, reservation, price, wallet, Auth, Reservation, helpers, coupon) ->
# User's wallet amount
$scope.walletAmount = wallet.amount
# Price
@ -608,8 +613,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
# Reservation
$scope.reservation = reservation
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
# Used in wallet info template to interpolate some translations
$scope.numberFilter = $filter('number')
# Button label
@ -649,7 +653,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
# first we check that a user was selected
if Object.keys($scope.ctrl.member).length > 0
r = mkReservation($scope.ctrl.member, training) # reservation without any Plan -> we get the training price
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
Price.compute mkRequestParams(r), (res) ->
$scope.selectedTrainingAmount = res.price
else
$scope.selectedTrainingAmount = null

View File

@ -1,11 +1,11 @@
Application.Directives.directive 'coupon', [ 'Coupon', 'growl', '_t', (Coupon, growl, _t) ->
Application.Directives.directive 'coupon', [ '$rootScope', 'Coupon', 'growl', '_t', ($rootScope, Coupon, growl, _t) ->
{
restrict: 'E'
scope:
show: '='
coupon: '='
total: '='
userId: '@'
hasSelectSlot: '='
templateUrl: '<%= asset_path "shared/_coupon.html" %>'
link: ($scope, element, attributes) ->
@ -16,15 +16,13 @@ Application.Directives.directive 'coupon', [ 'Coupon', 'growl', '_t', (Coupon, g
# Available status are: 'pending', 'valid', 'invalid'
$scope.status = 'pending'
# Binding for the code inputed
# Binding for the code inputed (see the attached template)
$scope.couponCode = null
$scope.$watch 'hasSelectSlot', (newValue) ->
unless newValue
$scope.coupon = null
$scope.couponCode = null
$scope.code.input = false
# Re-compute if the code can be applied when the total of the cart changes
$scope.$watch 'total', (newValue, oldValue) ->
if newValue and newValue != oldValue and $scope.couponCode
$scope.validateCode()
##
# Callback to validate the code
@ -34,10 +32,13 @@ Application.Directives.directive 'coupon', [ 'Coupon', 'growl', '_t', (Coupon, g
$scope.status = 'pending'
$scope.coupon = null
else
Coupon.validate {code: $scope.couponCode, user_id: $scope.userId}, (res) ->
Coupon.validate {code: $scope.couponCode, user_id: $scope.userId, amount: $scope.total}, (res) ->
$scope.status = 'valid'
$scope.coupon = res
growl.success(_t('the_coupon_has_been_applied_you_get_PERCENT_discount', {PERCENT: res.percent_off}))
if res.type == 'percent_off'
growl.success(_t('the_coupon_has_been_applied_you_get_PERCENT_discount', {PERCENT: res.percent_off}))
else
growl.success(_t('the_coupon_has_been_applied_you_get_AMOUNT_CURRENCY', {AMOUNT: res.amount_off, CURRENCY: $rootScope.currencySymbol}))
, (err) ->
$scope.status = 'invalid'
$scope.coupon = null

View File

@ -21,7 +21,20 @@
<span class="help-block error" ng-show="couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$error.pattern" translate>{{ 'code_must_be_composed_of_capital_letters_digits_and_or_dashes' }}</span>
</div>
<div class="form-group" ng-class="{'has-error': couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$invalid}">
<div class="form-group">
<label for="coupon[type]">{{ 'kind_of_coupon' | translate }} *</label>
<select id="coupon[type]"
name="coupon[type]"
class="form-control"
ng-model="coupon.type"
ng-disabled="mode == 'EDIT'"
required="required">
<option value="percent_off" translate>{{ 'percentage' }}</option>
<option value="amount_off" translate>{{ 'amount' }}</option>
</select>
</div>
<div class="form-group" ng-class="{'has-error': couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$invalid}" ng-show="coupon.type == 'percent_off'">
<label for="coupon[percent_off]">{{ 'percent_off' | translate }} *</label>
<div class="input-group">
<input type="number" id="coupon[percent_off]"
@ -31,13 +44,30 @@
min="0"
max="100"
ng-disabled="mode == 'EDIT'"
required="required"/>
ng-required="coupon.type == 'percent_off'"/>
<span class="input-group-addon"><i class="fa fa-percent"></i></span>
</div>
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$error.required" translate>{{ 'percent_off_is_required' }}</span>
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && (couponForm['coupon[percent_off]'].$error.min || couponForm['coupon[percent_off]'].$error.max)" translate>{{ 'percentage_must_be_between_0_and_100' }}</span>
</div>
<div class="form-group" ng-class="{'has-error': couponForm['coupon[amount_off]'].$dirty && couponForm['coupon[amount_off]'].$invalid}" ng-show="coupon.type == 'amount_off'">
<label for="coupon[amount_off]">{{ 'amount_off' | translate }} *</label>
<div class="input-group">
<span class="input-group-addon">{{currencySymbol}}</span>
<input type="number" id="coupon[amount_off]"
name="coupon[amount_off]"
class="form-control"
ng-model="coupon.amount_off"
min="0"
ng-disabled="mode == 'EDIT'"
ng-required="coupon.type == 'amount_off'"/>
</div>
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$error.required" translate>{{ 'percent_off_is_required' }}</span>
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && (couponForm['coupon[percent_off]'].$error.min || couponForm['coupon[percent_off]'].$error.max)" translate>{{ 'percentage_must_be_between_0_and_100' }}</span>
</div>
<div class="form-group" ng-class="{'has-error': couponForm['coupon[validity_per_user]'].$dirty && couponForm['coupon[validity_per_user]'].$invalid}">
<label for="coupon[validity_per_user]">{{ 'validity_per_user' | translate }} *</label>
<select id="coupon[validity_per_user]"

View File

@ -5,7 +5,7 @@
<thead>
<tr>
<th translate>{{ 'name' }}</th>
<th translate>{{ 'percentage_off' }}</th>
<th translate>{{ 'discount' }}</th>
<th translate>{{ 'nb_of_usages' }}</th>
<th translate>{{ 'status' }}</th>
<th></th>
@ -14,7 +14,10 @@
<tbody>
<tr ng-repeat="coupon in coupons">
<td>{{coupon.name}}</td>
<td>{{coupon.percent_off}} %</td>
<td>
<span ng-show="coupon.type == 'percent_off'">{{coupon.percent_off}} %</span>
<span ng-show="coupon.type == 'amount_off'">{{coupon.amount_off}} {{currencySymbol}}</span>
</td>
<td>{{coupon.usages}}</td>
<td translate>{{coupon.status}}</td>
<td>

View File

@ -13,12 +13,12 @@
<tr><th style="width:60%"></th></tr>
</thead>
<tbody>
<tr><td translate>{{'code'}}</td><td>{{coupon.code}}</td></tr>
<tr><td translate>{{'percent_off'}}</td><td>{{coupon.percent_off}} %</td></tr>
<tr><td translate>{{'validity_per_user'}}</td><td translate>{{coupon.validity_per_user}}</td></tr>
<tr><td translate>{{'valid_until'}}</td><td>{{coupon.valid_until | amDateFormat:'L'}}</td></tr>
<tr><td translate>{{'usages'}}</td><td>{{coupon.usages}} / {{coupon.max_usages | maxCount}}</td></tr>
<tr><td translate>{{'enabled'}}</td><td>{{coupon.active | booleanFormat}}</td></tr>
<tr><td translate>{{'code'}}</td> <td>{{coupon.code}}</td></tr>
<tr><td translate>{{'discount'}}</td> <td><span ng-show="coupon.type == 'percent_off'">{{coupon.percent_off}} %</span><span ng-show="coupon.type == 'amount_off'">{{coupon.amount_off}} {{currencySymbol}}</span></td></tr>
<tr><td translate>{{'validity_per_user'}}</td> <td translate>{{coupon.validity_per_user}}</td></tr>
<tr><td translate>{{'valid_until'}}</td> <td>{{coupon.valid_until | amDateFormat:'L'}}</td></tr>
<tr><td translate>{{'usages'}}</td> <td>{{coupon.usages}} / {{coupon.max_usages | maxCount}}</td></tr>
<tr><td translate>{{'enabled'}}</td> <td>{{coupon.active | booleanFormat}}</td></tr>
</tbody>
</table>
</div>

View File

@ -164,7 +164,7 @@
<button class="btn btn-warning-full rounded btn-block text-sm" ng-click="reserveEvent()" ng-show="event.nb_free_places > 0 && !reserve.toReserve">{{ 'book' | translate }}</button>
<coupon show="reserve.totalSeats > 0 && ctrl.member" coupon="coupon.applied" user-id="{{ctrl.member.id}}"></coupon>
<coupon show="reserve.totalSeats > 0 && ctrl.member" coupon="coupon.applied" total="reserve.totalNoCoupon" user-id="{{ctrl.member.id}}"></coupon>
</div>
</div>

View File

@ -78,7 +78,7 @@
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeMachineSlot(machineSlot, $event)" ng-if="machineSlot.isValid" translate>{{ 'remove_this_slot' }}</a></div>
</div>
<coupon show="machineSlotsValid() && (!plansAreShown || selectedPlan)" coupon="coupon.applied" has-select-slot="machineSlotsValid()" user-id="{{ctrl.member.id}}"></coupon>
<coupon show="machineSlotsValid() && (!plansAreShown || selectedPlan)" coupon="coupon.applied" total="totalNoCoupon" user-id="{{ctrl.member.id}}"></coupon>
<span ng-hide="fablabWithoutPlans">
<div ng-if="machineSlotsValid() && !ctrl.member.subscribed_plan" ng-show="!plansAreShown">

View File

@ -141,7 +141,7 @@
<div class="font-sbold">{{ 'subscription_price' | translate }} {{selectedPlan.amount | currency}}</div>
</div>
<coupon show="!ctrl.member.subscribed_plan" coupon="coupon.applied" user-id="{{ctrl.member.id}}"></coupon>
<coupon show="!ctrl.member.subscribed_plan" coupon="coupon.applied" total="selectedPlan.amount" user-id="{{ctrl.member.id}}"></coupon>
</div>
<div class="widget-footer">

View File

@ -82,7 +82,7 @@
<a class="pull-right m-t-xs text-u-l" href="#" ng-click="removeTraining($event)" ng-if="trainingIsValid" translate>{{ 'remove_this_slot' }}</a>
</div>
<coupon show="trainingIsValid && (!plansIsShow || selectedPlan)" coupon="coupon.applied" has-select-slot="trainingIsValid" user-id="{{ctrl.member.id}}"></coupon>
<coupon show="trainingIsValid && (!plansIsShow || selectedPlan)" coupon="coupon.applied" user-id="{{ctrl.member.id}}" total="totalNoCoupon"></coupon>
<span ng-hide="fablabWithoutPlans">
<div ng-if="trainingIsValid && !ctrl.member.subscribed_plan" ng-show="!plansIsShow">

View File

@ -20,7 +20,7 @@ class API::CouponsController < API::ApiController
end
def validate
@coupon = Coupon.find_by_code(params[:code])
@coupon = Coupon.find_by(code: params[:code])
if @coupon.nil?
render json: {status: 'rejected'}, status: :not_found
else
@ -30,7 +30,8 @@ class API::CouponsController < API::ApiController
_user_id = params[:user_id]
end
status = @coupon.status(_user_id)
amount = params[:amount].to_f * 100.0
status = @coupon.status(_user_id, amount)
if status != 'active'
render json: {status: status}, status: :unprocessable_entity
else
@ -60,7 +61,7 @@ class API::CouponsController < API::ApiController
def send_to
authorize Coupon
@coupon = Coupon.find_by_code(params[:coupon_code])
@coupon = Coupon.find_by(code: params[:coupon_code])
if @coupon.nil?
render json: {error: "no coupon with code #{params[:coupon_code]}"}, status: :not_found
else
@ -78,7 +79,14 @@ class API::CouponsController < API::ApiController
end
def coupon_params
params.require(:coupon).permit(:name, :code, :percent_off, :validity_per_user, :valid_until, :max_usages, :active)
if @parameters
@parameters
else
@parameters = params
@parameters[:coupon][:amount_off] = @parameters[:coupon][:amount_off].to_i * 100.0 if @parameters[:coupon][:amount_off]
@parameters = @parameters.require(:coupon).permit(:name, :code, :percent_off, :amount_off, :validity_per_user, :valid_until, :max_usages, :active)
end
end
def coupon_editable_params

View File

@ -9,7 +9,12 @@ class API::FeedsController < API::ApiController
limit = 3
end
from_account = Setting.find_by(name: 'twitter_name').try(:value) || ENV['TWITTER_NAME']
@tweet_news = Feed.twitter.user_timeline(from_account, {count: limit})
begin
@tweet_news = Feed.twitter.user_timeline(from_account, {count: limit})
rescue Twitter::Error::BadRequest => e
STDERR.puts "[WARNING] Unable to retrieve the twitter feed, please check your ENV configuration. Details: #{e.message}"
render status: :no_content
end
end
end

View File

@ -145,7 +145,7 @@ class API::MembersController < API::ApiController
token = params.require(:user).permit(:auth_token)[:auth_token]
@account = User.find_by_auth_token(token)
@account = User.find_by(auth_token: token)
if @account
@flow_worker = MembersProcessor.new(@account)
begin

View File

@ -92,8 +92,6 @@
plan_file_attributes: [:id, :attachment, :_destroy],
prices_attributes: [:id, :amount]
)
@parameters
end
end
end

View File

@ -41,7 +41,7 @@ class API::ProjectsController < API::ApiController
end
def collaborator_valid
project_user = ProjectUser.find_by_valid_token params[:valid_token]
project_user = ProjectUser.find_by(valid_token: params[:valid_token])
if project_user
project_user.update(is_valid: true, valid_token: '')
redirect_to "/#!/projects/#{project_user.project.id}" and return

View File

@ -22,7 +22,7 @@ class API::StatisticsController < API::ApiController
# run additional custom aggregations, if any
if statistic_type and start_date and end_date
stat_index = StatisticIndex.find_by_es_type_key("#{path}")
stat_index = StatisticIndex.find_by(es_type_key: "#{path}")
stat_type = StatisticType.where(statistic_index_id: stat_index.id, key: statistic_type).first
client = Elasticsearch::Model.client
stat_type.statistic_custom_aggregations.each do |custom|

View File

@ -8,10 +8,9 @@ class Coupon < ActiveRecord::Base
validates :code, presence: true
validates :code, format: { with: /\A[A-Z0-9\-]+\z/ ,message: 'only caps letters, numbers, and dashes'}
validates :code, uniqueness: true
validates :percent_off, presence: true
validates :percent_off, :inclusion => 0..100
validates :validity_per_user, presence: true
validates :validity_per_user, inclusion: { in: %w(once forever) }
validates_with CouponDiscountValidator
def safe_destroy
if self.invoices.size == 0
@ -27,12 +26,15 @@ class Coupon < ActiveRecord::Base
# - may has expired because the validity date has been reached,
# - may have been used the maximum number of times it was allowed
# - may have already been used by the provided user, if the coupon is configured to allow only one use per user,
# - may exceed the current cart's total amount, if the coupon is configured to discount an amount (and not a percentage)
# - may be available for use
# @param [user_id] {Number} if provided and if the current coupon's validity_per_user == 'once', check that the coupon
# was already used by the provided user
# @param [amount] {Number} if provided and if the current coupon's type == 'amont_off' check that the coupon
# does not exceed the cart total price
# @return {String} status identifier
##
def status(user_id = nil)
def status(user_id = nil, amount = nil)
if not active?
'disabled'
elsif (!valid_until.nil?) and valid_until.at_end_of_day < DateTime.now
@ -41,11 +43,21 @@ class Coupon < ActiveRecord::Base
'sold_out'
elsif (!user_id.nil?) and validity_per_user == 'once' and users_ids.include?(user_id.to_i)
'already_used'
elsif (!amount.nil?) and type == 'amount_off' and amount_off > amount.to_f
'amount_exceeded'
else
'active'
end
end
def type
if amount_off.nil? and !percent_off.nil?
'percent_off'
elsif percent_off.nil? and !amount_off.nil?
'amount_off'
end
end
def users
self.invoices.map do |i|
i.user

View File

@ -149,7 +149,14 @@ class Invoice < ActiveRecord::Base
end
# handle coupon
unless avoir.coupon_id.nil?
discount = avoir.total * avoir.coupon.percent_off / 100.0
discount = avoir.total
if avoir.coupon.type == 'percent_off'
discount = avoir.total * avoir.coupon.percent_off / 100.0
elsif avoir.coupon.type == 'amount_off'
discount = avoir.coupon.amount_off
else
raise InvalidCouponError
end
avoir.total -= discount
end
avoir

View File

@ -45,7 +45,7 @@ class Price < ActiveRecord::Base
if machine_credit
hours_available = machine_credit.hours
if !new_plan_being_bought
user_credit = user.users_credits.find_by_credit_id(machine_credit.id)
user_credit = user.users_credits.find_by(credit_id: machine_credit.id)
if user_credit
hours_available = machine_credit.hours - user_credit.hours_used
end
@ -111,13 +111,11 @@ class Price < ActiveRecord::Base
end
# === apply Coupon if any ===
unless coupon_code.nil?
_coupon = Coupon.find_by_code(coupon_code)
_amount = _amount - (_amount * _coupon.percent_off / 100.0)
end
_amount_no_coupon = _amount
_amount = CouponApplyService.new.(_amount, coupon_code)
# return result
{elements: _elements, total: _amount}
{elements: _elements, total: _amount, before_coupon: _amount_no_coupon}
end
private

View File

@ -134,16 +134,25 @@ class Reservation < ActiveRecord::Base
# === Coupon ===
unless coupon_code.nil?
cp = Coupon.find_by_code(coupon_code)
if not cp.nil? and cp.status(user.id) == 'active'
@coupon = cp
@coupon = Coupon.find_by(code: coupon_code)
if not @coupon.nil? and @coupon.status(user.id) == 'active'
total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+)
discount = 0
if @coupon.type == 'percent_off'
discount = (total * @coupon.percent_off / 100).to_i
elsif @coupon.type == 'amount_off'
discount = @coupon.amount_off
else
raise InvalidCouponError
end
unless on_site
invoice_items << Stripe::InvoiceItem.create(
customer: user.stp_customer_id,
amount: -(total * cp.percent_off / 100).to_i,
amount: -discount,
currency: Rails.application.secrets.stripe_currency,
description: "coupon #{cp.code} - reservation"
description: "coupon #{@coupon.code} - reservation"
)
end
else
@ -395,7 +404,7 @@ class Reservation < ActiveRecord::Base
total += plan.amount
end
if @coupon
total = (total - (total * @coupon.percent_off / 100.0)).to_i
total = CouponApplyService.new.(total, @coupon, user.id)
end
wallet_amount = (user.wallet.amount * 100).to_i
@ -427,9 +436,9 @@ class Reservation < ActiveRecord::Base
total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+)
unless coupon_code.nil?
cp = Coupon.find_by_code(coupon_code)
cp = Coupon.find_by(code: coupon_code)
if not cp.nil? and cp.status(user.id) == 'active'
total = total - (total * cp.percent_off / 100.0)
total = CouponApplyService.new.(total, cp, user.id)
self.invoice.coupon_id = cp.id
else
raise InvalidCouponError

View File

@ -25,15 +25,24 @@ class Subscription < ActiveRecord::Base
invoice_items = []
unless coupon_code.nil?
cp = Coupon.find_by_code(coupon_code)
if not cp.nil? and cp.status(user.id) == 'active'
@coupon = cp
@coupon = Coupon.find_by(code: coupon_code)
if not @coupon.nil? and @coupon.status(user.id) == 'active'
total = plan.amount
discount = 0
if @coupon.type == 'percent_off'
discount = (total * @coupon.percent_off / 100).to_i
elsif @coupon.type == 'amount_off'
discount = @coupon.amount_off
else
raise InvalidCouponError
end
invoice_items << Stripe::InvoiceItem.create(
customer: user.stp_customer_id,
amount: -(total * cp.percent_off / 100.0).to_i,
amount: -discount,
currency: Rails.application.secrets.stripe_currency,
description: "coupon #{cp.code} - subscription"
description: "coupon #{@coupon.code} - subscription"
)
else
raise InvalidCouponError
@ -159,11 +168,11 @@ class Subscription < ActiveRecord::Base
total = plan.amount
unless coupon_code.nil?
cp = Coupon.find_by_code(coupon_code)
if not cp.nil? and cp.status(user.id) == 'active'
@coupon = cp
coupon_id = cp.id
total = plan.amount - (plan.amount * cp.percent_off / 100.0)
@coupon = Coupon.find_by(code: coupon_code)
unless @coupon.nil?
total = CouponApplyService.new.(plan.amount, @coupon, user.id)
coupon_id = @coupon.id
end
end
@ -293,7 +302,7 @@ class Subscription < ActiveRecord::Base
def get_wallet_amount_debit
total = plan.amount
if @coupon
total = (total - (total * @coupon.percent_off / 100.0)).to_i
total = CouponApplyService.new.(total, @coupon, user.id)
end
wallet_amount = (user.wallet.amount * 100).to_i
return wallet_amount >= total ? total : wallet_amount

View File

@ -131,7 +131,7 @@ class User < ActiveRecord::Base
end
json.machine_credits machine_credits do |mc|
json.machine_id mc.creditable_id
json.hours_used mc.users_credits.find_by_user_id(id).hours_used
json.hours_used mc.users_credits.find_by(user_id: id).hours_used
end
json.last_sign_in_at last_sign_in_at.iso8601 if last_sign_in_at
end

View File

@ -3,6 +3,7 @@ module PDF
class Invoice < Prawn::Document
require 'stringio'
include ActionView::Helpers::NumberHelper
include ApplicationHelper
def initialize(invoice)
super(:margin => 70)
@ -143,11 +144,25 @@ module PDF
# subtract the coupon, if any
unless invoice.coupon_id.nil?
cp = invoice.coupon
discount = total_calc * cp.percent_off / 100.0
discount = 0
if cp.type == 'percent_off'
discount = total_calc * cp.percent_off / 100.0
elsif cp.type == 'amount_off'
discount = cp.amount_off / 100.00
else
raise InvalidCouponError
end
total_calc = total_calc - discount
# discount textual description
literal_discount = cp.percent_off
if cp.type == 'amount_off'
literal_discount = number_to_currency(discount)
end
# add a row for the coupon
data += [ [I18n.t('invoices.coupon_CODE_discount_of_PERCENT', CODE: cp.code, PERCENT: cp.percent_off), number_to_currency(-discount)] ]
data += [ [_t('invoices.coupon_CODE_discount_of_DISCOUNT', {CODE: cp.code, DISCOUNT: literal_discount, TYPE: cp.type}), number_to_currency(-discount)] ]
end
# total verification

View File

@ -0,0 +1,37 @@
class CouponApplyService
##
# Apply the provided coupon, if active, to the given price. Usability tests will be run depending on the
# provided parameters.
# If no coupon/coupon code or if the code does not match, return origin price without change
#
# @param total {Number} invoice total, before any coupon is applied
# @param coupon {String|Coupon} Coupon's code OR Coupon object
# @param user_id {Number} user's id against the coupon will be tested for usability
# @return {Number}
##
def call(total, coupon, user_id = nil)
price = total
_coupon = nil
if coupon.instance_of? Coupon
_coupon = coupon
elsif coupon.instance_of? String
_coupon = Coupon.find_by(code: coupon)
end
unless _coupon.nil?
if _coupon.status(user_id, total) == 'active'
if _coupon.type == 'percent_off'
price = price - (price * _coupon.percent_off / 100.0)
elsif _coupon.type == 'amount_off'
# do not apply cash coupon unless it has a lower amount that the total price
if _coupon.amount_off <= price
price -= _coupon.amount_off
end
end
end
end
price
end
end

View File

@ -127,7 +127,7 @@ class StatisticService
if sub
ca = i.amount.to_i / 100.0
unless i.invoice.coupon_id.nil?
ca = ca - ( ca * i.invoice.coupon.percent_off / 100.0 )
ca = CouponApplyService.new(ca, i.invoice.coupon)
end
u = sub.user
p = sub.plan
@ -353,7 +353,7 @@ class StatisticService
end
# subtract coupon discount from invoices and refunds
unless invoice.coupon_id.nil?
ca = ca - ( ca * invoice.coupon.percent_off / 100.0 )
ca = CouponApplyService.new(ca, invoice.coupon)
end
# divide the result by 100 to convert from centimes to monetary unit
ca == 0 ? ca : ca / 100.0
@ -366,7 +366,7 @@ class StatisticService
end
# subtract coupon discount from the refund
unless invoice.coupon_id.nil?
ca = ca - ( ca * invoice.coupon.percent_off / 100.0 )
ca = CouponApplyService.new(ca, invoice.coupon)
end
ca == 0 ? ca : ca / 100.0
end

View File

@ -0,0 +1,15 @@
class CouponDiscountValidator < ActiveModel::Validator
def validate(record)
if !record.percent_off.nil?
unless [0..100].include? record.percent_off
record.errors[:percent_off] << 'Percentage must be included between 0 and 100'
end
elsif !record.amount_off.nil?
unless record.amount_off > 0
record.errors[:amount_off] << I18n.t('errors.messages.greater_than_or_equal_to', count: 0)
end
else
record.errors[:percent_off] << 'cannot be blank when amount_off is blank too'
end
end
end

View File

@ -1,3 +1,4 @@
json.extract! coupon, :id, :name, :code, :percent_off, :valid_until, :validity_per_user, :max_usages, :active, :created_at
json.extract! coupon, :id, :name, :code, :type, :percent_off, :valid_until, :validity_per_user, :max_usages, :active, :created_at
json.amount_off (coupon.amount_off / 100.00) unless coupon.amount_off.nil?
json.usages coupon.invoices.count
json.status coupon.status

View File

@ -1 +1,2 @@
json.extract! @coupon, :id, :code, :percent_off
json.extract! @coupon, :id, :code, :type, :percent_off
json.amount_off (@coupon.amount_off / 100.00) unless @coupon.amount_off.nil?

View File

@ -54,7 +54,7 @@ json.array!(@members) do |member|
json.machine_credits member.machine_credits do |mc|
json.machine_id mc.creditable_id
json.hours_used mc.users_credits.find_by_user_id(member.id).hours_used
json.hours_used mc.users_credits.find_by(user_id: member.id).hours_used
end if attribute_requested?(@requested_attributes, 'credits') or attribute_requested?(@requested_attributes, 'machine_credits')
json.tags member.tags do |t|

View File

@ -69,7 +69,7 @@ json.training_credits @member.training_credits do |tc|
end
json.machine_credits @member.machine_credits do |mc|
json.machine_id mc.creditable_id
json.hours_used mc.users_credits.find_by_user_id(@member.id).hours_used
json.hours_used mc.users_credits.find_by(user_id: @member.id).hours_used
end
json.last_sign_in_at @member.last_sign_in_at.iso8601 if @member.last_sign_in_at
json.all_projects @member.all_projects do |project|

View File

@ -1,4 +1,5 @@
json.price @amount[:total] / 100.00
json.price_without_coupon @amount[:before_coupon] / 100.00
json.details do
json.slots @amount[:elements][:slots] do |slot|
json.start_at slot[:start_at]

View File

@ -10,7 +10,7 @@ json.user do
end
json.machine_credits @reservation.user.machine_credits do |mc|
json.machine_id mc.creditable_id
json.hours_used mc.users_credits.find_by_user_id(@reservation.user_id).hours_used
json.hours_used mc.users_credits.find_by(user_id: @reservation.user_id).hours_used
end
end
json.message @reservation.message

View File

@ -1,10 +1,18 @@
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
<p><%= t('.body.enjoy_a_discount_of_PERCENT_with_code_CODE',
PERCENT: @attached_object.percent_off,
CODE: @attached_object.code
) %>
</p>
<% if @attached_object.type == 'percent_off' %>
<p><%= t('.body.enjoy_a_discount_of_PERCENT_with_code_CODE',
PERCENT: @attached_object.percent_off,
CODE: @attached_object.code
) %>
</p>
<% else %>
<p><%= t('.body.enjoy_a_discount_of_AMOUNT_with_code_CODE',
AMOUNT: number_to_currency(@attached_object.amount_off / 100.00),
CODE: @attached_object.code
) %>
</p>
<% end %>
<%
# we must tell the use if he could use the code just once or many times (in case we won't specify)
@ -21,6 +29,7 @@
<%= _t('.body.this_coupon_is_valid_USAGE_times_until_DATE_for_all_your_purchases',
{
USAGE: usages,
TYPE: @attached_object.type,
DATE: @attached_object.valid_until.nil? ? 'NO-DATE' : I18n.l(@attached_object.valid_until.to_date)
})
# messageFormat

View File

@ -16,7 +16,7 @@ class ReservationReminderWorker
already_sent = Notification.where(
attached_object_type: Reservation.name,
attached_object_id: r.id,
notification_type_id: NotificationType.find_by_name('notify_member_reservation_reminder')
notification_type_id: NotificationType.find_by(name: 'notify_member_reservation_reminder')
).count
unless already_sent > 0
NotificationCenter.call type: 'notify_member_reservation_reminder',

View File

@ -20,8 +20,14 @@ class StripeWorker
stp_coupon = {
id: coupon.code,
duration: coupon.validity_per_user,
percent_off: coupon.percent_off,
}
if coupon.type == 'percent_off'
stp_coupon[:percent_off] = coupon.percent_off
elsif coupon.type == 'amount_off'
stp_coupon[:amount_off] = coupon.amount_off
stp_coupon[:currency] = Rails.application.secrets.stripe_currency
end
unless coupon.valid_until.nil?
stp_coupon[:redeem_by] = coupon.valid_until.to_i
end

View File

@ -164,7 +164,7 @@ en:
unable_to_delete_the_specified_subscription_an_error_occurred: "Unable to delete the specified subscription, an error occurred."
coupons: "Coupons"
list_of_the_coupons: "List of the coupons"
percentage_off: "Percentage off"
discount: "Discount"
nb_of_usages: "Number of usages"
status: "Status"
add_a_new_coupon: "Add a new coupon"

View File

@ -164,7 +164,7 @@ fr:
unable_to_delete_the_specified_subscription_an_error_occurred: "Impossible de supprimer l'abonnement spécifié, une erreur s'est produite."
coupons: "Codes promotionnels"
list_of_the_coupons: "Liste des codes promotionnels"
percentage_off: "Pourcentage de réduction"
discount: "Réduction"
nb_of_usages: "Nombre d'utilisations"
status: "Statut"
add_a_new_coupon: "Ajouter un code promotionnel"

View File

@ -346,6 +346,10 @@ en:
code: "Code"
code_is_required: "Code is required."
code_must_be_composed_of_capital_letters_digits_and_or_dashes: "The code must be composed of capital letters, digits and/or dashes."
kind_of_coupon: "Kind of coupon"
percentage: "Percentage"
amount: "Amount"
amount_off: "Amount off"
percent_off: "Percentage off"
percent_off_is_required: "Percentage off is required."
percentage_must_be_between_0_and_100: "Percentage must be between 0 and 100."
@ -363,9 +367,12 @@ en:
# coupon (input zone for users)
i_have_a_coupon: "I have a coupon!"
code_: "Code:"
the_coupon_has_been_applied_you_get_PERCENT_discount: "The coupon has been applied. You get {{PERCENT}}% discount." # angular interpolation
the_coupon_has_been_applied_you_get_PERCENT_discount: "The coupon has been applied. You get a {{PERCENT}}% discount." # angular interpolation
the_coupon_has_been_applied_you_get_AMOUNT_CURRENCY: "The coupon has been applied. You get a discount of {{AMOUNT}} {{CURRENCY}}." # angular interpolation
unable_to_apply_the_coupon_because_disabled: "Unable to apply the coupon: this code was disabled."
unable_to_apply_the_coupon_because_expired: "Unable to apply the coupon: this code has expired."
unable_to_apply_the_coupon_because_sold_out: "Unable to apply the coupon: this code reached its quota."
unable_to_apply_the_coupon_because_already_used: "Unable to apply the coupon: you have already used this code once before."
unable_to_apply_the_coupon_because_amount_exceeded: "Unable to apply the coupon: the discount exceed the total amount of this purchase."
unable_to_apply_the_coupon_because_undefined: "Unable to apply the coupon: an unexpected error occurred, please contact the Fablab's manager."
unable_to_apply_the_coupon_because_rejected: "This code does not exists."

View File

@ -346,6 +346,10 @@ fr:
code: "Code"
code_is_required: "Le code est requis."
code_must_be_composed_of_capital_letters_digits_and_or_dashes: "Le code doit être composé de lettres majuscules, de chiffres et/ou de tirets."
kind_of_coupon: "Type de réduction"
percentage: "Pourcentage"
amount: "Montant"
amount_off: "Montant de la réduction"
percent_off: "Pourcentage de réduction"
percent_off_is_required: "Le pourcentage de réduction est requis."
percentage_must_be_between_0_and_100: "Le pourcentage doit être compris entre 0 et 100."
@ -364,8 +368,11 @@ fr:
i_have_a_coupon: "J'ai un code promo !"
code_: "Code :"
the_coupon_has_been_applied_you_get_PERCENT_discount: "Le code promo a bien été appliqué. Vous bénéficiez d'une remise de {{PERCENT}} %." # angular interpolation
the_coupon_has_been_applied_you_get_AMOUNT_CURRENCY: "Le code promo a bien été appliqué. Vous bénéficiez d'une remise de {{AMOUNT}} {{CURRENCY}}." # angular interpolation
unable_to_apply_the_coupon_because_disabled: "Impossible d'appliquer la réduction : ce code promo a été désactivé."
unable_to_apply_the_coupon_because_expired: "Impossible d'appliquer la réduction : ce code promo a expiré."
unable_to_apply_the_coupon_because_sold_out: "Impossible d'appliquer la réduction : ce code promo a atteint son quota."
unable_to_apply_the_coupon_because_already_used: "Impossible d'appliquer la réduction : vous avez déjà utilisé ce code promo par le passé."
unable_to_apply_the_coupon_because_amount_exceeded: "Impossible d'appliquer la réduction : la réduction dépasse le total de cet achat."
unable_to_apply_the_coupon_because_undefined: "Impossible d'appliquer la réduction : une erreur inattendue s'est produite, veuillez contacter le gestionnaire du Fablab."
unable_to_apply_the_coupon_because_rejected: "Ce code promo n'existe pas."

View File

@ -87,7 +87,7 @@ en:
one: "One %{NAME} ticket"
other: "%{count} %{NAME} tickets"
reservation_other: "Reservation (other)"
coupon_CODE_discount_of_PERCENT: "Coupon %{CODE}: discount of %{PERCENT}%"
coupon_CODE_discount_of_DISCOUNT: "Coupon {CODE}: discount of {DISCOUNT}{TYPE, select, percent_off{%} other{}}" # messageFormat interpolation
total_including_all_taxes: "Total incl. all taxes"
including_VAT_RATE: "Including VAT %{RATE}%"
including_total_excluding_taxes: "Including Total excl. taxes"

View File

@ -87,7 +87,7 @@ fr:
one: "Une place %{NAME}"
other: "%{count} places %{NAME}"
reservation_other: "Réservation (autre)"
coupon_CODE_discount_of_PERCENT: "Code %{CODE} : remise de %{PERCENT} %"
coupon_CODE_discount_of_DISCOUNT: "Code {CODE} : remise de {DISCOUNT} {TYPE, select, percent_off{%} other{}}" # messageFormat interpolation
total_including_all_taxes: "Total TTC"
including_VAT_RATE: "Dont TVA %{RATE}%"
including_total_excluding_taxes: "Dont total HT"

View File

@ -278,7 +278,8 @@ en:
subject: "Coupon"
body:
enjoy_a_discount_of_PERCENT_with_code_CODE: "Enjoy a discount of %{PERCENT}% on the whole site with the code %{CODE}."
this_coupon_is_valid_USAGE_times_until_DATE_for_all_your_purchases: "This coupon is valid {USAGE, plural, =1{just once} other{many times}}: for all your purchases, from now {DATE, select, NO-DATE{and without time limit} other{and until {DATE}}}."
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Enjoy a discount of %{AMOUNT} on the whole site with the code %{CODE}."
this_coupon_is_valid_USAGE_times_until_DATE_for_all_your_purchases: "This coupon is valid {USAGE, plural, =1{just once} other{many times}}: for all your purchases {TYPE, select, amount_off{at least equal to the amount of the coupon} other{}}, from now {DATE, select, NO-DATE{and without time limit} other{and until {DATE}}}."
shared:
hello: "Hello %{user_name}"

View File

@ -278,7 +278,8 @@ fr:
subject: "Code promo"
body:
enjoy_a_discount_of_PERCENT_with_code_CODE: "Bénéficiez d'une remise de %{PERCENT} % sur tout le site en utilisant le code promo %{CODE}."
this_coupon_is_valid_USAGE_times_until_DATE_for_all_your_purchases: "Ce code promo est valable {USAGE, plural, =1{une seule fois} other{plusieurs fois}} : pour tous vos achats, dès maintenant {DATE, select, NO-DATE{et sans limitation de durée} other{et jusqu'au {DATE}}}."
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Bénéficiez d'une remise de %{AMOUNT} sur tout le site en utilisant le code promo %{CODE}."
this_coupon_is_valid_USAGE_times_until_DATE_for_all_your_purchases: "Ce code promo est valable {USAGE, plural, =1{une seule fois} other{plusieurs fois}} : pour tous vos achats {TYPE, select, amount_off{dont le montant est au moins égal à celui du code promo} other{}}, dès maintenant {DATE, select, NO-DATE{et sans limitation de durée} other{et jusqu'au {DATE}}}."
shared:
hello: "Bonjour %{user_name}"

View File

@ -28,7 +28,7 @@ class MigrateEventReducedAmountToPriceCategory < ActiveRecord::Migration
end
def down
pc = PriceCategory.find_by_name(I18n.t('price_category.reduced_fare'))
pc = PriceCategory.find_by(name: I18n.t('price_category.reduced_fare'))
EventPriceCategory.where(price_category_id: pc.id).each do |epc|
epc.event.update_column(:reduced_amount, epc.amount)

View File

@ -1,7 +1,7 @@
class InsertCustomAggregations < ActiveRecord::Migration
def up
# available reservations hours for machines
machine = StatisticIndex.find_by_es_type_key('machine')
machine = StatisticIndex.find_by(es_type_key: 'machine')
machine_hours = StatisticType.find_by(key: 'hour', statistic_index_id: machine.id)
available_hours = StatisticCustomAggregation.new({
@ -14,7 +14,7 @@ class InsertCustomAggregations < ActiveRecord::Migration
available_hours.save!
# available training tickets
training = StatisticIndex.find_by_es_type_key('training')
training = StatisticIndex.find_by(es_type_key: 'training')
training_bookings = StatisticType.find_by(key: 'booking', statistic_index_id: training.id)
available_tickets = StatisticCustomAggregation.new({
@ -29,12 +29,12 @@ class InsertCustomAggregations < ActiveRecord::Migration
def down
machine = StatisticIndex.find_by_es_type_key('machine')
machine = StatisticIndex.find_by(es_type_key: 'machine')
machine_hours = StatisticType.find_by(key: 'hour', statistic_index_id: machine.id)
StatisticCustomAggregation.where(field: 'available_hours', statistic_type_id: machine_hours.id).first.destroy!
training = StatisticIndex.find_by_es_type_key('training')
training = StatisticIndex.find_by(es_type_key: 'training')
training_bookings = StatisticType.find_by(key: 'booking', statistic_index_id: training.id)
StatisticCustomAggregation.where(field: 'available_tickets', statistic_type_id: training_bookings.id).first.destroy!

View File

@ -0,0 +1,5 @@
class AddAmountOffToCoupons < ActiveRecord::Migration
def change
add_column :coupons, :amount_off, :integer
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160915105234) do
ActiveRecord::Schema.define(version: 20161123104604) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -114,6 +114,7 @@ ActiveRecord::Schema.define(version: 20160915105234) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "validity_per_user"
t.integer "amount_off"
end
create_table "credits", force: :cascade do |t|
@ -319,6 +320,7 @@ ActiveRecord::Schema.define(version: 20160915105234) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "profile_url"
t.string "logout_endpoint"
end
create_table "offer_days", force: :cascade do |t|

View File

@ -2,7 +2,7 @@ namespace :fablab do
# desc "Get all stripe plans and create in fablab app"
# task stripe_plan: :environment do
# Stripe::Plan.all.data.each do |plan|
# unless Plan.find_by_stp_plan_id(plan.id)
# unless Plan.find_by(stp_plan_id: plan.id)
# group = Group.friendly.find(plan.id.split('-').first)
# if group
# Plan.create(stp_plan_id: plan.id, name: plan.name, amount: plan.amount, interval: plan.interval, group_id: group.id, skip_create_stripe_plan: true)

View File

@ -159,3 +159,13 @@ availability_16:
updated_at: 2016-04-04 15:44:04.023557000 Z
nb_total_places:
destroying: false
availability_17:
id: 17
start_at: <%= 10.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
available_type: machines
created_at: 2016-04-04 15:44:04.023557000 Z
updated_at: 2016-04-04 15:44:04.023557000 Z
nb_total_places:
destroying: false

View File

@ -17,3 +17,12 @@ two:
max_usages: 10
active: true
validity_per_user: always
cash:
name: Cash Code
code: ZERG6H1R65H
amount_off: 10000
valid_until: <%= 1.year.from_now.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
max_usages: 1
active: true
validity_per_user: once

View File

@ -11,3 +11,15 @@ two:
event_id: 2
price_category_id: 1
amount: 1700
radio_youngs:
id: 3
event_id: 4
price_category_id: 1
amount: 7500
radio_unemployed:
id: 4
event_id: 4
price_category_id: 2
amount: 8000

View File

@ -41,3 +41,16 @@ event_1:
nb_free_places: 10
recurrence_id: 1
category_id: 2
event_4:
id: 4
title: Radio numérique
description: "Confectionnez votre propre récepteur FM compatible avec la radio numérique terrestre (RNT) :\r\n- logiciel
embarqué\r\n- électronique\r\n- démodulation d'ondes porteuses\r\n- conception et réalisation du design"
created_at: 2016-11-28 10:52:50.615611000 Z
updated_at: 2016-11-28 10:52:50.615611000 Z
availability_id: 17
amount: 10000
nb_total_places: 10
nb_free_places: 10
category_id: 1

View File

@ -3,7 +3,7 @@ class AdminsTest < ActionDispatch::IntegrationTest
# Called before every test method runs. Can be used
# to set up fixture information.
def setup
@admin = User.find_by_username('admin')
@admin = User.find_by(username: 'admin')
login_as(@admin, scope: :user)
end

View File

@ -20,7 +20,7 @@ module Availabilities
end
test 'get machine availabilities as admin' do
m = Machine.find_by_slug('decoupeuse-vinyle')
m = Machine.find_by(slug: 'decoupeuse-vinyle')
get "/api/availabilities/machines/#{m.id}"

View File

@ -1,11 +1,11 @@
class Availabilities::AsUserTest < ActionDispatch::IntegrationTest
setup do
user = User.find_by_username('kdumas')
user = User.find_by(username: 'kdumas')
login_as(user, scope: :user)
end
test 'get machine availabilities as user' do
m = Machine.find_by_slug('decoupeuse-vinyle')
m = Machine.find_by(slug: 'decoupeuse-vinyle')
get "/api/availabilities/machines/#{m.id}"

View File

@ -0,0 +1,206 @@
module Events
class AsAdminTest < ActionDispatch::IntegrationTest
setup do
admin = User.with_role(:admin).first
login_as(admin, scope: :user)
end
test 'creation modification reservation and re-modification scenario' do
# First, we create a new event
post '/api/events',
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0
}
}.to_json,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the event was created correctly
event = json_response(response.body)
e = Event.where(id: event[:id]).first
assert_not_nil e, 'Event was not created in database'
# Check the remaining free places are not defined
assert_nil e.nb_free_places, "Free places shouldn't be defined"
# Then, modify the event to set a nb of places
put "/api/events/#{e.id}",
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0,
nb_total_places: 10
}
}
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the places numbers were updated successfully
e = Event.where(id: event[:id]).first
assert_equal 10, e.nb_total_places, 'Total number of places was not updated'
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',
{
reservation: {
user_id: User.find_by(username: 'pdurand').id,
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,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the remaining places were updated successfully
e = Event.where(id: event[:id]).first
assert_equal 8, e.nb_free_places, 'Number of free places was not updated'
# Finally, modify the event to add some places
put "/api/events/#{e.id}",
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0,
nb_total_places: 20
}
}
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the places numbers were updated successfully
e = Event.where(id: event[:id]).first
assert_equal 20, e.nb_total_places, 'Total number of places was not updated'
assert_equal 18, e.nb_free_places, 'Number of free places was not updated'
end
test 'create event with custom price and reserve it with success' do
price_category = PriceCategory.first
# First, we create a new event
post '/api/events',
{
event: {
title: 'Electronics initiation',
description: 'A workshop about electronics and the abilities to create robots.',
start_date: 1.week.from_now.utc + 2.days,
start_time: 1.week.from_now.utc.change({hour: 18}) + 2.days,
end_date: 1.week.from_now.utc + 2.days,
end_time: 1.week.from_now.utc.change({hour: 22}) + 2.days,
category_id: Category.last.id,
amount: 20,
nb_total_places: 10,
event_price_categories_attributes: [
{
price_category_id: price_category.id.to_s,
amount: 16.to_s
}
]
}
}.to_json,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the event was created correctly
event = json_response(response.body)
e = Event.where(id: event[:id]).first
assert_not_nil e, 'Event was not created in database'
# Check the places numbers were set successfully
e = Event.where(id: event[:id]).first
assert_equal 10, e.nb_total_places, 'Total number of places was not updated'
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',
{
reservation: {
user_id: User.find_by(username: 'lseguin').id,
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
}
]
}
}.to_json,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the reservation match the required event
reservation = json_response(response.body)
r = Reservation.find(reservation[:id])
assert_equal e.id, r.reservable_id
assert_equal 'Event', r.reservable_type
# Check the remaining places were updated successfully
e = Event.where(id: event[:id]).first
assert_equal 2, e.nb_free_places, 'Number of free places was not updated'
# Check the resulting invoice generation and it has right price
assert_invoice_pdf r.invoice
assert_equal (4 * 20) + (4 * 16), r.invoice.total / 100.0
end
end
end

View File

@ -0,0 +1,103 @@
module Events
class AsUserTest < ActionDispatch::IntegrationTest
test 'reserve event with many prices and payment means' do
vlonchamp = User.find_by(username: 'vlonchamp')
login_as(vlonchamp, scope: :user)
radio = Event.find(4)
availability = radio.availability
reservations_count = Reservation.count
invoice_count = Invoice.count
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
wallet_transactions_count = WalletTransaction.count
# Reserve the 'radio' event
VCR.use_cassette('reserve_event_with_many_prices_and_payment_means') do
post reservations_path, {
reservation: {
user_id: User.find_by(username: 'vlonchamp').id,
reservable_id: radio.id,
reservable_type: 'Event',
nb_reserve_places: 2,
card_token: stripe_card_token,
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
}
]
},
coupon_code: 'SUNNYFABLAB'
}.to_json, default_headers
end
# general assertions
assert_equal 201, response.status
assert_equal reservations_count + 1, Reservation.count
assert_equal invoice_count + 1, Invoice.count
assert_equal invoice_items_count + 1, InvoiceItem.count
assert_equal users_credit_count, UsersCredit.count
assert_equal wallet_transactions_count + 1, WalletTransaction.count
# reservation assertions
reservation = Reservation.last
assert reservation.invoice
refute reservation.stp_invoice_id.blank?
assert_equal 1, reservation.invoice.invoice_items.count
# invoice assertions
invoice = reservation.invoice
refute invoice.stp_invoice_id.blank?
refute invoice.total.blank?
assert_equal 43350, invoice.total
# invoice_items assertions
## reservation
reservation_item = invoice.invoice_items.first
assert_not_nil reservation_item
assert reservation_item.stp_invoice_item_id
assert_equal 51000, reservation_item.amount
# invoice assertions
invoice = Invoice.find_by(invoiced: reservation)
assert_invoice_pdf invoice
VCR.use_cassette('reserve_event_with_many_prices_and_payment_means_retrieve_invoice_from_stripe') do
stp_invoice = Stripe::Invoice.retrieve(invoice.stp_invoice_id)
assert_equal stp_invoice.total, (invoice.total - invoice.wallet_amount)
end
# wallet assertions
assert_equal vlonchamp.wallet.amount, 0
assert_equal vlonchamp.wallet.wallet_transactions.count, 2
transaction = vlonchamp.wallet.wallet_transactions.last
assert_equal transaction.transaction_type, 'debit'
assert_equal transaction.amount, 10
assert_equal transaction.amount, invoice.wallet_amount / 100.0
# notifications
assert_not_empty Notification.where(attached_object: reservation)
assert_not_empty Notification.where(attached_object: invoice)
end
end
end

View File

@ -1,204 +0,0 @@
class EventsTest < ActionDispatch::IntegrationTest
setup do
admin = User.with_role(:admin).first
login_as(admin, scope: :user)
end
test 'creation modification reservation and re-modification scenario' do
# First, we create a new event
post '/api/events',
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0
}
}.to_json,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the event was created correctly
event = json_response(response.body)
e = Event.where(id: event[:id]).first
assert_not_nil e, 'Event was not created in database'
# Check the remaining free places are not defined
assert_nil e.nb_free_places, "Free places shouldn't be defined"
# Then, modify the event to set a nb of places
put "/api/events/#{e.id}",
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0,
nb_total_places: 10
}
}
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the places numbers were updated successfully
e = Event.where(id: event[:id]).first
assert_equal 10, e.nb_total_places, 'Total number of places was not updated'
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',
{
reservation: {
user_id: User.find_by_username('pdurand').id,
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,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the remaining places were updated successfully
e = Event.where(id: event[:id]).first
assert_equal 8, e.nb_free_places, 'Number of free places was not updated'
# Finally, modify the event to add some places
put "/api/events/#{e.id}",
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0,
nb_total_places: 20
}
}
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the places numbers were updated successfully
e = Event.where(id: event[:id]).first
assert_equal 20, e.nb_total_places, 'Total number of places was not updated'
assert_equal 18, e.nb_free_places, 'Number of free places was not updated'
end
test 'create event with custom price and reserve it with success' do
price_category = PriceCategory.first
# First, we create a new event
post '/api/events',
{
event: {
title: 'Electronics initiation',
description: 'A workshop about electronics and the abilities to create robots.',
start_date: 1.week.from_now.utc + 2.days,
start_time: 1.week.from_now.utc.change({hour: 18}) + 2.days,
end_date: 1.week.from_now.utc + 2.days,
end_time: 1.week.from_now.utc.change({hour: 22}) + 2.days,
category_id: Category.last.id,
amount: 20,
nb_total_places: 10,
event_price_categories_attributes: [
{
price_category_id: price_category.id.to_s,
amount: 16.to_s
}
]
}
}.to_json,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the event was created correctly
event = json_response(response.body)
e = Event.where(id: event[:id]).first
assert_not_nil e, 'Event was not created in database'
# Check the places numbers were set successfully
e = Event.where(id: event[:id]).first
assert_equal 10, e.nb_total_places, 'Total number of places was not updated'
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',
{
reservation: {
user_id: User.find_by_username('lseguin').id,
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
}
]
}
}.to_json,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check the reservation match the required event
reservation = json_response(response.body)
r = Reservation.find(reservation[:id])
assert_equal e.id, r.reservable_id
assert_equal 'Event', r.reservable_type
# Check the remaining places were updated successfully
e = Event.where(id: event[:id]).first
assert_equal 2, e.nb_free_places, 'Number of free places was not updated'
# Check the resulting invoice generation and it has right price
assert_invoice_pdf r.invoice
assert_equal (4 * 20) + (4 * 16), r.invoice.total / 100.0
end
end

View File

@ -3,12 +3,12 @@ module Subscriptions
setup do
@admin = User.find_by_username('admin')
@admin = User.find_by(username: 'admin')
login_as(@admin, scope: :user)
end
test "admin successfully takes a subscription for a user" do
user = User.find_by_username('jdupond')
user = User.find_by(username: 'jdupond')
plan = Plan.find_by(group_id: user.group.id, type: 'Plan', base_name: 'Mensuel')
VCR.use_cassette("subscriptions_admin_create_success") do
@ -40,7 +40,7 @@ module Subscriptions
assert_equal user.subscription.plan.training_credit_nb, plan.training_credit_nb, 'trainings credits were not allocated'
# Check that the user benefit from prices of his plan
printer = Machine.find_by_slug('imprimante-3d')
printer = Machine.find_by(slug: 'imprimante-3d')
assert_equal 15, (printer.prices.find_by(group_id: user.group_id, plan_id: user.subscription.plan_id).amount / 100), 'machine hourly price does not match'
# Check notification was sent to the user

View File

@ -2,7 +2,7 @@ class Subscriptions::CreateAsUserTest < ActionDispatch::IntegrationTest
setup do
@user = User.find_by_username('jdupond')
@user = User.find_by(username: 'jdupond')
login_as(@user, scope: :user)
end
@ -38,7 +38,7 @@ class Subscriptions::CreateAsUserTest < ActionDispatch::IntegrationTest
assert_equal @user.subscription.plan.training_credit_nb, plan.training_credit_nb, 'trainings credits were not allocated'
# Check that the user benefit from prices of his plan
printer = Machine.find_by_slug('imprimante-3d')
printer = Machine.find_by(slug: 'imprimante-3d')
assert_equal 15, (printer.prices.find_by(group_id: @user.group_id, plan_id: @user.subscription.plan_id).amount / 100), 'machine hourly price does not match'
# Check notifications were sent for every admins
@ -118,7 +118,7 @@ class Subscriptions::CreateAsUserTest < ActionDispatch::IntegrationTest
assert_equal @vlonchamp.subscription.plan.training_credit_nb, plan.training_credit_nb, 'trainings credits were not allocated'
# Check that the user benefit from prices of his plan
printer = Machine.find_by_slug('imprimante-3d')
printer = Machine.find_by(slug: 'imprimante-3d')
assert_equal 10, (printer.prices.find_by(group_id: @vlonchamp.group_id, plan_id: @vlonchamp.subscription.plan_id).amount / 100), 'machine hourly price does not match'
# Check notifications were sent for every admins

View File

@ -1,13 +1,13 @@
class Subscriptions::RenewAsAdminTest < ActionDispatch::IntegrationTest
setup do
@admin = User.find_by_username('admin')
@admin = User.find_by(username: 'admin')
login_as(@admin, scope: :user)
end
test 'admin successfully renew a subscription before it has ended' do
user = User.find_by_username('kdumas')
user = User.find_by(username: 'kdumas')
plan = Plan.find_by(base_name: 'Mensuel tarif réduit')
VCR.use_cassette("subscriptions_admin_renew_success") do
@ -38,7 +38,7 @@ class Subscriptions::RenewAsAdminTest < ActionDispatch::IntegrationTest
assert_equal user.subscription.plan.training_credit_nb, plan.training_credit_nb, 'trainings credits were not allocated'
# Check that the user benefit from prices of his plan
printer = Machine.find_by_slug('imprimante-3d')
printer = Machine.find_by(slug: 'imprimante-3d')
assert_equal 10, (printer.prices.find_by(group_id: user.group_id, plan_id: user.subscription.plan_id).amount / 100), 'machine hourly price does not match'
# Check notification was sent to the user

View File

@ -2,7 +2,7 @@ class Subscriptions::RenewAsUserTest < ActionDispatch::IntegrationTest
setup do
@user = User.find_by_username('lseguin')
@user = User.find_by(username: 'lseguin')
login_as(@user, scope: :user)
end

View File

@ -24,7 +24,7 @@ class WalletsTest < ActionDispatch::IntegrationTest
end
test 'admin can get wallet by user id' do
@admin = User.find_by_username('admin')
@admin = User.find_by(username: 'admin')
login_as(@admin, scope: :user)
@user1 = User.first
get "/api/wallet/by_user/#{@user1.id}"

View File

@ -7,12 +7,22 @@ class CouponTest < ActiveSupport::TestCase
end
test 'expired coupon must return the proper status' do
c = Coupon.find_by_code('XMAS10')
assert c.status == 'expired'
c = Coupon.find_by(code: 'XMAS10')
assert_equal 'expired', c.status
end
test 'two coupons cannot have the same code' do
c = Coupon.new({name: 'Summer deals', code: 'SUNNYFABLAB', percent_off: 15, validity_per_user: 'always'})
assert c.invalid?
end
test 'coupon with cash amount has amount_off type' do
c = Coupon.new({name: 'Essential Box', code: 'KWXX2M', amount_off: 2000, validity_per_user: 'once', max_usages: 1})
assert_equal 'amount_off', c.type
end
test 'coupon with cash amount cannot be used with cheaper cart' do
c = Coupon.new({name: 'Premium Box', code: '6DDX2T44MQ', amount_off: 20000, validity_per_user: 'once', max_usages: 1, active: true})
assert_equal 'amount_exceeded', c.status(User.find_by(username: 'jdupond').id, 2000)
end
end

View File

@ -0,0 +1,28 @@
require 'test_helper'
class CouponApplyServiceTest < ActiveSupport::TestCase
setup do
@jdupond = User.find_by(username: 'jdupond')
@cash_coupon = Coupon.find_by(code: 'ZERG6H1R65H')
end
test 'user apply percent coupon to cart' do
total = CouponApplyService.new.(1000, 'SUNNYFABLAB', @jdupond.id)
assert_equal 850, total
end
test 'user cannot apply excessive coupon to cart' do
total = CouponApplyService.new.(1000, @cash_coupon, @jdupond.id)
assert_equal 1000, total
end
test 'user cannot apply invalid coupon to cart' do
total = CouponApplyService.new.(1000, 'INVALIDCODE', @jdupond.id)
assert_equal 1000, total
end
test 'user cannot apply expired coupon to cart' do
total = CouponApplyService.new.(1000, 'XMAS10', @jdupond.id)
assert_equal 1000, total
end
end

View File

@ -0,0 +1,954 @@
---
http_interactions:
- request:
method: post
uri: https://api.stripe.com/v1/tokens
body:
encoding: UTF-8
string: card[number]=4242424242424242&card[exp_month]=4&card[exp_year]=2017&card[cvc]=314
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
Content-Length:
- '81'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:21 GMT
Content-Type:
- application/json
Content-Length:
- '779'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4YKniSPeMq2N
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: UTF-8
string: |
{
"id": "tok_19KmOv2sOmf47Nz90KKaBGgJ",
"object": "token",
"card": {
"id": "card_19KmOv2sOmf47Nz94YYaGGHa",
"object": "card",
"address_city": null,
"address_country": null,
"address_line1": null,
"address_line1_check": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": null,
"brand": "Visa",
"country": "US",
"cvc_check": "unchecked",
"dynamic_last4": null,
"exp_month": 4,
"exp_year": 2017,
"fingerprint": "o52jybR7bnmNn6AT",
"funding": "credit",
"last4": "4242",
"metadata": {},
"name": null,
"tokenization_method": null
},
"client_ip": "90.52.237.73",
"created": 1480329021,
"livemode": false,
"type": "card",
"used": false
}
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:21 GMT
- request:
method: post
uri: https://api.stripe.com/v1/invoiceitems
body:
encoding: UTF-8
string: customer=cus_8CzNtM08NVlSGN&amount=51000&currency=usd&description=Radio+num%C3%A9rique+%0ADecember+08%2C+2016+11%3A00+AM+-+07%3A00+PM%0ADecember+09%2C+2016+11%3A00+AM+-+07%3A00+PM%0ADecember+10%2C+2016+11%3A00+AM+-+07%3A00+PM
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
Content-Length:
- '225'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:22 GMT
Content-Type:
- application/json
Content-Length:
- '552'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4Y3qKoP2Yqmq
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: ASCII-8BIT
string: !binary |-
ewogICJpZCI6ICJpaV8xOUttT3cyc09tZjQ3Tno5ZjBQYktiSHEiLAogICJv
YmplY3QiOiAiaW52b2ljZWl0ZW0iLAogICJhbW91bnQiOiA1MTAwMCwKICAi
Y3VycmVuY3kiOiAidXNkIiwKICAiY3VzdG9tZXIiOiAiY3VzXzhDek50TTA4
TlZsU0dOIiwKICAiZGF0ZSI6IDE0ODAzMjkwMjIsCiAgImRlc2NyaXB0aW9u
IjogIlJhZGlvIG51bcOpcmlxdWUgXG5EZWNlbWJlciAwOCwgMjAxNiAxMTow
MCBBTSAtIDA3OjAwIFBNXG5EZWNlbWJlciAwOSwgMjAxNiAxMTowMCBBTSAt
IDA3OjAwIFBNXG5EZWNlbWJlciAxMCwgMjAxNiAxMTowMCBBTSAtIDA3OjAw
IFBNIiwKICAiZGlzY291bnRhYmxlIjogdHJ1ZSwKICAiaW52b2ljZSI6IG51
bGwsCiAgImxpdmVtb2RlIjogZmFsc2UsCiAgIm1ldGFkYXRhIjoge30sCiAg
InBlcmlvZCI6IHsKICAgICJzdGFydCI6IDE0ODAzMjkwMjIsCiAgICAiZW5k
IjogMTQ4MDMyOTAyMgogIH0sCiAgInBsYW4iOiBudWxsLAogICJwcm9yYXRp
b24iOiBmYWxzZSwKICAicXVhbnRpdHkiOiBudWxsLAogICJzdWJzY3JpcHRp
b24iOiBudWxsCn0K
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:22 GMT
- request:
method: post
uri: https://api.stripe.com/v1/invoiceitems
body:
encoding: UTF-8
string: customer=cus_8CzNtM08NVlSGN&amount=-7650&currency=usd&description=coupon+SUNNYFABLAB+-+reservation
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
Content-Length:
- '98'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:23 GMT
Content-Type:
- application/json
Content-Length:
- '451'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4YzTw5ITNQ7t
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: UTF-8
string: |
{
"id": "ii_19KmOw2sOmf47Nz9fMgjHIhg",
"object": "invoiceitem",
"amount": -7650,
"currency": "usd",
"customer": "cus_8CzNtM08NVlSGN",
"date": 1480329022,
"description": "coupon SUNNYFABLAB - reservation",
"discountable": false,
"invoice": null,
"livemode": false,
"metadata": {},
"period": {
"start": 1480329022,
"end": 1480329022
},
"plan": null,
"proration": false,
"quantity": null,
"subscription": null
}
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:23 GMT
- request:
method: post
uri: https://api.stripe.com/v1/invoiceitems
body:
encoding: UTF-8
string: customer=cus_8CzNtM08NVlSGN&amount=-1000&currency=usd&description=wallet+-10.0
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
Content-Length:
- '78'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:23 GMT
Content-Type:
- application/json
Content-Length:
- '431'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4YHcxKC6TIpx
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: UTF-8
string: |
{
"id": "ii_19KmOx2sOmf47Nz9XIzvTjLC",
"object": "invoiceitem",
"amount": -1000,
"currency": "usd",
"customer": "cus_8CzNtM08NVlSGN",
"date": 1480329023,
"description": "wallet -10.0",
"discountable": false,
"invoice": null,
"livemode": false,
"metadata": {},
"period": {
"start": 1480329023,
"end": 1480329023
},
"plan": null,
"proration": false,
"quantity": null,
"subscription": null
}
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:23 GMT
- request:
method: get
uri: https://api.stripe.com/v1/customers/cus_8CzNtM08NVlSGN
body:
encoding: US-ASCII
string: ''
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:24 GMT
Content-Type:
- application/json
Content-Length:
- '1415'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4YUv3IAopxIK
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: UTF-8
string: |
{
"id": "cus_8CzNtM08NVlSGN",
"object": "customer",
"account_balance": 0,
"created": 1459782849,
"currency": "usd",
"default_source": "card_18ZhUf2sOmf47Nz9LhkQeEmO",
"delinquent": false,
"description": "Vanessa Lonchamp",
"discount": null,
"email": "vanessa.lonchamp@sfr.fr",
"livemode": false,
"metadata": {},
"shipping": null,
"sources": {
"object": "list",
"data": [
{
"id": "card_18ZhUf2sOmf47Nz9LhkQeEmO",
"object": "card",
"address_city": null,
"address_country": null,
"address_line1": null,
"address_line1_check": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": null,
"brand": "Visa",
"country": "US",
"customer": "cus_8CzNtM08NVlSGN",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 4,
"exp_year": 2017,
"fingerprint": "o52jybR7bnmNn6AT",
"funding": "credit",
"last4": "4242",
"metadata": {},
"name": null,
"tokenization_method": null
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/customers/cus_8CzNtM08NVlSGN/sources"
},
"subscriptions": {
"object": "list",
"data": [],
"has_more": false,
"total_count": 0,
"url": "/v1/customers/cus_8CzNtM08NVlSGN/subscriptions"
}
}
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:24 GMT
- request:
method: post
uri: https://api.stripe.com/v1/customers/cus_8CzNtM08NVlSGN/sources
body:
encoding: UTF-8
string: card=tok_19KmOv2sOmf47Nz90KKaBGgJ
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
Content-Length:
- '33'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:25 GMT
Content-Type:
- application/json
Content-Length:
- '577'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4Ychb7UhSBol
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: UTF-8
string: |
{
"id": "card_19KmOv2sOmf47Nz94YYaGGHa",
"object": "card",
"address_city": null,
"address_country": null,
"address_line1": null,
"address_line1_check": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": null,
"brand": "Visa",
"country": "US",
"customer": "cus_8CzNtM08NVlSGN",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 4,
"exp_year": 2017,
"fingerprint": "o52jybR7bnmNn6AT",
"funding": "credit",
"last4": "4242",
"metadata": {},
"name": null,
"tokenization_method": null
}
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:25 GMT
- request:
method: post
uri: https://api.stripe.com/v1/customers/cus_8CzNtM08NVlSGN
body:
encoding: UTF-8
string: default_source=card_19KmOv2sOmf47Nz94YYaGGHa
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
Content-Length:
- '44'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:26 GMT
Content-Type:
- application/json
Content-Length:
- '2143'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4Y7M4v5B4FLQ
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: UTF-8
string: |
{
"id": "cus_8CzNtM08NVlSGN",
"object": "customer",
"account_balance": 0,
"created": 1459782849,
"currency": "usd",
"default_source": "card_19KmOv2sOmf47Nz94YYaGGHa",
"delinquent": false,
"description": "Vanessa Lonchamp",
"discount": null,
"email": "vanessa.lonchamp@sfr.fr",
"livemode": false,
"metadata": {},
"shipping": null,
"sources": {
"object": "list",
"data": [
{
"id": "card_19KmOv2sOmf47Nz94YYaGGHa",
"object": "card",
"address_city": null,
"address_country": null,
"address_line1": null,
"address_line1_check": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": null,
"brand": "Visa",
"country": "US",
"customer": "cus_8CzNtM08NVlSGN",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 4,
"exp_year": 2017,
"fingerprint": "o52jybR7bnmNn6AT",
"funding": "credit",
"last4": "4242",
"metadata": {},
"name": null,
"tokenization_method": null
},
{
"id": "card_18ZhUf2sOmf47Nz9LhkQeEmO",
"object": "card",
"address_city": null,
"address_country": null,
"address_line1": null,
"address_line1_check": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": null,
"brand": "Visa",
"country": "US",
"customer": "cus_8CzNtM08NVlSGN",
"cvc_check": "pass",
"dynamic_last4": null,
"exp_month": 4,
"exp_year": 2017,
"fingerprint": "o52jybR7bnmNn6AT",
"funding": "credit",
"last4": "4242",
"metadata": {},
"name": null,
"tokenization_method": null
}
],
"has_more": false,
"total_count": 2,
"url": "/v1/customers/cus_8CzNtM08NVlSGN/sources"
},
"subscriptions": {
"object": "list",
"data": [],
"has_more": false,
"total_count": 0,
"url": "/v1/customers/cus_8CzNtM08NVlSGN/subscriptions"
}
}
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:26 GMT
- request:
method: post
uri: https://api.stripe.com/v1/invoices
body:
encoding: UTF-8
string: customer=cus_8CzNtM08NVlSGN
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
Content-Length:
- '27'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:27 GMT
Content-Type:
- application/json
Content-Length:
- '2516'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4YqC96ZArjnb
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: ASCII-8BIT
string: !binary |-
ewogICJpZCI6ICJpbl8xOUttUDEyc09tZjQ3Tno5Umw2YUtXRjgiLAogICJv
YmplY3QiOiAiaW52b2ljZSIsCiAgImFtb3VudF9kdWUiOiA0MjM1MCwKICAi
YXBwbGljYXRpb25fZmVlIjogbnVsbCwKICAiYXR0ZW1wdF9jb3VudCI6IDAs
CiAgImF0dGVtcHRlZCI6IGZhbHNlLAogICJjaGFyZ2UiOiBudWxsLAogICJj
bG9zZWQiOiBmYWxzZSwKICAiY3VycmVuY3kiOiAidXNkIiwKICAiY3VzdG9t
ZXIiOiAiY3VzXzhDek50TTA4TlZsU0dOIiwKICAiZGF0ZSI6IDE0ODAzMjkw
MjcsCiAgImRlc2NyaXB0aW9uIjogbnVsbCwKICAiZGlzY291bnQiOiBudWxs
LAogICJlbmRpbmdfYmFsYW5jZSI6IG51bGwsCiAgImZvcmdpdmVuIjogZmFs
c2UsCiAgImxpbmVzIjogewogICAgIm9iamVjdCI6ICJsaXN0IiwKICAgICJk
YXRhIjogWwogICAgICB7CiAgICAgICAgImlkIjogImlpXzE5S21PeDJzT21m
NDdOejlYSXp2VGpMQyIsCiAgICAgICAgIm9iamVjdCI6ICJsaW5lX2l0ZW0i
LAogICAgICAgICJhbW91bnQiOiAtMTAwMCwKICAgICAgICAiY3VycmVuY3ki
OiAidXNkIiwKICAgICAgICAiZGVzY3JpcHRpb24iOiAid2FsbGV0IC0xMC4w
IiwKICAgICAgICAiZGlzY291bnRhYmxlIjogZmFsc2UsCiAgICAgICAgImxp
dmVtb2RlIjogZmFsc2UsCiAgICAgICAgIm1ldGFkYXRhIjoge30sCiAgICAg
ICAgInBlcmlvZCI6IHsKICAgICAgICAgICJzdGFydCI6IDE0ODAzMjkwMjMs
CiAgICAgICAgICAiZW5kIjogMTQ4MDMyOTAyMwogICAgICAgIH0sCiAgICAg
ICAgInBsYW4iOiBudWxsLAogICAgICAgICJwcm9yYXRpb24iOiBmYWxzZSwK
ICAgICAgICAicXVhbnRpdHkiOiBudWxsLAogICAgICAgICJzdWJzY3JpcHRp
b24iOiBudWxsLAogICAgICAgICJ0eXBlIjogImludm9pY2VpdGVtIgogICAg
ICB9LAogICAgICB7CiAgICAgICAgImlkIjogImlpXzE5S21PdzJzT21mNDdO
ejlmTWdqSEloZyIsCiAgICAgICAgIm9iamVjdCI6ICJsaW5lX2l0ZW0iLAog
ICAgICAgICJhbW91bnQiOiAtNzY1MCwKICAgICAgICAiY3VycmVuY3kiOiAi
dXNkIiwKICAgICAgICAiZGVzY3JpcHRpb24iOiAiY291cG9uIFNVTk5ZRkFC
TEFCIC0gcmVzZXJ2YXRpb24iLAogICAgICAgICJkaXNjb3VudGFibGUiOiBm
YWxzZSwKICAgICAgICAibGl2ZW1vZGUiOiBmYWxzZSwKICAgICAgICAibWV0
YWRhdGEiOiB7fSwKICAgICAgICAicGVyaW9kIjogewogICAgICAgICAgInN0
YXJ0IjogMTQ4MDMyOTAyMiwKICAgICAgICAgICJlbmQiOiAxNDgwMzI5MDIy
CiAgICAgICAgfSwKICAgICAgICAicGxhbiI6IG51bGwsCiAgICAgICAgInBy
b3JhdGlvbiI6IGZhbHNlLAogICAgICAgICJxdWFudGl0eSI6IG51bGwsCiAg
ICAgICAgInN1YnNjcmlwdGlvbiI6IG51bGwsCiAgICAgICAgInR5cGUiOiAi
aW52b2ljZWl0ZW0iCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWQiOiAi
aWlfMTlLbU93MnNPbWY0N056OWYwUGJLYkhxIiwKICAgICAgICAib2JqZWN0
IjogImxpbmVfaXRlbSIsCiAgICAgICAgImFtb3VudCI6IDUxMDAwLAogICAg
ICAgICJjdXJyZW5jeSI6ICJ1c2QiLAogICAgICAgICJkZXNjcmlwdGlvbiI6
ICJSYWRpbyBudW3DqXJpcXVlIFxuRGVjZW1iZXIgMDgsIDIwMTYgMTE6MDAg
QU0gLSAwNzowMCBQTVxuRGVjZW1iZXIgMDksIDIwMTYgMTE6MDAgQU0gLSAw
NzowMCBQTVxuRGVjZW1iZXIgMTAsIDIwMTYgMTE6MDAgQU0gLSAwNzowMCBQ
TSIsCiAgICAgICAgImRpc2NvdW50YWJsZSI6IHRydWUsCiAgICAgICAgImxp
dmVtb2RlIjogZmFsc2UsCiAgICAgICAgIm1ldGFkYXRhIjoge30sCiAgICAg
ICAgInBlcmlvZCI6IHsKICAgICAgICAgICJzdGFydCI6IDE0ODAzMjkwMjIs
CiAgICAgICAgICAiZW5kIjogMTQ4MDMyOTAyMgogICAgICAgIH0sCiAgICAg
ICAgInBsYW4iOiBudWxsLAogICAgICAgICJwcm9yYXRpb24iOiBmYWxzZSwK
ICAgICAgICAicXVhbnRpdHkiOiBudWxsLAogICAgICAgICJzdWJzY3JpcHRp
b24iOiBudWxsLAogICAgICAgICJ0eXBlIjogImludm9pY2VpdGVtIgogICAg
ICB9CiAgICBdLAogICAgImhhc19tb3JlIjogZmFsc2UsCiAgICAidG90YWxf
Y291bnQiOiAzLAogICAgInVybCI6ICIvdjEvaW52b2ljZXMvaW5fMTlLbVAx
MnNPbWY0N056OVJsNmFLV0Y4L2xpbmVzIgogIH0sCiAgImxpdmVtb2RlIjog
ZmFsc2UsCiAgIm1ldGFkYXRhIjoge30sCiAgIm5leHRfcGF5bWVudF9hdHRl
bXB0IjogMTQ4MDMzMjYyNywKICAicGFpZCI6IGZhbHNlLAogICJwZXJpb2Rf
ZW5kIjogMTQ4MDMyOTAyNywKICAicGVyaW9kX3N0YXJ0IjogMTQ4MDMyOTAy
NywKICAicmVjZWlwdF9udW1iZXIiOiBudWxsLAogICJzdGFydGluZ19iYWxh
bmNlIjogMCwKICAic3RhdGVtZW50X2Rlc2NyaXB0b3IiOiBudWxsLAogICJz
dWJzY3JpcHRpb24iOiBudWxsLAogICJzdWJ0b3RhbCI6IDQyMzUwLAogICJ0
YXgiOiBudWxsLAogICJ0YXhfcGVyY2VudCI6IG51bGwsCiAgInRvdGFsIjog
NDIzNTAsCiAgIndlYmhvb2tzX2RlbGl2ZXJlZF9hdCI6IG51bGwKfQo=
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:27 GMT
- request:
method: post
uri: https://api.stripe.com/v1/invoices/in_19KmP12sOmf47Nz9Rl6aKWF8/pay
body:
encoding: ASCII-8BIT
string: ''
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
Content-Length:
- '0'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:28 GMT
Content-Type:
- application/json
Content-Length:
- '2535'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4Y7QI51Jw753
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: ASCII-8BIT
string: !binary |-
ewogICJpZCI6ICJpbl8xOUttUDEyc09tZjQ3Tno5Umw2YUtXRjgiLAogICJv
YmplY3QiOiAiaW52b2ljZSIsCiAgImFtb3VudF9kdWUiOiA0MjM1MCwKICAi
YXBwbGljYXRpb25fZmVlIjogbnVsbCwKICAiYXR0ZW1wdF9jb3VudCI6IDEs
CiAgImF0dGVtcHRlZCI6IHRydWUsCiAgImNoYXJnZSI6ICJjaF8xOUttUDIy
c09tZjQ3Tno5M2lmYklVMmkiLAogICJjbG9zZWQiOiB0cnVlLAogICJjdXJy
ZW5jeSI6ICJ1c2QiLAogICJjdXN0b21lciI6ICJjdXNfOEN6TnRNMDhOVmxT
R04iLAogICJkYXRlIjogMTQ4MDMyOTAyNywKICAiZGVzY3JpcHRpb24iOiBu
dWxsLAogICJkaXNjb3VudCI6IG51bGwsCiAgImVuZGluZ19iYWxhbmNlIjog
MCwKICAiZm9yZ2l2ZW4iOiBmYWxzZSwKICAibGluZXMiOiB7CiAgICAib2Jq
ZWN0IjogImxpc3QiLAogICAgImRhdGEiOiBbCiAgICAgIHsKICAgICAgICAi
aWQiOiAiaWlfMTlLbU94MnNPbWY0N056OVhJenZUakxDIiwKICAgICAgICAi
b2JqZWN0IjogImxpbmVfaXRlbSIsCiAgICAgICAgImFtb3VudCI6IC0xMDAw
LAogICAgICAgICJjdXJyZW5jeSI6ICJ1c2QiLAogICAgICAgICJkZXNjcmlw
dGlvbiI6ICJ3YWxsZXQgLTEwLjAiLAogICAgICAgICJkaXNjb3VudGFibGUi
OiBmYWxzZSwKICAgICAgICAibGl2ZW1vZGUiOiBmYWxzZSwKICAgICAgICAi
bWV0YWRhdGEiOiB7fSwKICAgICAgICAicGVyaW9kIjogewogICAgICAgICAg
InN0YXJ0IjogMTQ4MDMyOTAyMywKICAgICAgICAgICJlbmQiOiAxNDgwMzI5
MDIzCiAgICAgICAgfSwKICAgICAgICAicGxhbiI6IG51bGwsCiAgICAgICAg
InByb3JhdGlvbiI6IGZhbHNlLAogICAgICAgICJxdWFudGl0eSI6IG51bGws
CiAgICAgICAgInN1YnNjcmlwdGlvbiI6IG51bGwsCiAgICAgICAgInR5cGUi
OiAiaW52b2ljZWl0ZW0iCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWQi
OiAiaWlfMTlLbU93MnNPbWY0N056OWZNZ2pISWhnIiwKICAgICAgICAib2Jq
ZWN0IjogImxpbmVfaXRlbSIsCiAgICAgICAgImFtb3VudCI6IC03NjUwLAog
ICAgICAgICJjdXJyZW5jeSI6ICJ1c2QiLAogICAgICAgICJkZXNjcmlwdGlv
biI6ICJjb3Vwb24gU1VOTllGQUJMQUIgLSByZXNlcnZhdGlvbiIsCiAgICAg
ICAgImRpc2NvdW50YWJsZSI6IGZhbHNlLAogICAgICAgICJsaXZlbW9kZSI6
IGZhbHNlLAogICAgICAgICJtZXRhZGF0YSI6IHt9LAogICAgICAgICJwZXJp
b2QiOiB7CiAgICAgICAgICAic3RhcnQiOiAxNDgwMzI5MDIyLAogICAgICAg
ICAgImVuZCI6IDE0ODAzMjkwMjIKICAgICAgICB9LAogICAgICAgICJwbGFu
IjogbnVsbCwKICAgICAgICAicHJvcmF0aW9uIjogZmFsc2UsCiAgICAgICAg
InF1YW50aXR5IjogbnVsbCwKICAgICAgICAic3Vic2NyaXB0aW9uIjogbnVs
bCwKICAgICAgICAidHlwZSI6ICJpbnZvaWNlaXRlbSIKICAgICAgfSwKICAg
ICAgewogICAgICAgICJpZCI6ICJpaV8xOUttT3cyc09tZjQ3Tno5ZjBQYkti
SHEiLAogICAgICAgICJvYmplY3QiOiAibGluZV9pdGVtIiwKICAgICAgICAi
YW1vdW50IjogNTEwMDAsCiAgICAgICAgImN1cnJlbmN5IjogInVzZCIsCiAg
ICAgICAgImRlc2NyaXB0aW9uIjogIlJhZGlvIG51bcOpcmlxdWUgXG5EZWNl
bWJlciAwOCwgMjAxNiAxMTowMCBBTSAtIDA3OjAwIFBNXG5EZWNlbWJlciAw
OSwgMjAxNiAxMTowMCBBTSAtIDA3OjAwIFBNXG5EZWNlbWJlciAxMCwgMjAx
NiAxMTowMCBBTSAtIDA3OjAwIFBNIiwKICAgICAgICAiZGlzY291bnRhYmxl
IjogdHJ1ZSwKICAgICAgICAibGl2ZW1vZGUiOiBmYWxzZSwKICAgICAgICAi
bWV0YWRhdGEiOiB7fSwKICAgICAgICAicGVyaW9kIjogewogICAgICAgICAg
InN0YXJ0IjogMTQ4MDMyOTAyMiwKICAgICAgICAgICJlbmQiOiAxNDgwMzI5
MDIyCiAgICAgICAgfSwKICAgICAgICAicGxhbiI6IG51bGwsCiAgICAgICAg
InByb3JhdGlvbiI6IGZhbHNlLAogICAgICAgICJxdWFudGl0eSI6IG51bGws
CiAgICAgICAgInN1YnNjcmlwdGlvbiI6IG51bGwsCiAgICAgICAgInR5cGUi
OiAiaW52b2ljZWl0ZW0iCiAgICAgIH0KICAgIF0sCiAgICAiaGFzX21vcmUi
OiBmYWxzZSwKICAgICJ0b3RhbF9jb3VudCI6IDMsCiAgICAidXJsIjogIi92
MS9pbnZvaWNlcy9pbl8xOUttUDEyc09tZjQ3Tno5Umw2YUtXRjgvbGluZXMi
CiAgfSwKICAibGl2ZW1vZGUiOiBmYWxzZSwKICAibWV0YWRhdGEiOiB7fSwK
ICAibmV4dF9wYXltZW50X2F0dGVtcHQiOiBudWxsLAogICJwYWlkIjogdHJ1
ZSwKICAicGVyaW9kX2VuZCI6IDE0ODAzMjkwMjcsCiAgInBlcmlvZF9zdGFy
dCI6IDE0ODAzMjkwMjcsCiAgInJlY2VpcHRfbnVtYmVyIjogbnVsbCwKICAi
c3RhcnRpbmdfYmFsYW5jZSI6IDAsCiAgInN0YXRlbWVudF9kZXNjcmlwdG9y
IjogbnVsbCwKICAic3Vic2NyaXB0aW9uIjogbnVsbCwKICAic3VidG90YWwi
OiA0MjM1MCwKICAidGF4IjogbnVsbCwKICAidGF4X3BlcmNlbnQiOiBudWxs
LAogICJ0b3RhbCI6IDQyMzUwLAogICJ3ZWJob29rc19kZWxpdmVyZWRfYXQi
OiAxNDgwMzI5MDI3Cn0K
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:28 GMT
- request:
method: delete
uri: https://api.stripe.com/v1/customers/cus_8CzNtM08NVlSGN/sources/card_19KmOv2sOmf47Nz94YYaGGHa
body:
encoding: US-ASCII
string: ''
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:30:29 GMT
Content-Type:
- application/json
Content-Length:
- '63'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4YMgucjzDaXM
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: UTF-8
string: |
{
"deleted": true,
"id": "card_19KmOv2sOmf47Nz94YYaGGHa"
}
http_version:
recorded_at: Mon, 28 Nov 2016 10:30:29 GMT
recorded_with: VCR 3.0.1

View File

@ -0,0 +1,117 @@
---
http_interactions:
- request:
method: get
uri: https://api.stripe.com/v1/invoices/in_19KmP12sOmf47Nz9Rl6aKWF8
body:
encoding: US-ASCII
string: ''
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
User-Agent:
- Stripe/v1 RubyBindings/1.30.2
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-User-Agent:
- '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 4.4.0-47-generic (buildd@lcy01-03) (gcc version 5.4.0 20160609 (Ubuntu
5.4.0-6ubuntu1~16.04.2) ) #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016","hostname":"sylvain-sleede-pc"}'
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Mon, 28 Nov 2016 10:42:47 GMT
Content-Type:
- application/json
Content-Length:
- '2535'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Request-Id:
- req_9e4kXKKT6XjyhI
Stripe-Version:
- '2015-10-16'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains
body:
encoding: ASCII-8BIT
string: !binary |-
ewogICJpZCI6ICJpbl8xOUttUDEyc09tZjQ3Tno5Umw2YUtXRjgiLAogICJv
YmplY3QiOiAiaW52b2ljZSIsCiAgImFtb3VudF9kdWUiOiA0MjM1MCwKICAi
YXBwbGljYXRpb25fZmVlIjogbnVsbCwKICAiYXR0ZW1wdF9jb3VudCI6IDEs
CiAgImF0dGVtcHRlZCI6IHRydWUsCiAgImNoYXJnZSI6ICJjaF8xOUttUDIy
c09tZjQ3Tno5M2lmYklVMmkiLAogICJjbG9zZWQiOiB0cnVlLAogICJjdXJy
ZW5jeSI6ICJ1c2QiLAogICJjdXN0b21lciI6ICJjdXNfOEN6TnRNMDhOVmxT
R04iLAogICJkYXRlIjogMTQ4MDMyOTAyNywKICAiZGVzY3JpcHRpb24iOiBu
dWxsLAogICJkaXNjb3VudCI6IG51bGwsCiAgImVuZGluZ19iYWxhbmNlIjog
MCwKICAiZm9yZ2l2ZW4iOiBmYWxzZSwKICAibGluZXMiOiB7CiAgICAib2Jq
ZWN0IjogImxpc3QiLAogICAgImRhdGEiOiBbCiAgICAgIHsKICAgICAgICAi
aWQiOiAiaWlfMTlLbU94MnNPbWY0N056OVhJenZUakxDIiwKICAgICAgICAi
b2JqZWN0IjogImxpbmVfaXRlbSIsCiAgICAgICAgImFtb3VudCI6IC0xMDAw
LAogICAgICAgICJjdXJyZW5jeSI6ICJ1c2QiLAogICAgICAgICJkZXNjcmlw
dGlvbiI6ICJ3YWxsZXQgLTEwLjAiLAogICAgICAgICJkaXNjb3VudGFibGUi
OiBmYWxzZSwKICAgICAgICAibGl2ZW1vZGUiOiBmYWxzZSwKICAgICAgICAi
bWV0YWRhdGEiOiB7fSwKICAgICAgICAicGVyaW9kIjogewogICAgICAgICAg
InN0YXJ0IjogMTQ4MDMyOTAyMywKICAgICAgICAgICJlbmQiOiAxNDgwMzI5
MDIzCiAgICAgICAgfSwKICAgICAgICAicGxhbiI6IG51bGwsCiAgICAgICAg
InByb3JhdGlvbiI6IGZhbHNlLAogICAgICAgICJxdWFudGl0eSI6IG51bGws
CiAgICAgICAgInN1YnNjcmlwdGlvbiI6IG51bGwsCiAgICAgICAgInR5cGUi
OiAiaW52b2ljZWl0ZW0iCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiaWQi
OiAiaWlfMTlLbU93MnNPbWY0N056OWZNZ2pISWhnIiwKICAgICAgICAib2Jq
ZWN0IjogImxpbmVfaXRlbSIsCiAgICAgICAgImFtb3VudCI6IC03NjUwLAog
ICAgICAgICJjdXJyZW5jeSI6ICJ1c2QiLAogICAgICAgICJkZXNjcmlwdGlv
biI6ICJjb3Vwb24gU1VOTllGQUJMQUIgLSByZXNlcnZhdGlvbiIsCiAgICAg
ICAgImRpc2NvdW50YWJsZSI6IGZhbHNlLAogICAgICAgICJsaXZlbW9kZSI6
IGZhbHNlLAogICAgICAgICJtZXRhZGF0YSI6IHt9LAogICAgICAgICJwZXJp
b2QiOiB7CiAgICAgICAgICAic3RhcnQiOiAxNDgwMzI5MDIyLAogICAgICAg
ICAgImVuZCI6IDE0ODAzMjkwMjIKICAgICAgICB9LAogICAgICAgICJwbGFu
IjogbnVsbCwKICAgICAgICAicHJvcmF0aW9uIjogZmFsc2UsCiAgICAgICAg
InF1YW50aXR5IjogbnVsbCwKICAgICAgICAic3Vic2NyaXB0aW9uIjogbnVs
bCwKICAgICAgICAidHlwZSI6ICJpbnZvaWNlaXRlbSIKICAgICAgfSwKICAg
ICAgewogICAgICAgICJpZCI6ICJpaV8xOUttT3cyc09tZjQ3Tno5ZjBQYkti
SHEiLAogICAgICAgICJvYmplY3QiOiAibGluZV9pdGVtIiwKICAgICAgICAi
YW1vdW50IjogNTEwMDAsCiAgICAgICAgImN1cnJlbmN5IjogInVzZCIsCiAg
ICAgICAgImRlc2NyaXB0aW9uIjogIlJhZGlvIG51bcOpcmlxdWUgXG5EZWNl
bWJlciAwOCwgMjAxNiAxMTowMCBBTSAtIDA3OjAwIFBNXG5EZWNlbWJlciAw
OSwgMjAxNiAxMTowMCBBTSAtIDA3OjAwIFBNXG5EZWNlbWJlciAxMCwgMjAx
NiAxMTowMCBBTSAtIDA3OjAwIFBNIiwKICAgICAgICAiZGlzY291bnRhYmxl
IjogdHJ1ZSwKICAgICAgICAibGl2ZW1vZGUiOiBmYWxzZSwKICAgICAgICAi
bWV0YWRhdGEiOiB7fSwKICAgICAgICAicGVyaW9kIjogewogICAgICAgICAg
InN0YXJ0IjogMTQ4MDMyOTAyMiwKICAgICAgICAgICJlbmQiOiAxNDgwMzI5
MDIyCiAgICAgICAgfSwKICAgICAgICAicGxhbiI6IG51bGwsCiAgICAgICAg
InByb3JhdGlvbiI6IGZhbHNlLAogICAgICAgICJxdWFudGl0eSI6IG51bGws
CiAgICAgICAgInN1YnNjcmlwdGlvbiI6IG51bGwsCiAgICAgICAgInR5cGUi
OiAiaW52b2ljZWl0ZW0iCiAgICAgIH0KICAgIF0sCiAgICAiaGFzX21vcmUi
OiBmYWxzZSwKICAgICJ0b3RhbF9jb3VudCI6IDMsCiAgICAidXJsIjogIi92
MS9pbnZvaWNlcy9pbl8xOUttUDEyc09tZjQ3Tno5Umw2YUtXRjgvbGluZXMi
CiAgfSwKICAibGl2ZW1vZGUiOiBmYWxzZSwKICAibWV0YWRhdGEiOiB7fSwK
ICAibmV4dF9wYXltZW50X2F0dGVtcHQiOiBudWxsLAogICJwYWlkIjogdHJ1
ZSwKICAicGVyaW9kX2VuZCI6IDE0ODAzMjkwMjcsCiAgInBlcmlvZF9zdGFy
dCI6IDE0ODAzMjkwMjcsCiAgInJlY2VpcHRfbnVtYmVyIjogbnVsbCwKICAi
c3RhcnRpbmdfYmFsYW5jZSI6IDAsCiAgInN0YXRlbWVudF9kZXNjcmlwdG9y
IjogbnVsbCwKICAic3Vic2NyaXB0aW9uIjogbnVsbCwKICAic3VidG90YWwi
OiA0MjM1MCwKICAidGF4IjogbnVsbCwKICAidGF4X3BlcmNlbnQiOiBudWxs
LAogICJ0b3RhbCI6IDQyMzUwLAogICJ3ZWJob29rc19kZWxpdmVyZWRfYXQi
OiAxNDgwMzI5MDI3Cn0K
http_version:
recorded_at: Mon, 28 Nov 2016 10:42:47 GMT
recorded_with: VCR 3.0.1