mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-21 15:54:22 +01:00
Merge branch 'dev' for release 5.5.5
This commit is contained in:
commit
71409395e2
@ -1,5 +1,14 @@
|
|||||||
# Changelog Fab-manager
|
# Changelog Fab-manager
|
||||||
|
|
||||||
|
## v5.5.5 2022 November 22
|
||||||
|
|
||||||
|
- Soft destroy of spaces and machines
|
||||||
|
- Fix a bug: in upgrade script, the error "the input device is not a TTY" is thrown when migrating the database
|
||||||
|
- Fix a bug: broken display of machines pages
|
||||||
|
- Fix a bug: some automated tests were randomly failing because ElasticSearch was not synced
|
||||||
|
- Fix a bug: payment related objects are not synced on Stripe when enabling the online payment module
|
||||||
|
- Fix a bug: unable set a main image of product and remove an image of product
|
||||||
|
|
||||||
## v5.5.4 2022 November 17
|
## v5.5.4 2022 November 17
|
||||||
|
|
||||||
- Fix a bug: unable to download an existing export of the statistics
|
- Fix a bug: unable to download an existing export of the statistics
|
||||||
|
@ -12,6 +12,8 @@ class API::MachinesController < API::ApiController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@machine = Machine.includes(:machine_files, :projects).friendly.find(params[:id])
|
@machine = Machine.includes(:machine_files, :projects).friendly.find(params[:id])
|
||||||
|
|
||||||
|
head :not_found if @machine.deleted_at
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@ -35,7 +37,11 @@ class API::MachinesController < API::ApiController
|
|||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
authorize @machine
|
authorize @machine
|
||||||
@machine.destroy
|
if @machine.destroyable?
|
||||||
|
@machine.destroy
|
||||||
|
else
|
||||||
|
@machine.soft_destroy!
|
||||||
|
end
|
||||||
head :no_content
|
head :no_content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -6,11 +6,13 @@ class API::SpacesController < API::ApiController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@spaces = Space.includes(:space_image)
|
@spaces = Space.includes(:space_image).where(deleted_at: nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@space = Space.includes(:space_files, :projects).friendly.find(params[:id])
|
@space = Space.includes(:space_files, :projects).friendly.find(params[:id])
|
||||||
|
|
||||||
|
head :not_found if @space.deleted_at
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@ -36,7 +38,11 @@ class API::SpacesController < API::ApiController
|
|||||||
def destroy
|
def destroy
|
||||||
@space = get_space
|
@space = get_space
|
||||||
authorize @space
|
authorize @space
|
||||||
@space.destroy
|
if @space.destroyable?
|
||||||
|
@space.destroy
|
||||||
|
else
|
||||||
|
@space.soft_destroy!
|
||||||
|
end
|
||||||
head :no_content
|
head :no_content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -60,11 +60,12 @@ export const FormImageUpload = <TFieldValues extends FieldValues, TContext exten
|
|||||||
attachment_name: f.name
|
attachment_name: f.name
|
||||||
});
|
});
|
||||||
setValue(
|
setValue(
|
||||||
id as Path<TFieldValues>,
|
`${id}.attachment_name` as Path<TFieldValues>,
|
||||||
{
|
f.name as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>
|
||||||
attachment_name: f.name,
|
);
|
||||||
_destroy: false
|
setValue(
|
||||||
} as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>
|
`${id}._destroy` as Path<TFieldValues>,
|
||||||
|
false as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>
|
||||||
);
|
);
|
||||||
if (typeof onFileChange === 'function') {
|
if (typeof onFileChange === 'function') {
|
||||||
onFileChange({ attachment_name: f.name });
|
onFileChange({ attachment_name: f.name });
|
||||||
|
@ -6,9 +6,10 @@ import { FieldValues } from 'react-hook-form/dist/types/fields';
|
|||||||
import { FormComponent, FormControlledComponent } from '../../models/form-component';
|
import { FormComponent, FormControlledComponent } from '../../models/form-component';
|
||||||
import { AbstractFormItemProps } from './abstract-form-item';
|
import { AbstractFormItemProps } from './abstract-form-item';
|
||||||
import { UseFormSetValue } from 'react-hook-form/dist/types/form';
|
import { UseFormSetValue } from 'react-hook-form/dist/types/form';
|
||||||
import { ArrayPath, FieldArray, useFieldArray } from 'react-hook-form';
|
import { ArrayPath, FieldArray, Path, useFieldArray, useWatch } from 'react-hook-form';
|
||||||
import { FileType } from '../../models/file';
|
import { FileType } from '../../models/file';
|
||||||
import { UnpackNestedValue } from 'react-hook-form/dist/types';
|
import { UnpackNestedValue } from 'react-hook-form/dist/types';
|
||||||
|
import { FieldPathValue } from 'react-hook-form/dist/types/path';
|
||||||
|
|
||||||
interface FormMultiFileUploadProps<TFieldValues, TContext extends object> extends FormComponent<TFieldValues>, FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
interface FormMultiFileUploadProps<TFieldValues, TContext extends object> extends FormComponent<TFieldValues>, FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||||
setValue: UseFormSetValue<TFieldValues>,
|
setValue: UseFormSetValue<TFieldValues>,
|
||||||
@ -20,13 +21,26 @@ interface FormMultiFileUploadProps<TFieldValues, TContext extends object> extend
|
|||||||
* This component allows to upload multiple files, in forms managed by react-hook-form.
|
* This component allows to upload multiple files, in forms managed by react-hook-form.
|
||||||
*/
|
*/
|
||||||
export const FormMultiFileUpload = <TFieldValues extends FieldValues, TContext extends object>({ id, className, register, control, setValue, formState, addButtonLabel, accept }: FormMultiFileUploadProps<TFieldValues, TContext>) => {
|
export const FormMultiFileUpload = <TFieldValues extends FieldValues, TContext extends object>({ id, className, register, control, setValue, formState, addButtonLabel, accept }: FormMultiFileUploadProps<TFieldValues, TContext>) => {
|
||||||
const { fields, append, remove } = useFieldArray({ control, name: id as ArrayPath<TFieldValues> });
|
const { append } = useFieldArray({ control, name: id as ArrayPath<TFieldValues> });
|
||||||
|
const output = useWatch({ control, name: id as Path<TFieldValues> });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an file
|
||||||
|
*/
|
||||||
|
const handleRemoveFile = (file: FileType, index: number) => {
|
||||||
|
return () => {
|
||||||
|
setValue(
|
||||||
|
`${id}.${index}._destroy` as Path<TFieldValues>,
|
||||||
|
true as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`form-multi-file-upload ${className || ''}`}>
|
<div className={`form-multi-file-upload ${className || ''}`}>
|
||||||
<div className="list">
|
<div className="list">
|
||||||
{fields.map((field: FileType, index) => (
|
{output.map((field: FileType, index) => (
|
||||||
<FormFileUpload key={field.id}
|
<FormFileUpload key={index}
|
||||||
defaultFile={field}
|
defaultFile={field}
|
||||||
id={`${id}.${index}`}
|
id={`${id}.${index}`}
|
||||||
accept={accept}
|
accept={accept}
|
||||||
@ -34,7 +48,7 @@ export const FormMultiFileUpload = <TFieldValues extends FieldValues, TContext e
|
|||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
formState={formState}
|
formState={formState}
|
||||||
className={field._destroy ? 'hidden' : ''}
|
className={field._destroy ? 'hidden' : ''}
|
||||||
onFileRemove={() => remove(index)}/>
|
onFileRemove={() => handleRemoveFile(field, index)}/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<FabButton
|
<FabButton
|
||||||
|
@ -20,7 +20,7 @@ interface FormMultiImageUploadProps<TFieldValues, TContext extends object> exten
|
|||||||
* This component allows to upload multiple images, in forms managed by react-hook-form.
|
* This component allows to upload multiple images, in forms managed by react-hook-form.
|
||||||
*/
|
*/
|
||||||
export const FormMultiImageUpload = <TFieldValues extends FieldValues, TContext extends object>({ id, className, register, control, setValue, formState, addButtonLabel }: FormMultiImageUploadProps<TFieldValues, TContext>) => {
|
export const FormMultiImageUpload = <TFieldValues extends FieldValues, TContext extends object>({ id, className, register, control, setValue, formState, addButtonLabel }: FormMultiImageUploadProps<TFieldValues, TContext>) => {
|
||||||
const { fields, append, remove } = useFieldArray({ control, name: id as ArrayPath<TFieldValues> });
|
const { append } = useFieldArray({ control, name: id as ArrayPath<TFieldValues> });
|
||||||
const output = useWatch({ control, name: id as Path<TFieldValues> });
|
const output = useWatch({ control, name: id as Path<TFieldValues> });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,14 +44,10 @@ export const FormMultiImageUpload = <TFieldValues extends FieldValues, TContext
|
|||||||
true as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>
|
true as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (typeof image.id === 'string') {
|
setValue(
|
||||||
remove(index);
|
`${id}.${index}._destroy` as Path<TFieldValues>,
|
||||||
} else {
|
true as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>
|
||||||
setValue(
|
);
|
||||||
`${id}.${index}._destroy` as Path<TFieldValues>,
|
|
||||||
true as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -74,8 +70,8 @@ export const FormMultiImageUpload = <TFieldValues extends FieldValues, TContext
|
|||||||
return (
|
return (
|
||||||
<div className={`form-multi-image-upload ${className || ''}`}>
|
<div className={`form-multi-image-upload ${className || ''}`}>
|
||||||
<div className="list">
|
<div className="list">
|
||||||
{fields.map((field: ImageType, index) => (
|
{output.map((field: ImageType, index) => (
|
||||||
<FormImageUpload key={field.id}
|
<FormImageUpload key={index}
|
||||||
defaultImage={field}
|
defaultImage={field}
|
||||||
id={`${id}.${index}`}
|
id={`${id}.${index}`}
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
|
@ -44,12 +44,12 @@ const MachineCard: React.FC<MachineCardProps> = ({ user, machine, onShowMachine,
|
|||||||
* Return the machine's picture or a placeholder
|
* Return the machine's picture or a placeholder
|
||||||
*/
|
*/
|
||||||
const machinePicture = (): ReactNode => {
|
const machinePicture = (): ReactNode => {
|
||||||
if (!machine.machine_image_attributes) {
|
if (!machine.machine_image) {
|
||||||
return <div className="machine-picture no-picture" />;
|
return <div className="machine-picture no-picture" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="machine-picture" style={{ backgroundImage: `url(${machine.machine_image_attributes.attachment_url}), url('/default-image.png')` }} onClick={handleShowMachine} />
|
<div className="machine-picture" style={{ backgroundImage: `url(${machine.machine_image}), url('/default-image.png')` }} onClick={handleShowMachine} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Reservation } from './reservation';
|
import { Reservation } from './reservation';
|
||||||
import { ApiFilter } from './api';
|
import { ApiFilter } from './api';
|
||||||
import { FileType } from './file';
|
|
||||||
|
|
||||||
export interface MachineIndexFilter extends ApiFilter {
|
export interface MachineIndexFilter extends ApiFilter {
|
||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
@ -13,8 +12,12 @@ export interface Machine {
|
|||||||
spec?: string,
|
spec?: string,
|
||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
slug: string,
|
slug: string,
|
||||||
machine_image_attributes: FileType,
|
machine_image: string,
|
||||||
machine_files_attributes?: Array<FileType>,
|
machine_files_attributes?: Array<{
|
||||||
|
id: number,
|
||||||
|
attachment: string,
|
||||||
|
attachment_url: string
|
||||||
|
}>,
|
||||||
trainings?: Array<{
|
trainings?: Array<{
|
||||||
id: number,
|
id: number,
|
||||||
name: string,
|
name: string,
|
||||||
|
@ -82,6 +82,10 @@ class Machine < ApplicationRecord
|
|||||||
reservations.empty?
|
reservations.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def soft_destroy!
|
||||||
|
update(deleted_at: DateTime.current)
|
||||||
|
end
|
||||||
|
|
||||||
def packs?(user)
|
def packs?(user)
|
||||||
prepaid_packs.where(group_id: user.group_id)
|
prepaid_packs.where(group_id: user.group_id)
|
||||||
.where(disabled: [false, nil])
|
.where(disabled: [false, nil])
|
||||||
|
@ -15,7 +15,7 @@ class Product < ApplicationRecord
|
|||||||
accepts_nested_attributes_for :product_files, allow_destroy: true, reject_if: :all_blank
|
accepts_nested_attributes_for :product_files, allow_destroy: true, reject_if: :all_blank
|
||||||
|
|
||||||
has_many :product_images, as: :viewable, dependent: :destroy
|
has_many :product_images, as: :viewable, dependent: :destroy
|
||||||
accepts_nested_attributes_for :product_images, allow_destroy: true, reject_if: ->(i) { i[:attachment].blank? }
|
accepts_nested_attributes_for :product_images, allow_destroy: true, reject_if: ->(i) { i[:attachment].blank? && i[:id].blank? }
|
||||||
|
|
||||||
has_many :product_stock_movements, dependent: :destroy
|
has_many :product_stock_movements, dependent: :destroy
|
||||||
accepts_nested_attributes_for :product_stock_movements, allow_destroy: true, reject_if: :all_blank
|
accepts_nested_attributes_for :product_stock_movements, allow_destroy: true, reject_if: :all_blank
|
||||||
|
@ -66,6 +66,10 @@ class Space < ApplicationRecord
|
|||||||
reservations.empty?
|
reservations.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def soft_destroy!
|
||||||
|
update(deleted_at: DateTime.current)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def update_gateway_product
|
def update_gateway_product
|
||||||
|
@ -11,6 +11,6 @@ class MachinePolicy < ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy?
|
def destroy?
|
||||||
user.admin? and record.destroyable?
|
user.admin?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Check the access policies for API::SpacesController
|
||||||
class SpacePolicy < ApplicationPolicy
|
class SpacePolicy < ApplicationPolicy
|
||||||
def create?
|
def create?
|
||||||
user.admin?
|
user.admin?
|
||||||
@ -8,6 +11,6 @@ class SpacePolicy < ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy?
|
def destroy?
|
||||||
user.admin? and record.destroyable?
|
user.admin?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# module definition
|
||||||
|
module Cart; end
|
||||||
|
|
||||||
# Provides methods for set offer to item in cart
|
# Provides methods for set offer to item in cart
|
||||||
class Cart::SetOfferService
|
class Cart::SetOfferService
|
||||||
def call(order, orderable, is_offered)
|
def call(order, orderable, is_offered)
|
||||||
|
@ -9,6 +9,9 @@ class MachineService
|
|||||||
else
|
else
|
||||||
Machine.includes(:machine_image, :plans).order(sort_by)
|
Machine.includes(:machine_image, :plans).order(sort_by)
|
||||||
end
|
end
|
||||||
|
# do not include soft destroyed
|
||||||
|
machines = machines.where(deleted_at: nil)
|
||||||
|
|
||||||
if filters[:disabled].present?
|
if filters[:disabled].present?
|
||||||
state = filters[:disabled] == 'false' ? [nil, false] : true
|
state = filters[:disabled] == 'false' ? [nil, false] : true
|
||||||
machines = machines.where(disabled: state)
|
machines = machines.where(disabled: state)
|
||||||
|
@ -46,7 +46,7 @@ class SettingService
|
|||||||
|
|
||||||
# sync all objects on stripe
|
# sync all objects on stripe
|
||||||
def sync_stripe_objects(setting)
|
def sync_stripe_objects(setting)
|
||||||
return unless setting.name == 'stripe_secret_key'
|
return unless %w[stripe_secret_key online_payment_module].include?(setting.name)
|
||||||
|
|
||||||
SyncObjectsOnStripeWorker.perform_async(setting.history_values.last&.invoicing_profile&.user&.id)
|
SyncObjectsOnStripeWorker.perform_async(setting.history_values.last&.invoicing_profile&.user&.id)
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.array!(@machines) do |machine|
|
json.array!(@machines) do |machine|
|
||||||
json.partial! 'api/machines/machine', machine: machine
|
json.extract! machine, :id, :name, :slug, :disabled
|
||||||
|
|
||||||
|
json.machine_image machine.machine_image.attachment.medium.url if machine.machine_image
|
||||||
end
|
end
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.partial! 'api/machines/machine', machine: @machine
|
json.extract! @machine, :id, :name, :description, :spec, :disabled, :slug
|
||||||
json.extract! @machine, :description, :spec
|
json.machine_image @machine.machine_image.attachment.large.url if @machine.machine_image
|
||||||
|
|
||||||
json.machine_files_attributes @machine.machine_files do |f|
|
json.machine_files_attributes @machine.machine_files do |f|
|
||||||
json.id f.id
|
json.id f.id
|
||||||
json.attachment_name f.attachment_identifier
|
json.attachment f.attachment_identifier
|
||||||
json.attachment_url f.attachment_url
|
json.attachment_url f.attachment_url
|
||||||
end
|
end
|
||||||
json.trainings @machine.trainings.each, :id, :name, :disabled
|
json.trainings @machine.trainings.each, :id, :name, :disabled
|
||||||
|
@ -4,12 +4,12 @@ json.extract! product, :id, :name, :slug, :sku, :is_active, :product_category_id
|
|||||||
:low_stock_threshold, :machine_ids, :created_at
|
:low_stock_threshold, :machine_ids, :created_at
|
||||||
json.description sanitize(product.description)
|
json.description sanitize(product.description)
|
||||||
json.amount product.amount / 100.0 if product.amount.present?
|
json.amount product.amount / 100.0 if product.amount.present?
|
||||||
json.product_files_attributes product.product_files do |f|
|
json.product_files_attributes product.product_files.order(created_at: :asc) do |f|
|
||||||
json.id f.id
|
json.id f.id
|
||||||
json.attachment_name f.attachment_identifier
|
json.attachment_name f.attachment_identifier
|
||||||
json.attachment_url f.attachment_url
|
json.attachment_url f.attachment_url
|
||||||
end
|
end
|
||||||
json.product_images_attributes product.product_images do |f|
|
json.product_images_attributes product.product_images.order(created_at: :asc) do |f|
|
||||||
json.id f.id
|
json.id f.id
|
||||||
json.attachment_name f.attachment_identifier
|
json.attachment_name f.attachment_identifier
|
||||||
json.attachment_url f.attachment_url
|
json.attachment_url f.attachment_url
|
||||||
|
@ -7,22 +7,45 @@ class SyncObjectsOnStripeWorker
|
|||||||
sidekiq_options lock: :until_executed, on_conflict: :reject, queue: :stripe
|
sidekiq_options lock: :until_executed, on_conflict: :reject, queue: :stripe
|
||||||
|
|
||||||
def perform(notify_user_id = nil)
|
def perform(notify_user_id = nil)
|
||||||
|
unless Stripe::Helper.enabled?
|
||||||
|
Rails.logger.warn('A request to sync payment related objects on Stripe will not run because Stripe is not enabled')
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
w = StripeWorker.new
|
||||||
|
sync_customers(w)
|
||||||
|
sync_coupons
|
||||||
|
sync_objects(w)
|
||||||
|
|
||||||
|
Rails.logger.info 'Sync is done'
|
||||||
|
return unless notify_user_id
|
||||||
|
|
||||||
|
Rails.logger.info "Notify user #{notify_user_id}"
|
||||||
|
user = User.find(notify_user_id)
|
||||||
|
NotificationCenter.call type: :notify_admin_objects_stripe_sync,
|
||||||
|
receiver: user,
|
||||||
|
attached_object: user
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_customers(worker)
|
||||||
Rails.logger.info 'We create all non-existing customers on stripe. This may take a while...'
|
Rails.logger.info 'We create all non-existing customers on stripe. This may take a while...'
|
||||||
total = User.online_payers.count
|
total = User.online_payers.count
|
||||||
User.online_payers.each_with_index do |member, index|
|
User.online_payers.each_with_index do |member, index|
|
||||||
Rails.logger.info "#{index} / #{total}"
|
Rails.logger.info "#{index} / #{total}"
|
||||||
begin
|
begin
|
||||||
stp_customer = member.payment_gateway_object&.gateway_object&.retrieve
|
stp_customer = member.payment_gateway_object&.gateway_object&.retrieve
|
||||||
StripeWorker.new.create_stripe_customer(member.id) if stp_customer.nil? || stp_customer[:deleted]
|
worker.perform(:create_stripe_customer, member.id) if stp_customer.nil? || stp_customer[:deleted]
|
||||||
rescue Stripe::InvalidRequestError
|
rescue Stripe::InvalidRequestError
|
||||||
begin
|
begin
|
||||||
StripeWorker.new.create_stripe_customer(member.id)
|
worker.perform(:create_stripe_customer, member.id)
|
||||||
rescue Stripe::InvalidRequestError => e
|
rescue Stripe::InvalidRequestError => e
|
||||||
Rails.logger.error "Unable to create the customer #{member.id} do to a Stripe error: #{e}"
|
Rails.logger.error "Unable to create the customer #{member.id} do to a Stripe error: #{e}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_coupons
|
||||||
Rails.logger.info 'We create all non-existing coupons on stripe. This may take a while...'
|
Rails.logger.info 'We create all non-existing coupons on stripe. This may take a while...'
|
||||||
total = Coupon.all.count
|
total = Coupon.all.count
|
||||||
Coupon.all.each_with_index do |coupon, index|
|
Coupon.all.each_with_index do |coupon, index|
|
||||||
@ -35,23 +58,16 @@ class SyncObjectsOnStripeWorker
|
|||||||
Rails.logger.error "Unable to create coupon #{coupon.code} on stripe: #{e}"
|
Rails.logger.error "Unable to create coupon #{coupon.code} on stripe: #{e}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
w = StripeWorker.new
|
def sync_objects(worker)
|
||||||
[Machine, Training, Space, Plan].each do |klass|
|
[Machine, Training, Space, Plan].each do |klass|
|
||||||
Rails.logger.info "We create all non-existing #{klass} on stripe. This may take a while..."
|
Rails.logger.info "We create all non-existing #{klass} on stripe. This may take a while..."
|
||||||
total = klass.all.count
|
total = klass.all.count
|
||||||
klass.all.each_with_index do |item, index|
|
klass.all.each_with_index do |item, index|
|
||||||
Rails.logger.info "#{index} / #{total}"
|
Rails.logger.info "#{index} / #{total}"
|
||||||
w.perform(:create_or_update_stp_product, klass.name, item.id)
|
worker.perform(:create_or_update_stp_product, klass.name, item.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
Rails.logger.info 'Sync is done'
|
|
||||||
return unless notify_user_id
|
|
||||||
|
|
||||||
Rails.logger.info "Notify user #{notify_user_id}"
|
|
||||||
user = User.find(notify_user_id)
|
|
||||||
NotificationCenter.call type: :notify_admin_objects_stripe_sync,
|
|
||||||
receiver: user,
|
|
||||||
attached_object: user
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
12
db/migrate/20221122123557_add_deleted_at_to_machine.rb
Normal file
12
db/migrate/20221122123557_add_deleted_at_to_machine.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Allow soft destroy of machines.
|
||||||
|
# Machines with existing reservation cannot be destroyed because we need them for rebuilding invoices, statistics, etc.
|
||||||
|
# This attribute allows to make a "soft destroy" of a Machine, marking it as destroyed so it doesn't appear anymore in
|
||||||
|
# the interface (as if it was destroyed) but still lives in the database so we can use it to build data.
|
||||||
|
class AddDeletedAtToMachine < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :machines, :deleted_at, :datetime
|
||||||
|
add_index :machines, :deleted_at
|
||||||
|
end
|
||||||
|
end
|
12
db/migrate/20221122123605_add_deleted_at_to_space.rb
Normal file
12
db/migrate/20221122123605_add_deleted_at_to_space.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Allow soft destroy of spaces.
|
||||||
|
# Spaces with existing reservation cannot be destroyed because we need them for rebuilding invoices, statistics, etc.
|
||||||
|
# This attribute allows to make a "soft destroy" of a Space, marking it as destroyed so it doesn't appear anymore in
|
||||||
|
# the interface (as if it was destroyed) but still lives in the database so we can use it to build data.
|
||||||
|
class AddDeletedAtToSpace < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :spaces, :deleted_at, :datetime
|
||||||
|
add_index :spaces, :deleted_at
|
||||||
|
end
|
||||||
|
end
|
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
ActiveRecord::Schema.define(version: 2022_11_22_123605) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "fuzzystrmatch"
|
enable_extension "fuzzystrmatch"
|
||||||
@ -358,6 +358,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
|||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.string "slug"
|
t.string "slug"
|
||||||
t.boolean "disabled"
|
t.boolean "disabled"
|
||||||
|
t.datetime "deleted_at"
|
||||||
|
t.index ["deleted_at"], name: "index_machines_on_deleted_at"
|
||||||
t.index ["slug"], name: "index_machines_on_slug", unique: true
|
t.index ["slug"], name: "index_machines_on_slug", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -878,6 +880,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
|||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.text "characteristics"
|
t.text "characteristics"
|
||||||
t.boolean "disabled"
|
t.boolean "disabled"
|
||||||
|
t.datetime "deleted_at"
|
||||||
|
t.index ["deleted_at"], name: "index_spaces_on_deleted_at"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "spaces_availabilities", id: :serial, force: :cascade do |t|
|
create_table "spaces_availabilities", id: :serial, force: :cascade do |t|
|
||||||
|
@ -21,3 +21,12 @@ to the corresponding OpenID Connect claims:
|
|||||||
- profile.address
|
- profile.address
|
||||||
|
|
||||||
To use the automatic mapping, add one of the fields above and click on the magic wand button near to the "Userinfo claim" input.
|
To use the automatic mapping, add one of the fields above and click on the magic wand button near to the "Userinfo claim" input.
|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
|
||||||
|
```
|
||||||
|
Not found. Authentication passthru.
|
||||||
|
```
|
||||||
|
This issue may occur if you have misconfigured the environment variable `DEFAULT_HOST` and/or `DEFAULT_PROTOCOL`.
|
||||||
|
Especially, if you have an automatic redirection (e.g. from example.org to example.com), `DEFAULT_HOST` *MUST* be configured with the redirection target (here example.com).
|
||||||
|
Once you have reconfigured these variables, please switch back the active authentication provider to FabManager, restart the application, then delete the OIDC provider you configured and re-create a new one for the new settings to be used.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fab-manager",
|
"name": "fab-manager",
|
||||||
"version": "5.5.4",
|
"version": "5.5.5",
|
||||||
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
|
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"fablab",
|
"fablab",
|
||||||
|
@ -296,7 +296,7 @@ upgrade()
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
compile_assets
|
compile_assets
|
||||||
if ! docker-compose run --rm "$SERVICE" bundle exec rake db:migrate; then
|
if ! docker-compose run --rm "$SERVICE" bundle exec rake db:migrate </dev/tty; then
|
||||||
restore_tag
|
restore_tag
|
||||||
printf "\e[91m[ ❌ ] Something went wrong while migrating the database, please check the logs above.\e[39m\nExiting...\n"
|
printf "\e[91m[ ❌ ] Something went wrong while migrating the database, please check the logs above.\e[39m\nExiting...\n"
|
||||||
exit 4
|
exit 4
|
||||||
|
@ -12,6 +12,7 @@ class Exports::StatisticsExportTest < ActionDispatch::IntegrationTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
test 'export machine reservations statistics to Excel' do
|
test 'export machine reservations statistics to Excel' do
|
||||||
|
Stats::Machine.refresh_index!
|
||||||
# Build the stats for the June 2015, a machine reservation should have happened at the time
|
# Build the stats for the June 2015, a machine reservation should have happened at the time
|
||||||
::Statistics::BuilderService.generate_statistic({ start_date: '2015-06-01'.to_date.beginning_of_day,
|
::Statistics::BuilderService.generate_statistic({ start_date: '2015-06-01'.to_date.beginning_of_day,
|
||||||
end_date: '2015-06-30'.to_date.end_of_day })
|
end_date: '2015-06-30'.to_date.end_of_day })
|
||||||
@ -35,6 +36,7 @@ class Exports::StatisticsExportTest < ActionDispatch::IntegrationTest
|
|||||||
assert_not_nil e, 'Export was not created in database'
|
assert_not_nil e, 'Export was not created in database'
|
||||||
|
|
||||||
# Run the worker
|
# Run the worker
|
||||||
|
Stats::Machine.refresh_index!
|
||||||
worker = StatisticsExportWorker.new
|
worker = StatisticsExportWorker.new
|
||||||
worker.perform(e.id)
|
worker.perform(e.id)
|
||||||
|
|
||||||
@ -69,6 +71,7 @@ class Exports::StatisticsExportTest < ActionDispatch::IntegrationTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
test 'export global statistics to Excel' do
|
test 'export global statistics to Excel' do
|
||||||
|
Stats::Machine.refresh_index!
|
||||||
# Build the stats for the June 2015
|
# Build the stats for the June 2015
|
||||||
::Statistics::BuilderService.generate_statistic({ start_date: '2015-06-01'.to_date.beginning_of_day,
|
::Statistics::BuilderService.generate_statistic({ start_date: '2015-06-01'.to_date.beginning_of_day,
|
||||||
end_date: '2015-06-30'.to_date.end_of_day })
|
end_date: '2015-06-30'.to_date.end_of_day })
|
||||||
|
209
test/integration/store/admin_order_for_himself_test.rb
Normal file
209
test/integration/store/admin_order_for_himself_test.rb
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
module Store; end
|
||||||
|
|
||||||
|
class Store::AdminOrderForHimselfTest < ActionDispatch::IntegrationTest
|
||||||
|
setup do
|
||||||
|
@admin = User.find_by(username: 'admin')
|
||||||
|
@pjproudhon = User.find_by(username: 'pjproudhon')
|
||||||
|
@caisse_en_bois = Product.find_by(slug: 'caisse-en-bois')
|
||||||
|
@panneaux = Product.find_by(slug: 'panneaux-de-mdf')
|
||||||
|
@cart1 = Order.find_by(token: '0DKxbAOzSXRx-amXyhmDdg1666691976019')
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'admin pay himself order by card with success' do
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
invoice_count = Invoice.count
|
||||||
|
invoice_items_count = InvoiceItem.count
|
||||||
|
|
||||||
|
VCR.use_cassette('store_order_admin_pay_by_card_success') do
|
||||||
|
post '/api/checkout/payment',
|
||||||
|
params: {
|
||||||
|
payment_id: stripe_payment_method,
|
||||||
|
order_token: @cart1.token,
|
||||||
|
customer_id: @admin.id
|
||||||
|
}.to_json, headers: default_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
@cart1.reload
|
||||||
|
|
||||||
|
# general assertions
|
||||||
|
assert_equal 200, response.status
|
||||||
|
assert_equal invoice_count + 1, Invoice.count
|
||||||
|
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||||
|
|
||||||
|
# invoice_items assertions
|
||||||
|
invoice_item = InvoiceItem.last
|
||||||
|
|
||||||
|
assert invoice_item.check_footprint
|
||||||
|
|
||||||
|
# invoice assertions
|
||||||
|
invoice = Invoice.last
|
||||||
|
assert_invoice_pdf invoice
|
||||||
|
assert_not_nil invoice.debug_footprint
|
||||||
|
|
||||||
|
assert_not @cart1.payment_gateway_object.blank?
|
||||||
|
assert_not invoice.payment_gateway_object.blank?
|
||||||
|
assert_not invoice.total.blank?
|
||||||
|
assert invoice.check_footprint
|
||||||
|
|
||||||
|
# notification
|
||||||
|
assert_not_empty Notification.where(attached_object: invoice)
|
||||||
|
|
||||||
|
assert_equal @cart1.state, 'paid'
|
||||||
|
assert_equal @cart1.payment_method, 'card'
|
||||||
|
assert_equal @cart1.paid_total, 262_500
|
||||||
|
|
||||||
|
stock_movement = @caisse_en_bois.product_stock_movements.last
|
||||||
|
assert_equal stock_movement.stock_type, 'external'
|
||||||
|
assert_equal stock_movement.reason, 'sold'
|
||||||
|
assert_equal stock_movement.quantity, -5
|
||||||
|
assert_equal stock_movement.order_item_id, @cart1.order_items.first.id
|
||||||
|
|
||||||
|
stock_movement = @panneaux.product_stock_movements.last
|
||||||
|
assert_equal stock_movement.stock_type, 'external'
|
||||||
|
assert_equal stock_movement.reason, 'sold'
|
||||||
|
assert_equal stock_movement.quantity, -2
|
||||||
|
assert_equal stock_movement.order_item_id, @cart1.order_items.last.id
|
||||||
|
|
||||||
|
activity = @cart1.order_activities.last
|
||||||
|
assert_equal activity.activity_type, 'paid'
|
||||||
|
assert_equal activity.operator_profile_id, @admin.invoicing_profile.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'admin pay himself order by card and wallet with success' do
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
service = WalletService.new(user: @admin, wallet: @admin.wallet)
|
||||||
|
service.credit(1000)
|
||||||
|
|
||||||
|
invoice_count = Invoice.count
|
||||||
|
invoice_items_count = InvoiceItem.count
|
||||||
|
users_credit_count = UsersCredit.count
|
||||||
|
wallet_transactions_count = WalletTransaction.count
|
||||||
|
|
||||||
|
VCR.use_cassette('store_order_admin_pay_by_cart_and_wallet_success') do
|
||||||
|
post '/api/checkout/payment',
|
||||||
|
params: {
|
||||||
|
payment_id: stripe_payment_method,
|
||||||
|
order_token: @cart1.token,
|
||||||
|
customer_id: @admin.id
|
||||||
|
}.to_json, headers: default_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
@admin.wallet.reload
|
||||||
|
@cart1.reload
|
||||||
|
|
||||||
|
# general assertions
|
||||||
|
assert_equal 200, response.status
|
||||||
|
assert_equal invoice_count + 1, Invoice.count
|
||||||
|
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||||
|
|
||||||
|
# invoice_items assertions
|
||||||
|
invoice_item = InvoiceItem.last
|
||||||
|
|
||||||
|
assert invoice_item.check_footprint
|
||||||
|
|
||||||
|
# invoice assertions
|
||||||
|
invoice = Invoice.last
|
||||||
|
assert_invoice_pdf invoice
|
||||||
|
assert_not_nil invoice.debug_footprint
|
||||||
|
|
||||||
|
assert_not @cart1.payment_gateway_object.blank?
|
||||||
|
assert_not invoice.payment_gateway_object.blank?
|
||||||
|
assert_not invoice.total.blank?
|
||||||
|
assert invoice.check_footprint
|
||||||
|
|
||||||
|
# notification
|
||||||
|
assert_not_empty Notification.where(attached_object: invoice)
|
||||||
|
|
||||||
|
assert_equal @cart1.state, 'paid'
|
||||||
|
assert_equal @cart1.payment_method, 'card'
|
||||||
|
assert_equal @cart1.paid_total, 162_500
|
||||||
|
assert_equal users_credit_count, UsersCredit.count
|
||||||
|
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
||||||
|
|
||||||
|
# wallet
|
||||||
|
assert_equal 0, @admin.wallet.amount
|
||||||
|
assert_equal 2, @admin.wallet.wallet_transactions.count
|
||||||
|
transaction = @admin.wallet.wallet_transactions.last
|
||||||
|
assert_equal 'debit', transaction.transaction_type
|
||||||
|
assert_equal @cart1.wallet_amount / 100.0, transaction.amount
|
||||||
|
assert_equal @cart1.wallet_transaction_id, transaction.id
|
||||||
|
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'admin pay himself order by wallet with success' do
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
service = WalletService.new(user: @admin, wallet: @admin.wallet)
|
||||||
|
service.credit(@cart1.total / 100)
|
||||||
|
|
||||||
|
invoice_count = Invoice.count
|
||||||
|
invoice_items_count = InvoiceItem.count
|
||||||
|
users_credit_count = UsersCredit.count
|
||||||
|
wallet_transactions_count = WalletTransaction.count
|
||||||
|
|
||||||
|
post '/api/checkout/payment',
|
||||||
|
params: {
|
||||||
|
order_token: @cart1.token,
|
||||||
|
customer_id: @admin.id
|
||||||
|
}.to_json, headers: default_headers
|
||||||
|
|
||||||
|
@admin.wallet.reload
|
||||||
|
@cart1.reload
|
||||||
|
|
||||||
|
# general assertions
|
||||||
|
assert_equal 200, response.status
|
||||||
|
assert_equal @cart1.state, 'paid'
|
||||||
|
assert_equal invoice_count + 1, Invoice.count
|
||||||
|
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||||
|
assert_equal users_credit_count, UsersCredit.count
|
||||||
|
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
||||||
|
|
||||||
|
# invoice_items assertions
|
||||||
|
invoice_item = InvoiceItem.last
|
||||||
|
|
||||||
|
assert invoice_item.check_footprint
|
||||||
|
|
||||||
|
# invoice assertions
|
||||||
|
invoice = Invoice.last
|
||||||
|
assert_invoice_pdf invoice
|
||||||
|
assert_not_nil invoice.debug_footprint
|
||||||
|
|
||||||
|
assert invoice.payment_gateway_object.blank?
|
||||||
|
assert_not invoice.total.blank?
|
||||||
|
assert invoice.check_footprint
|
||||||
|
|
||||||
|
# notification
|
||||||
|
assert_not_empty Notification.where(attached_object: invoice)
|
||||||
|
|
||||||
|
# wallet
|
||||||
|
assert_equal 0, @admin.wallet.amount
|
||||||
|
assert_equal 2, @admin.wallet.wallet_transactions.count
|
||||||
|
transaction = @admin.wallet.wallet_transactions.last
|
||||||
|
assert_equal 'debit', transaction.transaction_type
|
||||||
|
assert_equal @cart1.paid_total, 0
|
||||||
|
assert_equal @cart1.wallet_amount / 100.0, transaction.amount
|
||||||
|
assert_equal @cart1.payment_method, 'wallet'
|
||||||
|
assert_equal @cart1.wallet_transaction_id, transaction.id
|
||||||
|
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'admin cannot offer products to himself' do
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
put '/api/cart/set_offer',
|
||||||
|
params: {
|
||||||
|
order_token: @cart1.token,
|
||||||
|
customer_id: @admin.id,
|
||||||
|
is_offered: true,
|
||||||
|
orderable_id: @caisse_en_bois.id
|
||||||
|
}.to_json, headers: default_headers
|
||||||
|
|
||||||
|
assert_equal 403, response.status
|
||||||
|
end
|
||||||
|
end
|
@ -13,129 +13,6 @@ class Store::AdminPayOrderTest < ActionDispatch::IntegrationTest
|
|||||||
@cart1 = Order.find_by(token: '0DKxbAOzSXRx-amXyhmDdg1666691976019')
|
@cart1 = Order.find_by(token: '0DKxbAOzSXRx-amXyhmDdg1666691976019')
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'admin pay himself order by cart with success' do
|
|
||||||
login_as(@admin, scope: :user)
|
|
||||||
|
|
||||||
invoice_count = Invoice.count
|
|
||||||
invoice_items_count = InvoiceItem.count
|
|
||||||
|
|
||||||
VCR.use_cassette('store_order_admin_pay_by_cart_success') do
|
|
||||||
post '/api/checkout/payment',
|
|
||||||
params: {
|
|
||||||
payment_id: stripe_payment_method,
|
|
||||||
order_token: @cart1.token,
|
|
||||||
customer_id: @admin.id
|
|
||||||
}.to_json, headers: default_headers
|
|
||||||
end
|
|
||||||
|
|
||||||
@cart1.reload
|
|
||||||
|
|
||||||
# general assertions
|
|
||||||
assert_equal 200, response.status
|
|
||||||
assert_equal invoice_count + 1, Invoice.count
|
|
||||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
|
||||||
|
|
||||||
# invoice_items assertions
|
|
||||||
invoice_item = InvoiceItem.last
|
|
||||||
|
|
||||||
assert invoice_item.check_footprint
|
|
||||||
|
|
||||||
# invoice assertions
|
|
||||||
invoice = Invoice.last
|
|
||||||
assert_invoice_pdf invoice
|
|
||||||
assert_not_nil invoice.debug_footprint
|
|
||||||
|
|
||||||
assert_not @cart1.payment_gateway_object.blank?
|
|
||||||
assert_not invoice.payment_gateway_object.blank?
|
|
||||||
assert_not invoice.total.blank?
|
|
||||||
assert invoice.check_footprint
|
|
||||||
|
|
||||||
# notification
|
|
||||||
assert_not_empty Notification.where(attached_object: invoice)
|
|
||||||
|
|
||||||
assert_equal @cart1.state, 'paid'
|
|
||||||
assert_equal @cart1.payment_method, 'card'
|
|
||||||
assert_equal @cart1.paid_total, 262_500
|
|
||||||
|
|
||||||
stock_movement = @caisse_en_bois.product_stock_movements.last
|
|
||||||
assert_equal stock_movement.stock_type, 'external'
|
|
||||||
assert_equal stock_movement.reason, 'sold'
|
|
||||||
assert_equal stock_movement.quantity, -5
|
|
||||||
assert_equal stock_movement.order_item_id, @cart1.order_items.first.id
|
|
||||||
|
|
||||||
stock_movement = @panneaux.product_stock_movements.last
|
|
||||||
assert_equal stock_movement.stock_type, 'external'
|
|
||||||
assert_equal stock_movement.reason, 'sold'
|
|
||||||
assert_equal stock_movement.quantity, -2
|
|
||||||
assert_equal stock_movement.order_item_id, @cart1.order_items.last.id
|
|
||||||
|
|
||||||
activity = @cart1.order_activities.last
|
|
||||||
assert_equal activity.activity_type, 'paid'
|
|
||||||
assert_equal activity.operator_profile_id, @admin.invoicing_profile.id
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'admin pay himself order by cart and wallet with success' do
|
|
||||||
login_as(@admin, scope: :user)
|
|
||||||
|
|
||||||
service = WalletService.new(user: @admin, wallet: @admin.wallet)
|
|
||||||
service.credit(1000)
|
|
||||||
|
|
||||||
invoice_count = Invoice.count
|
|
||||||
invoice_items_count = InvoiceItem.count
|
|
||||||
users_credit_count = UsersCredit.count
|
|
||||||
wallet_transactions_count = WalletTransaction.count
|
|
||||||
|
|
||||||
VCR.use_cassette('store_order_admin_pay_by_cart_and_wallet_success') do
|
|
||||||
post '/api/checkout/payment',
|
|
||||||
params: {
|
|
||||||
payment_id: stripe_payment_method,
|
|
||||||
order_token: @cart1.token,
|
|
||||||
customer_id: @admin.id
|
|
||||||
}.to_json, headers: default_headers
|
|
||||||
end
|
|
||||||
|
|
||||||
@admin.wallet.reload
|
|
||||||
@cart1.reload
|
|
||||||
|
|
||||||
# general assertions
|
|
||||||
assert_equal 200, response.status
|
|
||||||
assert_equal invoice_count + 1, Invoice.count
|
|
||||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
|
||||||
|
|
||||||
# invoice_items assertions
|
|
||||||
invoice_item = InvoiceItem.last
|
|
||||||
|
|
||||||
assert invoice_item.check_footprint
|
|
||||||
|
|
||||||
# invoice assertions
|
|
||||||
invoice = Invoice.last
|
|
||||||
assert_invoice_pdf invoice
|
|
||||||
assert_not_nil invoice.debug_footprint
|
|
||||||
|
|
||||||
assert_not @cart1.payment_gateway_object.blank?
|
|
||||||
assert_not invoice.payment_gateway_object.blank?
|
|
||||||
assert_not invoice.total.blank?
|
|
||||||
assert invoice.check_footprint
|
|
||||||
|
|
||||||
# notification
|
|
||||||
assert_not_empty Notification.where(attached_object: invoice)
|
|
||||||
|
|
||||||
assert_equal @cart1.state, 'paid'
|
|
||||||
assert_equal @cart1.payment_method, 'card'
|
|
||||||
assert_equal @cart1.paid_total, 162_500
|
|
||||||
assert_equal users_credit_count, UsersCredit.count
|
|
||||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
|
||||||
|
|
||||||
# wallet
|
|
||||||
assert_equal 0, @admin.wallet.amount
|
|
||||||
assert_equal 2, @admin.wallet.wallet_transactions.count
|
|
||||||
transaction = @admin.wallet.wallet_transactions.last
|
|
||||||
assert_equal 'debit', transaction.transaction_type
|
|
||||||
assert_equal @cart1.wallet_amount / 100.0, transaction.amount
|
|
||||||
assert_equal @cart1.wallet_transaction_id, transaction.id
|
|
||||||
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'admin pay user order by local with success' do
|
test 'admin pay user order by local with success' do
|
||||||
login_as(@admin, scope: :user)
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
@ -254,63 +131,6 @@ class Store::AdminPayOrderTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal activity.operator_profile_id, @admin.invoicing_profile.id
|
assert_equal activity.operator_profile_id, @admin.invoicing_profile.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'admin pay himself order by wallet with success' do
|
|
||||||
login_as(@admin, scope: :user)
|
|
||||||
|
|
||||||
service = WalletService.new(user: @admin, wallet: @admin.wallet)
|
|
||||||
service.credit(@cart1.total / 100)
|
|
||||||
|
|
||||||
invoice_count = Invoice.count
|
|
||||||
invoice_items_count = InvoiceItem.count
|
|
||||||
users_credit_count = UsersCredit.count
|
|
||||||
wallet_transactions_count = WalletTransaction.count
|
|
||||||
|
|
||||||
post '/api/checkout/payment',
|
|
||||||
params: {
|
|
||||||
order_token: @cart1.token,
|
|
||||||
customer_id: @admin.id
|
|
||||||
}.to_json, headers: default_headers
|
|
||||||
|
|
||||||
@admin.wallet.reload
|
|
||||||
@cart1.reload
|
|
||||||
|
|
||||||
# general assertions
|
|
||||||
assert_equal 200, response.status
|
|
||||||
assert_equal @cart1.state, 'paid'
|
|
||||||
assert_equal invoice_count + 1, Invoice.count
|
|
||||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
|
||||||
assert_equal users_credit_count, UsersCredit.count
|
|
||||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
|
||||||
|
|
||||||
# invoice_items assertions
|
|
||||||
invoice_item = InvoiceItem.last
|
|
||||||
|
|
||||||
assert invoice_item.check_footprint
|
|
||||||
|
|
||||||
# invoice assertions
|
|
||||||
invoice = Invoice.last
|
|
||||||
assert_invoice_pdf invoice
|
|
||||||
assert_not_nil invoice.debug_footprint
|
|
||||||
|
|
||||||
assert invoice.payment_gateway_object.blank?
|
|
||||||
assert_not invoice.total.blank?
|
|
||||||
assert invoice.check_footprint
|
|
||||||
|
|
||||||
# notification
|
|
||||||
assert_not_empty Notification.where(attached_object: invoice)
|
|
||||||
|
|
||||||
# wallet
|
|
||||||
assert_equal 0, @admin.wallet.amount
|
|
||||||
assert_equal 2, @admin.wallet.wallet_transactions.count
|
|
||||||
transaction = @admin.wallet.wallet_transactions.last
|
|
||||||
assert_equal 'debit', transaction.transaction_type
|
|
||||||
assert_equal @cart1.paid_total, 0
|
|
||||||
assert_equal @cart1.wallet_amount / 100.0, transaction.amount
|
|
||||||
assert_equal @cart1.payment_method, 'wallet'
|
|
||||||
assert_equal @cart1.wallet_transaction_id, transaction.id
|
|
||||||
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'admin pay user order by wallet with success' do
|
test 'admin pay user order by wallet with success' do
|
||||||
login_as(@admin, scope: :user)
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
@ -91,6 +91,10 @@ class ReservationSubscriptionStatisticServiceTest < ActionDispatch::IntegrationT
|
|||||||
]
|
]
|
||||||
}.to_json, headers: default_headers
|
}.to_json, headers: default_headers
|
||||||
|
|
||||||
|
Stats::Machine.refresh_index!
|
||||||
|
Stats::Training.refresh_index!
|
||||||
|
Stats::Subscription.refresh_index!
|
||||||
|
|
||||||
# Build the stats for the last 3 days, we expect the above invoices (reservations+subscription) to appear in the resulting stats
|
# Build the stats for the last 3 days, we expect the above invoices (reservations+subscription) to appear in the resulting stats
|
||||||
::Statistics::BuilderService.generate_statistic({ start_date: 2.days.ago.beginning_of_day,
|
::Statistics::BuilderService.generate_statistic({ start_date: 2.days.ago.beginning_of_day,
|
||||||
end_date: DateTime.current.end_of_day })
|
end_date: DateTime.current.end_of_day })
|
||||||
@ -126,6 +130,8 @@ class ReservationSubscriptionStatisticServiceTest < ActionDispatch::IntegrationT
|
|||||||
check_statistics_on_user(stat_hour)
|
check_statistics_on_user(stat_hour)
|
||||||
|
|
||||||
# training
|
# training
|
||||||
|
Stats::Training.refresh_index!
|
||||||
|
|
||||||
stat_training = Stats::Training.search(query: { bool: { must: [{ term: { date: 1.day.ago.to_date.iso8601 } },
|
stat_training = Stats::Training.search(query: { bool: { must: [{ term: { date: 1.day.ago.to_date.iso8601 } },
|
||||||
{ term: { type: 'booking' } }] } }).first
|
{ term: { type: 'booking' } }] } }).first
|
||||||
assert_not_nil stat_training
|
assert_not_nil stat_training
|
||||||
|
@ -0,0 +1,338 @@
|
|||||||
|
---
|
||||||
|
http_interactions:
|
||||||
|
- request:
|
||||||
|
method: post
|
||||||
|
uri: https://api.stripe.com/v1/payment_methods
|
||||||
|
body:
|
||||||
|
encoding: UTF-8
|
||||||
|
string: type=card&card[number]=4242424242424242&card[exp_month]=4&card[exp_year]=2023&card[cvc]=314
|
||||||
|
headers:
|
||||||
|
User-Agent:
|
||||||
|
- Stripe/v1 RubyBindings/5.29.0
|
||||||
|
Authorization:
|
||||||
|
- Bearer sk_test_testfaketestfaketestfake
|
||||||
|
Content-Type:
|
||||||
|
- application/x-www-form-urlencoded
|
||||||
|
Stripe-Version:
|
||||||
|
- '2019-08-14'
|
||||||
|
X-Stripe-Client-User-Agent:
|
||||||
|
- '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.3 p62 (2019-04-16)","platform":"x86_64-darwin18","engine":"ruby","publisher":"stripe","uname":"Darwin
|
||||||
|
MacBook-Pro-Sleede-Peng 20.6.0 Darwin Kernel Version 20.6.0: Thu Sep 29 20:15:11
|
||||||
|
PDT 2022; root:xnu-7195.141.42~1/RELEASE_X86_64 x86_64","hostname":"MacBook-Pro-Sleede-Peng"}'
|
||||||
|
Accept-Encoding:
|
||||||
|
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
||||||
|
Accept:
|
||||||
|
- "*/*"
|
||||||
|
response:
|
||||||
|
status:
|
||||||
|
code: 200
|
||||||
|
message: OK
|
||||||
|
headers:
|
||||||
|
Server:
|
||||||
|
- nginx
|
||||||
|
Date:
|
||||||
|
- Mon, 07 Nov 2022 17:22:23 GMT
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Content-Length:
|
||||||
|
- '930'
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Access-Control-Allow-Credentials:
|
||||||
|
- 'true'
|
||||||
|
Access-Control-Allow-Methods:
|
||||||
|
- GET, POST, HEAD, OPTIONS, DELETE
|
||||||
|
Access-Control-Allow-Origin:
|
||||||
|
- "*"
|
||||||
|
Access-Control-Expose-Headers:
|
||||||
|
- Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required
|
||||||
|
Access-Control-Max-Age:
|
||||||
|
- '300'
|
||||||
|
Cache-Control:
|
||||||
|
- no-cache, no-store
|
||||||
|
Idempotency-Key:
|
||||||
|
- 3d420ca2-b79a-451b-88bc-56efb989ae3e
|
||||||
|
Original-Request:
|
||||||
|
- req_7tnZKRYjblvjJF
|
||||||
|
Request-Id:
|
||||||
|
- req_7tnZKRYjblvjJF
|
||||||
|
Stripe-Should-Retry:
|
||||||
|
- 'false'
|
||||||
|
Stripe-Version:
|
||||||
|
- '2019-08-14'
|
||||||
|
Strict-Transport-Security:
|
||||||
|
- max-age=63072000; includeSubDomains; preload
|
||||||
|
body:
|
||||||
|
encoding: UTF-8
|
||||||
|
string: |-
|
||||||
|
{
|
||||||
|
"id": "pm_1M1Z1f2sOmf47Nz9y4qaYQap",
|
||||||
|
"object": "payment_method",
|
||||||
|
"billing_details": {
|
||||||
|
"address": {
|
||||||
|
"city": null,
|
||||||
|
"country": null,
|
||||||
|
"line1": null,
|
||||||
|
"line2": null,
|
||||||
|
"postal_code": null,
|
||||||
|
"state": null
|
||||||
|
},
|
||||||
|
"email": null,
|
||||||
|
"name": null,
|
||||||
|
"phone": null
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"brand": "visa",
|
||||||
|
"checks": {
|
||||||
|
"address_line1_check": null,
|
||||||
|
"address_postal_code_check": null,
|
||||||
|
"cvc_check": "unchecked"
|
||||||
|
},
|
||||||
|
"country": "US",
|
||||||
|
"exp_month": 4,
|
||||||
|
"exp_year": 2023,
|
||||||
|
"fingerprint": "o52jybR7bnmNn6AT",
|
||||||
|
"funding": "credit",
|
||||||
|
"generated_from": null,
|
||||||
|
"last4": "4242",
|
||||||
|
"networks": {
|
||||||
|
"available": [
|
||||||
|
"visa"
|
||||||
|
],
|
||||||
|
"preferred": null
|
||||||
|
},
|
||||||
|
"three_d_secure_usage": {
|
||||||
|
"supported": true
|
||||||
|
},
|
||||||
|
"wallet": null
|
||||||
|
},
|
||||||
|
"created": 1667841743,
|
||||||
|
"customer": null,
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {},
|
||||||
|
"type": "card"
|
||||||
|
}
|
||||||
|
recorded_at: Mon, 07 Nov 2022 17:22:24 GMT
|
||||||
|
- request:
|
||||||
|
method: post
|
||||||
|
uri: https://api.stripe.com/v1/payment_intents
|
||||||
|
body:
|
||||||
|
encoding: UTF-8
|
||||||
|
string: payment_method=pm_1M1Z1f2sOmf47Nz9y4qaYQap&amount=162500¤cy=usd&confirmation_method=manual&confirm=true&customer=cus_8CyNk3UTi8lvCc
|
||||||
|
headers:
|
||||||
|
User-Agent:
|
||||||
|
- Stripe/v1 RubyBindings/5.29.0
|
||||||
|
Authorization:
|
||||||
|
- Bearer sk_test_testfaketestfaketestfake
|
||||||
|
Content-Type:
|
||||||
|
- application/x-www-form-urlencoded
|
||||||
|
X-Stripe-Client-Telemetry:
|
||||||
|
- '{"last_request_metrics":{"request_id":"req_7tnZKRYjblvjJF","request_duration_ms":697}}'
|
||||||
|
Stripe-Version:
|
||||||
|
- '2019-08-14'
|
||||||
|
X-Stripe-Client-User-Agent:
|
||||||
|
- '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.3 p62 (2019-04-16)","platform":"x86_64-darwin18","engine":"ruby","publisher":"stripe","uname":"Darwin
|
||||||
|
MacBook-Pro-Sleede-Peng 20.6.0 Darwin Kernel Version 20.6.0: Thu Sep 29 20:15:11
|
||||||
|
PDT 2022; root:xnu-7195.141.42~1/RELEASE_X86_64 x86_64","hostname":"MacBook-Pro-Sleede-Peng"}'
|
||||||
|
Accept-Encoding:
|
||||||
|
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
||||||
|
Accept:
|
||||||
|
- "*/*"
|
||||||
|
response:
|
||||||
|
status:
|
||||||
|
code: 200
|
||||||
|
message: OK
|
||||||
|
headers:
|
||||||
|
Server:
|
||||||
|
- nginx
|
||||||
|
Date:
|
||||||
|
- Mon, 07 Nov 2022 17:22:25 GMT
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Content-Length:
|
||||||
|
- '4476'
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Access-Control-Allow-Credentials:
|
||||||
|
- 'true'
|
||||||
|
Access-Control-Allow-Methods:
|
||||||
|
- GET, POST, HEAD, OPTIONS, DELETE
|
||||||
|
Access-Control-Allow-Origin:
|
||||||
|
- "*"
|
||||||
|
Access-Control-Expose-Headers:
|
||||||
|
- Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required
|
||||||
|
Access-Control-Max-Age:
|
||||||
|
- '300'
|
||||||
|
Cache-Control:
|
||||||
|
- no-cache, no-store
|
||||||
|
Idempotency-Key:
|
||||||
|
- 5294d6d7-d766-4aa5-bac1-be52f491fa20
|
||||||
|
Original-Request:
|
||||||
|
- req_c0S6XFCR5hlcc9
|
||||||
|
Request-Id:
|
||||||
|
- req_c0S6XFCR5hlcc9
|
||||||
|
Stripe-Should-Retry:
|
||||||
|
- 'false'
|
||||||
|
Stripe-Version:
|
||||||
|
- '2019-08-14'
|
||||||
|
Strict-Transport-Security:
|
||||||
|
- max-age=63072000; includeSubDomains; preload
|
||||||
|
body:
|
||||||
|
encoding: UTF-8
|
||||||
|
string: |-
|
||||||
|
{
|
||||||
|
"id": "pi_3M1Z1g2sOmf47Nz91KcAbrWR",
|
||||||
|
"object": "payment_intent",
|
||||||
|
"amount": 162500,
|
||||||
|
"amount_capturable": 0,
|
||||||
|
"amount_details": {
|
||||||
|
"tip": {}
|
||||||
|
},
|
||||||
|
"amount_received": 162500,
|
||||||
|
"application": null,
|
||||||
|
"application_fee_amount": null,
|
||||||
|
"automatic_payment_methods": null,
|
||||||
|
"canceled_at": null,
|
||||||
|
"cancellation_reason": null,
|
||||||
|
"capture_method": "automatic",
|
||||||
|
"charges": {
|
||||||
|
"object": "list",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "ch_3M1Z1g2sOmf47Nz91YJQvMPK",
|
||||||
|
"object": "charge",
|
||||||
|
"amount": 162500,
|
||||||
|
"amount_captured": 162500,
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"application": null,
|
||||||
|
"application_fee": null,
|
||||||
|
"application_fee_amount": null,
|
||||||
|
"balance_transaction": "txn_3M1Z1g2sOmf47Nz913ZvXNvF",
|
||||||
|
"billing_details": {
|
||||||
|
"address": {
|
||||||
|
"city": null,
|
||||||
|
"country": null,
|
||||||
|
"line1": null,
|
||||||
|
"line2": null,
|
||||||
|
"postal_code": null,
|
||||||
|
"state": null
|
||||||
|
},
|
||||||
|
"email": null,
|
||||||
|
"name": null,
|
||||||
|
"phone": null
|
||||||
|
},
|
||||||
|
"calculated_statement_descriptor": "Stripe",
|
||||||
|
"captured": true,
|
||||||
|
"created": 1667841744,
|
||||||
|
"currency": "usd",
|
||||||
|
"customer": "cus_8CyNk3UTi8lvCc",
|
||||||
|
"description": null,
|
||||||
|
"destination": null,
|
||||||
|
"dispute": null,
|
||||||
|
"disputed": false,
|
||||||
|
"failure_balance_transaction": null,
|
||||||
|
"failure_code": null,
|
||||||
|
"failure_message": null,
|
||||||
|
"fraud_details": {},
|
||||||
|
"invoice": null,
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {},
|
||||||
|
"on_behalf_of": null,
|
||||||
|
"order": null,
|
||||||
|
"outcome": {
|
||||||
|
"network_status": "approved_by_network",
|
||||||
|
"reason": null,
|
||||||
|
"risk_level": "normal",
|
||||||
|
"risk_score": 45,
|
||||||
|
"seller_message": "Payment complete.",
|
||||||
|
"type": "authorized"
|
||||||
|
},
|
||||||
|
"paid": true,
|
||||||
|
"payment_intent": "pi_3M1Z1g2sOmf47Nz91KcAbrWR",
|
||||||
|
"payment_method": "pm_1M1Z1f2sOmf47Nz9y4qaYQap",
|
||||||
|
"payment_method_details": {
|
||||||
|
"card": {
|
||||||
|
"brand": "visa",
|
||||||
|
"checks": {
|
||||||
|
"address_line1_check": null,
|
||||||
|
"address_postal_code_check": null,
|
||||||
|
"cvc_check": "pass"
|
||||||
|
},
|
||||||
|
"country": "US",
|
||||||
|
"exp_month": 4,
|
||||||
|
"exp_year": 2023,
|
||||||
|
"fingerprint": "o52jybR7bnmNn6AT",
|
||||||
|
"funding": "credit",
|
||||||
|
"installments": null,
|
||||||
|
"last4": "4242",
|
||||||
|
"mandate": null,
|
||||||
|
"network": "visa",
|
||||||
|
"three_d_secure": null,
|
||||||
|
"wallet": null
|
||||||
|
},
|
||||||
|
"type": "card"
|
||||||
|
},
|
||||||
|
"receipt_email": null,
|
||||||
|
"receipt_number": null,
|
||||||
|
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xMDNyRTYyc09tZjQ3Tno5KNH9pJsGMgbMAdBS9Kg6LBbMQFUWP1mmaiHjAUCgW-WYHRH5dwIHTlYhiTVjiSL5fqEMQr17GSJhWPA-",
|
||||||
|
"refunded": false,
|
||||||
|
"refunds": {
|
||||||
|
"object": "list",
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/charges/ch_3M1Z1g2sOmf47Nz91YJQvMPK/refunds"
|
||||||
|
},
|
||||||
|
"review": null,
|
||||||
|
"shipping": null,
|
||||||
|
"source": null,
|
||||||
|
"source_transfer": null,
|
||||||
|
"statement_descriptor": null,
|
||||||
|
"statement_descriptor_suffix": null,
|
||||||
|
"status": "succeeded",
|
||||||
|
"transfer_data": null,
|
||||||
|
"transfer_group": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"has_more": false,
|
||||||
|
"total_count": 1,
|
||||||
|
"url": "/v1/charges?payment_intent=pi_3M1Z1g2sOmf47Nz91KcAbrWR"
|
||||||
|
},
|
||||||
|
"client_secret": "pi_3M1Z1g2sOmf47Nz91KcAbrWR_secret_ocSaI8LfCIvNBzfXl5iwRB9kS",
|
||||||
|
"confirmation_method": "manual",
|
||||||
|
"created": 1667841744,
|
||||||
|
"currency": "usd",
|
||||||
|
"customer": "cus_8CyNk3UTi8lvCc",
|
||||||
|
"description": null,
|
||||||
|
"invoice": null,
|
||||||
|
"last_payment_error": null,
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {},
|
||||||
|
"next_action": null,
|
||||||
|
"on_behalf_of": null,
|
||||||
|
"payment_method": "pm_1M1Z1f2sOmf47Nz9y4qaYQap",
|
||||||
|
"payment_method_options": {
|
||||||
|
"card": {
|
||||||
|
"installments": null,
|
||||||
|
"mandate_options": null,
|
||||||
|
"network": null,
|
||||||
|
"request_three_d_secure": "automatic"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"payment_method_types": [
|
||||||
|
"card"
|
||||||
|
],
|
||||||
|
"processing": null,
|
||||||
|
"receipt_email": null,
|
||||||
|
"review": null,
|
||||||
|
"setup_future_usage": null,
|
||||||
|
"shipping": null,
|
||||||
|
"source": null,
|
||||||
|
"statement_descriptor": null,
|
||||||
|
"statement_descriptor_suffix": null,
|
||||||
|
"status": "succeeded",
|
||||||
|
"transfer_data": null,
|
||||||
|
"transfer_group": null
|
||||||
|
}
|
||||||
|
recorded_at: Mon, 07 Nov 2022 17:22:26 GMT
|
||||||
|
recorded_with: VCR 6.0.0
|
@ -13,6 +13,8 @@ http_interactions:
|
|||||||
- Bearer sk_test_testfaketestfaketestfake
|
- Bearer sk_test_testfaketestfaketestfake
|
||||||
Content-Type:
|
Content-Type:
|
||||||
- application/x-www-form-urlencoded
|
- application/x-www-form-urlencoded
|
||||||
|
X-Stripe-Client-Telemetry:
|
||||||
|
- '{"last_request_metrics":{"request_id":"req_Xmih0ndHQjzde4","request_duration_ms":2}}'
|
||||||
Stripe-Version:
|
Stripe-Version:
|
||||||
- '2019-08-14'
|
- '2019-08-14'
|
||||||
X-Stripe-Client-User-Agent:
|
X-Stripe-Client-User-Agent:
|
||||||
@ -31,7 +33,7 @@ http_interactions:
|
|||||||
Server:
|
Server:
|
||||||
- nginx
|
- nginx
|
||||||
Date:
|
Date:
|
||||||
- Mon, 07 Nov 2022 17:22:23 GMT
|
- Tue, 22 Nov 2022 10:23:29 GMT
|
||||||
Content-Type:
|
Content-Type:
|
||||||
- application/json
|
- application/json
|
||||||
Content-Length:
|
Content-Length:
|
||||||
@ -51,11 +53,11 @@ http_interactions:
|
|||||||
Cache-Control:
|
Cache-Control:
|
||||||
- no-cache, no-store
|
- no-cache, no-store
|
||||||
Idempotency-Key:
|
Idempotency-Key:
|
||||||
- 3d420ca2-b79a-451b-88bc-56efb989ae3e
|
- 524422f2-06be-4b13-a164-1d78f9221db3
|
||||||
Original-Request:
|
Original-Request:
|
||||||
- req_7tnZKRYjblvjJF
|
- req_LJ3F130BMDTCDc
|
||||||
Request-Id:
|
Request-Id:
|
||||||
- req_7tnZKRYjblvjJF
|
- req_LJ3F130BMDTCDc
|
||||||
Stripe-Should-Retry:
|
Stripe-Should-Retry:
|
||||||
- 'false'
|
- 'false'
|
||||||
Stripe-Version:
|
Stripe-Version:
|
||||||
@ -66,7 +68,7 @@ http_interactions:
|
|||||||
encoding: UTF-8
|
encoding: UTF-8
|
||||||
string: |-
|
string: |-
|
||||||
{
|
{
|
||||||
"id": "pm_1M1Z1f2sOmf47Nz9y4qaYQap",
|
"id": "pm_1M6tdV2sOmf47Nz9ZhwXBRTL",
|
||||||
"object": "payment_method",
|
"object": "payment_method",
|
||||||
"billing_details": {
|
"billing_details": {
|
||||||
"address": {
|
"address": {
|
||||||
@ -106,19 +108,19 @@ http_interactions:
|
|||||||
},
|
},
|
||||||
"wallet": null
|
"wallet": null
|
||||||
},
|
},
|
||||||
"created": 1667841743,
|
"created": 1669112609,
|
||||||
"customer": null,
|
"customer": null,
|
||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"type": "card"
|
"type": "card"
|
||||||
}
|
}
|
||||||
recorded_at: Mon, 07 Nov 2022 17:22:24 GMT
|
recorded_at: Tue, 22 Nov 2022 10:23:29 GMT
|
||||||
- request:
|
- request:
|
||||||
method: post
|
method: post
|
||||||
uri: https://api.stripe.com/v1/payment_intents
|
uri: https://api.stripe.com/v1/payment_intents
|
||||||
body:
|
body:
|
||||||
encoding: UTF-8
|
encoding: UTF-8
|
||||||
string: payment_method=pm_1M1Z1f2sOmf47Nz9y4qaYQap&amount=162500¤cy=usd&confirmation_method=manual&confirm=true&customer=cus_8CyNk3UTi8lvCc
|
string: payment_method=pm_1M6tdV2sOmf47Nz9ZhwXBRTL&amount=162500¤cy=usd&confirmation_method=manual&confirm=true&customer=cus_8CyNk3UTi8lvCc
|
||||||
headers:
|
headers:
|
||||||
User-Agent:
|
User-Agent:
|
||||||
- Stripe/v1 RubyBindings/5.29.0
|
- Stripe/v1 RubyBindings/5.29.0
|
||||||
@ -127,7 +129,7 @@ http_interactions:
|
|||||||
Content-Type:
|
Content-Type:
|
||||||
- application/x-www-form-urlencoded
|
- application/x-www-form-urlencoded
|
||||||
X-Stripe-Client-Telemetry:
|
X-Stripe-Client-Telemetry:
|
||||||
- '{"last_request_metrics":{"request_id":"req_7tnZKRYjblvjJF","request_duration_ms":697}}'
|
- '{"last_request_metrics":{"request_id":"req_LJ3F130BMDTCDc","request_duration_ms":737}}'
|
||||||
Stripe-Version:
|
Stripe-Version:
|
||||||
- '2019-08-14'
|
- '2019-08-14'
|
||||||
X-Stripe-Client-User-Agent:
|
X-Stripe-Client-User-Agent:
|
||||||
@ -146,7 +148,7 @@ http_interactions:
|
|||||||
Server:
|
Server:
|
||||||
- nginx
|
- nginx
|
||||||
Date:
|
Date:
|
||||||
- Mon, 07 Nov 2022 17:22:25 GMT
|
- Tue, 22 Nov 2022 10:23:31 GMT
|
||||||
Content-Type:
|
Content-Type:
|
||||||
- application/json
|
- application/json
|
||||||
Content-Length:
|
Content-Length:
|
||||||
@ -166,11 +168,11 @@ http_interactions:
|
|||||||
Cache-Control:
|
Cache-Control:
|
||||||
- no-cache, no-store
|
- no-cache, no-store
|
||||||
Idempotency-Key:
|
Idempotency-Key:
|
||||||
- 5294d6d7-d766-4aa5-bac1-be52f491fa20
|
- 3c835ee8-86ea-4896-811f-0d3e0785cd09
|
||||||
Original-Request:
|
Original-Request:
|
||||||
- req_c0S6XFCR5hlcc9
|
- req_ybBnHONjaSyx1X
|
||||||
Request-Id:
|
Request-Id:
|
||||||
- req_c0S6XFCR5hlcc9
|
- req_ybBnHONjaSyx1X
|
||||||
Stripe-Should-Retry:
|
Stripe-Should-Retry:
|
||||||
- 'false'
|
- 'false'
|
||||||
Stripe-Version:
|
Stripe-Version:
|
||||||
@ -181,7 +183,7 @@ http_interactions:
|
|||||||
encoding: UTF-8
|
encoding: UTF-8
|
||||||
string: |-
|
string: |-
|
||||||
{
|
{
|
||||||
"id": "pi_3M1Z1g2sOmf47Nz91KcAbrWR",
|
"id": "pi_3M6tdW2sOmf47Nz90KZ43vXZ",
|
||||||
"object": "payment_intent",
|
"object": "payment_intent",
|
||||||
"amount": 162500,
|
"amount": 162500,
|
||||||
"amount_capturable": 0,
|
"amount_capturable": 0,
|
||||||
@ -199,7 +201,7 @@ http_interactions:
|
|||||||
"object": "list",
|
"object": "list",
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"id": "ch_3M1Z1g2sOmf47Nz91YJQvMPK",
|
"id": "ch_3M6tdW2sOmf47Nz90CWztIp1",
|
||||||
"object": "charge",
|
"object": "charge",
|
||||||
"amount": 162500,
|
"amount": 162500,
|
||||||
"amount_captured": 162500,
|
"amount_captured": 162500,
|
||||||
@ -207,7 +209,7 @@ http_interactions:
|
|||||||
"application": null,
|
"application": null,
|
||||||
"application_fee": null,
|
"application_fee": null,
|
||||||
"application_fee_amount": null,
|
"application_fee_amount": null,
|
||||||
"balance_transaction": "txn_3M1Z1g2sOmf47Nz913ZvXNvF",
|
"balance_transaction": "txn_3M6tdW2sOmf47Nz90xHPB7ge",
|
||||||
"billing_details": {
|
"billing_details": {
|
||||||
"address": {
|
"address": {
|
||||||
"city": null,
|
"city": null,
|
||||||
@ -223,7 +225,7 @@ http_interactions:
|
|||||||
},
|
},
|
||||||
"calculated_statement_descriptor": "Stripe",
|
"calculated_statement_descriptor": "Stripe",
|
||||||
"captured": true,
|
"captured": true,
|
||||||
"created": 1667841744,
|
"created": 1669112610,
|
||||||
"currency": "usd",
|
"currency": "usd",
|
||||||
"customer": "cus_8CyNk3UTi8lvCc",
|
"customer": "cus_8CyNk3UTi8lvCc",
|
||||||
"description": null,
|
"description": null,
|
||||||
@ -243,13 +245,13 @@ http_interactions:
|
|||||||
"network_status": "approved_by_network",
|
"network_status": "approved_by_network",
|
||||||
"reason": null,
|
"reason": null,
|
||||||
"risk_level": "normal",
|
"risk_level": "normal",
|
||||||
"risk_score": 45,
|
"risk_score": 19,
|
||||||
"seller_message": "Payment complete.",
|
"seller_message": "Payment complete.",
|
||||||
"type": "authorized"
|
"type": "authorized"
|
||||||
},
|
},
|
||||||
"paid": true,
|
"paid": true,
|
||||||
"payment_intent": "pi_3M1Z1g2sOmf47Nz91KcAbrWR",
|
"payment_intent": "pi_3M6tdW2sOmf47Nz90KZ43vXZ",
|
||||||
"payment_method": "pm_1M1Z1f2sOmf47Nz9y4qaYQap",
|
"payment_method": "pm_1M6tdV2sOmf47Nz9ZhwXBRTL",
|
||||||
"payment_method_details": {
|
"payment_method_details": {
|
||||||
"card": {
|
"card": {
|
||||||
"brand": "visa",
|
"brand": "visa",
|
||||||
@ -274,14 +276,14 @@ http_interactions:
|
|||||||
},
|
},
|
||||||
"receipt_email": null,
|
"receipt_email": null,
|
||||||
"receipt_number": null,
|
"receipt_number": null,
|
||||||
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xMDNyRTYyc09tZjQ3Tno5KNH9pJsGMgbMAdBS9Kg6LBbMQFUWP1mmaiHjAUCgW-WYHRH5dwIHTlYhiTVjiSL5fqEMQr17GSJhWPA-",
|
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xMDNyRTYyc09tZjQ3Tno5KKPG8psGMgbPi1sBzwo6LBYq6qekkFXHe51z6Ei_GniUXrfPnNwAP9pxTHhMG3SSyEbSD-_jgxqNwc0o",
|
||||||
"refunded": false,
|
"refunded": false,
|
||||||
"refunds": {
|
"refunds": {
|
||||||
"object": "list",
|
"object": "list",
|
||||||
"data": [],
|
"data": [],
|
||||||
"has_more": false,
|
"has_more": false,
|
||||||
"total_count": 0,
|
"total_count": 0,
|
||||||
"url": "/v1/charges/ch_3M1Z1g2sOmf47Nz91YJQvMPK/refunds"
|
"url": "/v1/charges/ch_3M6tdW2sOmf47Nz90CWztIp1/refunds"
|
||||||
},
|
},
|
||||||
"review": null,
|
"review": null,
|
||||||
"shipping": null,
|
"shipping": null,
|
||||||
@ -296,11 +298,11 @@ http_interactions:
|
|||||||
],
|
],
|
||||||
"has_more": false,
|
"has_more": false,
|
||||||
"total_count": 1,
|
"total_count": 1,
|
||||||
"url": "/v1/charges?payment_intent=pi_3M1Z1g2sOmf47Nz91KcAbrWR"
|
"url": "/v1/charges?payment_intent=pi_3M6tdW2sOmf47Nz90KZ43vXZ"
|
||||||
},
|
},
|
||||||
"client_secret": "pi_3M1Z1g2sOmf47Nz91KcAbrWR_secret_ocSaI8LfCIvNBzfXl5iwRB9kS",
|
"client_secret": "pi_3M6tdW2sOmf47Nz90KZ43vXZ_secret_EZPT1VWG8jNZgqaW0iqXN0LjY",
|
||||||
"confirmation_method": "manual",
|
"confirmation_method": "manual",
|
||||||
"created": 1667841744,
|
"created": 1669112610,
|
||||||
"currency": "usd",
|
"currency": "usd",
|
||||||
"customer": "cus_8CyNk3UTi8lvCc",
|
"customer": "cus_8CyNk3UTi8lvCc",
|
||||||
"description": null,
|
"description": null,
|
||||||
@ -310,7 +312,7 @@ http_interactions:
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"next_action": null,
|
"next_action": null,
|
||||||
"on_behalf_of": null,
|
"on_behalf_of": null,
|
||||||
"payment_method": "pm_1M1Z1f2sOmf47Nz9y4qaYQap",
|
"payment_method": "pm_1M6tdV2sOmf47Nz9ZhwXBRTL",
|
||||||
"payment_method_options": {
|
"payment_method_options": {
|
||||||
"card": {
|
"card": {
|
||||||
"installments": null,
|
"installments": null,
|
||||||
@ -334,5 +336,5 @@ http_interactions:
|
|||||||
"transfer_data": null,
|
"transfer_data": null,
|
||||||
"transfer_group": null
|
"transfer_group": null
|
||||||
}
|
}
|
||||||
recorded_at: Mon, 07 Nov 2022 17:22:26 GMT
|
recorded_at: Tue, 22 Nov 2022 10:23:31 GMT
|
||||||
recorded_with: VCR 6.0.0
|
recorded_with: VCR 6.0.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user