shoppper 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +22 -0
- data/app/assets/config/shopper_manifest.js +2 -0
- data/app/assets/javascripts/shopper/application.js +15 -0
- data/app/assets/stylesheets/shopper/application.css +15 -0
- data/app/commands/shopper/cart_page/add_product.rb +16 -0
- data/app/commands/shopper/cart_page/update_cart.rb +21 -0
- data/app/commands/shopper/cart_page/update_coupon.rb +51 -0
- data/app/commands/shopper/cart_page/update_order_items.rb +31 -0
- data/app/commands/shopper/checkout_page/add_checkout_addresses.rb +62 -0
- data/app/commands/shopper/checkout_page/add_checkout_delivery.rb +18 -0
- data/app/commands/shopper/checkout_page/add_checkout_payment.rb +19 -0
- data/app/commands/shopper/checkout_page/place_order.rb +40 -0
- data/app/commands/shopper/checkout_page/proceed_checkout.rb +14 -0
- data/app/commands/shopper/orders_page/get_orders.rb +34 -0
- data/app/controllers/shopper/cart_controller.rb +29 -0
- data/app/controllers/shopper/checkout_controller.rb +68 -0
- data/app/controllers/shopper/order_item_controller.rb +12 -0
- data/app/controllers/shopper/orders_controller.rb +19 -0
- data/app/controllers/shopper/shopper_controller.rb +4 -0
- data/app/decorators/concerns/shopper/caller_attachable.rb +24 -0
- data/app/decorators/concerns/shopper/view_helpers.rb +15 -0
- data/app/decorators/shopper/cart_page/coupon_decorator.rb +23 -0
- data/app/decorators/shopper/checkout_page/credit_card_decorator.rb +9 -0
- data/app/decorators/shopper/checkout_page/delivery_decorator.rb +28 -0
- data/app/decorators/shopper/items_table/item_decorator.rb +46 -0
- data/app/decorators/shopper/order_details/address_decorator.rb +19 -0
- data/app/decorators/shopper/order_details/order_decorator.rb +45 -0
- data/app/decorators/shopper/order_summary/order_decorator.rb +38 -0
- data/app/decorators/shopper/orders_page/order_decorator.rb +31 -0
- data/app/forms/shopper/address_form.rb +32 -0
- data/app/forms/shopper/coupon_form.rb +12 -0
- data/app/forms/shopper/credit_card_form.rb +36 -0
- data/app/helpers/shopper/application_helper.rb +4 -0
- data/app/jobs/shopper/application_job.rb +4 -0
- data/app/mailers/shopper/checkout_mailer.rb +9 -0
- data/app/models/concerns/shopper/order_aasm.rb +35 -0
- data/app/models/concerns/shopper/order_arithmetic_helpers.rb +24 -0
- data/app/models/concerns/shopper/order_item_arithmetic_helpers.rb +7 -0
- data/app/models/concerns/shopper/order_number.rb +13 -0
- data/app/models/concerns/shopper/order_product_helpers.rb +15 -0
- data/app/models/shopper/address.rb +12 -0
- data/app/models/shopper/application_record.rb +5 -0
- data/app/models/shopper/billing_address.rb +4 -0
- data/app/models/shopper/country.rb +11 -0
- data/app/models/shopper/coupon.rb +13 -0
- data/app/models/shopper/credit_card.rb +9 -0
- data/app/models/shopper/delivery.rb +24 -0
- data/app/models/shopper/order.rb +27 -0
- data/app/models/shopper/order_item.rb +18 -0
- data/app/models/shopper/shipping_address.rb +4 -0
- data/app/presenters/shopper/cart_page/cart_presenter.rb +34 -0
- data/app/presenters/shopper/checkout_page/address_step_presenter.rb +55 -0
- data/app/presenters/shopper/checkout_page/complete_step_presenter.rb +34 -0
- data/app/presenters/shopper/checkout_page/confirm_step_presenter.rb +20 -0
- data/app/presenters/shopper/checkout_page/delivery_step_presenter.rb +16 -0
- data/app/presenters/shopper/checkout_page/payment_step_presenter.rb +16 -0
- data/app/presenters/shopper/checkout_page/progress_presenter.rb +37 -0
- data/app/presenters/shopper/orders_page/order_presenter.rb +28 -0
- data/app/presenters/shopper/orders_page/orders_presenter.rb +17 -0
- data/app/services/shopper/checkout_manager.rb +55 -0
- data/app/validators/shopper/phone_validator.rb +11 -0
- data/app/views/shopper/cart/add_product.js.haml +3 -0
- data/app/views/shopper/cart/show.html.haml +22 -0
- data/app/views/shopper/checkout/_address_fields.html.haml +7 -0
- data/app/views/shopper/checkout/_layout.html.haml +12 -0
- data/app/views/shopper/checkout/address.html.haml +37 -0
- data/app/views/shopper/checkout/complete.html.haml +17 -0
- data/app/views/shopper/checkout/confirm.html.haml +6 -0
- data/app/views/shopper/checkout/delivery.html.haml +35 -0
- data/app/views/shopper/checkout/payment.html.haml +20 -0
- data/app/views/shopper/checkout_mailer/complete.html.haml +5 -0
- data/app/views/shopper/order_item/_table.html.haml +4 -0
- data/app/views/shopper/order_item/_table_desktop.html.haml +40 -0
- data/app/views/shopper/order_item/_table_phone.html.haml +30 -0
- data/app/views/shopper/orders/_details.html.haml +21 -0
- data/app/views/shopper/orders/_summary.html.haml +27 -0
- data/app/views/shopper/orders/index.html.haml +32 -0
- data/app/views/shopper/orders/show.html.haml +4 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20170126000000_create_shopper_countries.rb +11 -0
- data/db/migrate/20170127194802_create_shopper_addresses.rb +19 -0
- data/db/migrate/20170127194999_create_shopper_deliveries.rb +12 -0
- data/db/migrate/20170129160931_create_shopper_orders.rb +14 -0
- data/db/migrate/20170129162148_create_shopper_order_items.rb +14 -0
- data/db/migrate/20170220181741_create_shopper_coupons.rb +11 -0
- data/db/migrate/20170224112113_add_state_to_shopper_orders.rb +5 -0
- data/db/migrate/20170224174654_create_shopper_credit_cards.rb +15 -0
- data/lib/shopper.rb +18 -0
- data/lib/shopper/checkout_wizard.rb +43 -0
- data/lib/shopper/configuration.rb +22 -0
- data/lib/shopper/current_order.rb +47 -0
- data/lib/shopper/engine.rb +21 -0
- data/lib/shopper/model_methods.rb +35 -0
- data/lib/shopper/version.rb +3 -0
- data/lib/tasks/shopper_tasks.rake +4 -0
- data/spec/commands/add_product_spec.rb +39 -0
- data/spec/controllers/cart_controller_spec.rb +34 -0
- data/spec/controllers/order_item_controller_spec.rb +18 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +5 -0
- data/spec/dummy/app/assets/images/fallback/product_default.png +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/javascripts/cable.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +20 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/jobs/application_job.rb +2 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/ability.rb +7 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/customer.rb +3 -0
- data/spec/dummy/app/models/product.rb +21 -0
- data/spec/dummy/app/views/layouts/application.html.haml +17 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/app/views/root/index.html +1 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +34 -0
- data/spec/dummy/bin/update +29 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +21 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +9 -0
- data/spec/dummy/config/database.ci.yml +7 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +54 -0
- data/spec/dummy/config/environments/production.rb +86 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/new_framework_defaults.rb +24 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/simple_form.rb +169 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/locales/simple_form.en.yml +31 -0
- data/spec/dummy/config/puma.rb +47 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/db/migrate/20170405091757_create_products.rb +10 -0
- data/spec/dummy/db/migrate/20170405160406_create_customers.rb +11 -0
- data/spec/dummy/db/schema.rb +33 -0
- data/spec/dummy/lib/templates/erb/scaffold/_form.html.erb +13 -0
- data/spec/dummy/log/development.log +378 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/-5/-5Q4Ub_11IMVDpGB1eI8cCVFFsUlJ8XHRzneSdfWwvA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/-u/-uuoXn7BP51SPd9jCpfonUaE46sb8x-w0b34wwdHt9E.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/0T/0T0Tt9dfVU3GpS08jbSmEA-3V9YEKDfIoSeIPwG-V5M.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/0X/0XswbOgGjXmH1fo5xq93jNgHe48bthciRdbD6tnWCvo.cache +3 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/1c/1ctUiWcG10n8KMVoiqAlIapKLbW5MDV1opGFwHsx34o.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/2Z/2ZWFIlXKLDgIwGlnU7FU5aLf_Y1-m4KLYDXG0GdOvcc.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/2c/2cgrQmg7ldfRivjXyyPNEH2O9Kps6UbeJH-Y9H1GmAs.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/2l/2ltamKE3DUsifk0Gd4gmMxsgm5yKUZiNdhnSVHq_rVs.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/31/312SvTp1rlNjttuCrjMR-sOiWZ8_7XmiagVfUeNAWfU.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/4G/4GIZLDG2a1lnDZF2QESc0VLN9oHXztGnjE-LhJAssbk.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/4R/4RE2lQ9Q_Khn7V4lGNDAW4xVGALXbiqnfOx-mXE831Q.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/6U/6UKVmfPR7t8yUajbBceuWKaXogETyIV6Tbp0Mo8sR8A.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/6Z/6Zs5ocm3aNgIQhqxQonxR7DfNzImkwFVBFsFSoXJ9nM.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/6b/6bbfebueG2G5YjKNjtH5pwuc1LEJjKmfpIL4IREIArw.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/7n/7ncqaz_mmyCQD_AQHpvMs2GXmVS9ETbTrWhH7RWvuYg.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/7v/7vrAes6c_ihwe8jMx1yxyRDgWgAmDYBRMlsqqjlOA24.cache +3 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/8t/8tcTiIy9nbujTSsrbowd_m1ZDK-XWxe5KBzu4iWULdg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/9H/9H8beH0fozTusUWWhP9agRzmmCJigE63QS3yN6USIVY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/BX/BXeClZZ7pe07EVX5rjMY_bO_Ec1T114oejXtrS7DYLY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/DN/DN6DwzYk58CqQliQANOOcf4LZ9NfwuROLMpG_aIn1iA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/EF/EFDkZUV83NReWDmYJPAbH4QssV1WH7dhLAafE0jMdLE.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F4/F4HRhWWKLcUlE_9fUDcNFndpclhItP7pzduMZ0u0zEg.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F9/F95lTcrn4OEdV2-8CpWRA7Vo_g8Cyy46-JWOiLmNuZ8.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/G5/G52PYwE_K5U0GKtgEfZMMMxYvZn20YV8JnDsE8miA8U.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/GY/GYoI8lC94xLeXahTKQmXCYQmEcBTeJVY3rw4Ow29mAk.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/HI/HIQD3918XDGcbRA7DQnrUx7Ij6_KmqX7A6-Xg8cTgvs.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/IG/IGBxZd8bfUKXZ0qApnsHIDYrpmlGh4_stvNLwNClo-Q.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/JZ/JZvskjPGda8b9BWf--ABB7K8u8eZWLEY_IGmIwN-lsM.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/LZ/LZtqwTyBeV780reoWMqU06rIHTuSggj_moLE9aZl4zc.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/MH/MHuhgGwsUMEUsXy-NMZLnq_3FmpwQ6Hr-x9Pj2Lg6jQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/MW/MWqLf-tbtXiYTRmuVMVRXCVjAEX6LaEZGlVxS74nM-E.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/OK/OKLY4QbXl_c1lHz1gm6tyYrpRIFU-014RSUQpb9fwyw.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Ok/OkpxMmj1ovAbbyXiexCJnj0apz54kz8iqahL4c1aXDo.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/QI/QI3sr401dtpp_Wc_mTpdvAI5bpraLUIIf8Mv2feBvEw.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/R0/R0TSIcvnfF3-QoBBBWQ32DiJLIR8D676f-SkUhprDK8.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/RU/RUX4wpSRbADt7-aSjvb97ICl-fCJjPZxe1MNF6pcGOo.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/To/To0QY39VWI3mZb6XXZAgOQCJcEj0tv0BJmIiu2oNdbg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/W9/W9krhTTy1IFXT5Q_rF4CifVRj4AVwKeE_F6pln8Rcmg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Ws/Wsgy75qmOuCK1Vp25X17Py-pML-11GG3F3e-OYOsfGk.cache +4 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Yb/YbBCKCjwVLMTjLflIx-p2w001jnZ2kVbDwnbDTh20ps.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Zi/ZiHXd8AlVOwGmpnKoVXVS3EKEBdo1Ac-Zcp4eiCPnRQ.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/_C/_CHENopb4h3hRBPr0mRRTtNBvd64WDGI5v5hMC7PYgs.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/a9/a94GSaPUyrAw0VlSAmwgl4iwDPatPtP7tsesOf07Fuo.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/bk/bkyxSPKtV8sC84dH7iq0OkFMQzLCJ7DjdUoC6wYHaAA.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/cE/cEQDNdSRl1tLTsWUUnyDWWXECh-MtIchaMG7PA2kuM8.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ch/chgbSC87FkXFMbNWMZJAtU_euvqnCKdUcV4CAjuRZXg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/dG/dGcYyTZ1x7nZldIv9CU9P6FILL3BZ_pHeJUEBXOFDtk.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/eH/eHNL12KXLctBrvH2X9flxNGA4GRJGoyLwqRrxOWLHss.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/fm/fmL1Nfucs6nwuozNi61KJ6nEHU8mb3F2SieWSl1sgeY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/gg/ggoM7JVi5PFoOTHQGrMmhFwnOSjd_RsNYmV9SpLGNPY.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/iH/iHBHhxxOEBtpSeS0MFX4NL9vEfZMaQ72PxSXtbLy4pQ.cache +3 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/kQ/kQZ9KBvn5nS686f0v-15x3q-XPK9Puu03Od4FRDV_DY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ln/ln7-McEMuq67tYE86JS_5anWVQE-o7kjVzTfmJ-pfYU.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/n9/n9fyL8DlM6Ze117bMHweLE2x-4YM5aAW8cT3XYw-de8.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/nM/nMZGQCxEPqJUI3praZMAm-YNu7TdqkdDhx3WG6J1KLY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/n_/n_xYqQYhwEMQknb3jFQnjlxxBE9TzMNHCdJ-bEyZFIw.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/nx/nxTv3sKVUQZADJyM3dPaVmUA78MIsMLD_K279yN_GsI.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/op/opCcAQpH-enBbSXpi0u1AEuA_O2KBuVSFHXlgdw8dNg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/qC/qCvelfVAvK45T3Goi7dqiicmsAKQoF9MkLAZtA4LVC0.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/qI/qIdIIo1-efJi3gqwWbkhmcDlc4qTwuolMYutSBGYrxw.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/qP/qPmv5snMrDw830S6hSICDcnIy7kVEWoFKXhGKT38lG4.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/qx/qxYROkyCYJzti8DI00LfLhgV7FZgHCmaUnCjB3ayfWM.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/wd/wdwXC49DcebjF5LPaEs1vqDXCWlq-32hHXfiKvjifno.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/wg/wgn7xxwAmTkEhfWm8ujbjnId_c4MherEJuyXwkipOIg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/x7/x7PYh8DJvPykcEqpVab2vcY9-GFz-3cqtoMlRAu94Uc.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/xg/xgrrLe9BN5muIDu9b9ADzAJHJtZFmr-PlDUBSkdJTDM.cache +3 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/z7/z79WysQah2YzrP7757RmZSO9M_8yzxDJPheGOhMjyXU.cache +1 -0
- data/spec/factories/addresses.rb +14 -0
- data/spec/factories/countries.rb +9 -0
- data/spec/factories/coupons.rb +10 -0
- data/spec/factories/credit_cards.rb +9 -0
- data/spec/factories/customer.rb +12 -0
- data/spec/factories/deliveries.rb +8 -0
- data/spec/factories/order_items.rb +7 -0
- data/spec/factories/orders.rb +42 -0
- data/spec/factories/product.rb +6 -0
- data/spec/features/cart_spec.rb +66 -0
- data/spec/features/checkout_spec.rb +53 -0
- data/spec/forms/credit_card_form_spec.rb +56 -0
- data/spec/models/customer_spec.rb +9 -0
- data/spec/models/product_spec.rb +7 -0
- data/spec/models/shopper/address_spec.rb +14 -0
- data/spec/models/shopper/billing_address_spec.rb +7 -0
- data/spec/models/shopper/country_spec.rb +10 -0
- data/spec/models/shopper/coupon_spec.rb +9 -0
- data/spec/models/shopper/credit_card_spec.rb +7 -0
- data/spec/models/shopper/delivery_spec.rb +28 -0
- data/spec/models/shopper/order_item_spec.rb +18 -0
- data/spec/models/shopper/order_spec.rb +105 -0
- data/spec/models/shopper/shipping_address_spec.rb +7 -0
- data/spec/rails_helper.rb +52 -0
- data/spec/services/checkout_manager_spec.rb +106 -0
- data/spec/spec_helper.rb +99 -0
- data/spec/support/capybara.rb +23 -0
- data/spec/support/database_cleaner.rb +44 -0
- data/spec/support/factory_girl.rb +3 -0
- data/spec/support/macroses/shared_macroses.rb +32 -0
- data/spec/support/macroses/stub_current_order.rb +18 -0
- data/spec/support/rails-controller-testing.rb +7 -0
- data/spec/support/shared_examples/addressable.rb +22 -0
- data/spec/support/shoulda_matchers.rb +4 -0
- data/spec/support/show_me_the_cookies.rb +3 -0
- metadata +855 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module Shopper
|
2
|
+
class CartController < ShopperController
|
3
|
+
respond_to :js, only: [:add_product]
|
4
|
+
|
5
|
+
def show
|
6
|
+
present CartPage::CartPresenter.new(current_order)
|
7
|
+
end
|
8
|
+
|
9
|
+
def update
|
10
|
+
CartPage::UpdateCart.call(params, current_order) do
|
11
|
+
on(:invalid_coupon) { flash[:error] = 'Invalid coupon code' }
|
12
|
+
on(:invalid_product) { flash[:error] = 'Invalid product quantity' }
|
13
|
+
on(:validate) do |*attr|
|
14
|
+
present CartPage::CartPresenter.new(*attr)
|
15
|
+
render 'show'
|
16
|
+
end
|
17
|
+
on(:ok) { redirect_to cart_path, flash: { success: 'Cart was updated successfully' } }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_product
|
22
|
+
CartPage::AddProduct.call(params) do
|
23
|
+
on(:invalid_product) do
|
24
|
+
redirect_to main_app.root_path, flash: { error: 'Invalid product' }, js: true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Shopper
|
2
|
+
class CheckoutController < ShopperController
|
3
|
+
include Shopper::CheckoutWizard
|
4
|
+
|
5
|
+
before_action :fast_authenticate_customer!
|
6
|
+
before_action :check_empty_cart
|
7
|
+
before_action :check_accesability, except: :complete
|
8
|
+
|
9
|
+
before_action :set_progress_presenter
|
10
|
+
|
11
|
+
rescue_from Wicked::Wizard::InvalidStepError do |_|
|
12
|
+
redirect_to cart_path, alert: t('checkout.failure.invalid_step')
|
13
|
+
end
|
14
|
+
|
15
|
+
rescue_from Shopper::CheckoutWizard::EmptyCardError do |_|
|
16
|
+
redirect_to cart_path, alert: t('checkout.failure.cart_empty')
|
17
|
+
end
|
18
|
+
|
19
|
+
rescue_from Shopper::CheckoutWizard::CantAccessError do |_|
|
20
|
+
redirect_to checkout_path(minimal_accessible_step),
|
21
|
+
flash: { error: t('checkout.failure.must_fill_previous') }
|
22
|
+
end
|
23
|
+
|
24
|
+
def show
|
25
|
+
present step_presenter.new(current_order)
|
26
|
+
render_wizard
|
27
|
+
end
|
28
|
+
|
29
|
+
def update
|
30
|
+
Shopper::CheckoutPage::ProceedCheckout.call(current_order, params, step) do
|
31
|
+
on(:invalid) do |*attrs|
|
32
|
+
present step_presenter.new(*attrs)
|
33
|
+
render_wizard
|
34
|
+
end
|
35
|
+
on(:ok) { redirect_to_next_step }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def complete
|
40
|
+
Shopper::CheckoutPage::PlaceOrder.call(current_order, params) do
|
41
|
+
on(:invalid) { redirect_to cart_path, alert: t('checkout.failure.invalid_step') }
|
42
|
+
on(:ok) do |old_order|
|
43
|
+
present step_presenter.new(old_order)
|
44
|
+
render_wizard
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def set_progress_presenter
|
52
|
+
select_up_to_step = if step == :complete
|
53
|
+
:complete
|
54
|
+
else
|
55
|
+
minimal_accessible_step
|
56
|
+
end
|
57
|
+
present Shopper::CheckoutPage::ProgressPresenter.new(
|
58
|
+
steps,
|
59
|
+
step,
|
60
|
+
select_up_to_step
|
61
|
+
), for: :progress
|
62
|
+
end
|
63
|
+
|
64
|
+
def step_presenter
|
65
|
+
"Shopper::CheckoutPage::#{step.capitalize}StepPresenter".constantize
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Shopper
|
2
|
+
class OrderItemController < ShopperController
|
3
|
+
def destroy
|
4
|
+
@item = OrderItem.find(params[:id])
|
5
|
+
authorize! :destroy, @item
|
6
|
+
@item.destroy
|
7
|
+
redirect_to cart_path
|
8
|
+
rescue CanCan::AccessDenied
|
9
|
+
redirect_to cart_path, alert: t('auth.access_denied')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Shopper
|
2
|
+
class OrdersController < ShopperController
|
3
|
+
before_action :authenticate_user!
|
4
|
+
load_and_authorize_resource only: :show
|
5
|
+
|
6
|
+
def index
|
7
|
+
OrdersPage::GetOrders.call do
|
8
|
+
on(:invalid_fiter) { flash[:error] = 'Invalid filter' }
|
9
|
+
on(:ok) do |*attrs|
|
10
|
+
present OrdersPage::OrdersPresenter.new(*attrs)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def show
|
16
|
+
present OrdersPage::OrderPresenter.new(@order)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Shopper
|
2
|
+
module CallerAttachable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
def attach(caller)
|
7
|
+
@caller = caller
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(method_name, *args, &block)
|
12
|
+
if @caller&.respond_to?(method_name, true)
|
13
|
+
@caller.send(method_name, *args, &block)
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_to_missing?(method_name, include_private = false)
|
20
|
+
@caller&.respond_to?(method_name, include_private) || super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Shopper
|
2
|
+
module CartPage
|
3
|
+
class CouponDecorator < SimpleDelegator
|
4
|
+
def error_class
|
5
|
+
'has-error' if error_message
|
6
|
+
end
|
7
|
+
|
8
|
+
def helper?
|
9
|
+
error_message
|
10
|
+
end
|
11
|
+
|
12
|
+
def helper
|
13
|
+
error_message
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def error_message
|
19
|
+
@error_message ||= __getobj__.errors&.full_messages_for(:code)&.first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Shopper
|
2
|
+
module CheckoutPage
|
3
|
+
class DeliveryDecorator < SimpleDelegator
|
4
|
+
include Shopper::ViewHelpers
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def for_collection(objects, current_delivery_id:)
|
8
|
+
@current_delivery_id = current_delivery_id
|
9
|
+
objects.map { |object| new(object) }
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :current_delivery_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def days
|
16
|
+
"#{min_days} to #{max_days} days"
|
17
|
+
end
|
18
|
+
|
19
|
+
def price
|
20
|
+
helpers.number_to_currency(__getobj__.price)
|
21
|
+
end
|
22
|
+
|
23
|
+
def selected?
|
24
|
+
__getobj__.id == self.class.current_delivery_id
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Shopper
|
2
|
+
module ItemsTable
|
3
|
+
class ItemDecorator < SimpleDelegator
|
4
|
+
class << self
|
5
|
+
def for_collection(objects, editable: true, description: true)
|
6
|
+
@editable = editable
|
7
|
+
@description = description
|
8
|
+
objects.map { |object| new(object) }
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :editable, :description
|
12
|
+
end
|
13
|
+
|
14
|
+
include Shopper::ViewHelpers
|
15
|
+
|
16
|
+
def initialize(order_item)
|
17
|
+
@order_item = order_item
|
18
|
+
super(order_item.product)
|
19
|
+
end
|
20
|
+
|
21
|
+
delegate :id, :quantity, :to_param, to: :order_item
|
22
|
+
delegate :editable, to: :class
|
23
|
+
attr_reader :order_item
|
24
|
+
|
25
|
+
def cover
|
26
|
+
cover_url_or_default(version: :thumb)
|
27
|
+
end
|
28
|
+
|
29
|
+
def description
|
30
|
+
__getobj__.description.truncate(50) if self.class.description
|
31
|
+
end
|
32
|
+
|
33
|
+
def error_class
|
34
|
+
'has-error' if @order_item.errors&.any?
|
35
|
+
end
|
36
|
+
|
37
|
+
def price
|
38
|
+
helpers.number_to_currency(__getobj__.price)
|
39
|
+
end
|
40
|
+
|
41
|
+
def subtotal
|
42
|
+
helpers.number_to_currency(@order_item.subtotal)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Shopper
|
2
|
+
module OrderDetails
|
3
|
+
class AddressDecorator < SimpleDelegator
|
4
|
+
include Shopper::ViewHelpers
|
5
|
+
|
6
|
+
BR = '<br />'.html_safe.freeze
|
7
|
+
|
8
|
+
def to_html
|
9
|
+
full_name = "#{first_name} #{last_name}"
|
10
|
+
middle = helpers.safe_join([street, city, country.name], BR)
|
11
|
+
|
12
|
+
phone_label = I18n.t('simple_form.labels.address.phone')
|
13
|
+
phone = helpers.safe_join([phone_label, __getobj__.phone], ' ')
|
14
|
+
|
15
|
+
helpers.safe_join([full_name, middle, phone], BR)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Shopper
|
2
|
+
module OrderDetails
|
3
|
+
class OrderDecorator < SimpleDelegator
|
4
|
+
include Shopper::CallerAttachable
|
5
|
+
|
6
|
+
STARS = '** ** ** '.freeze
|
7
|
+
BR = '<br />'.html_safe.freeze
|
8
|
+
|
9
|
+
def initialize(order, edit_link:)
|
10
|
+
@edit_link = edit_link
|
11
|
+
super order
|
12
|
+
end
|
13
|
+
|
14
|
+
def edit_link?
|
15
|
+
@edit_link
|
16
|
+
end
|
17
|
+
|
18
|
+
def edit_link(step)
|
19
|
+
link_to t('checkout.edit'), @caller.wizard_path(step), class: 'general-edit'
|
20
|
+
end
|
21
|
+
|
22
|
+
def billing_address
|
23
|
+
@billing_address ||= OrderDetails::AddressDecorator.new(__getobj__.billing_address).to_html
|
24
|
+
end
|
25
|
+
|
26
|
+
def shipping_address
|
27
|
+
return billing_address if __getobj__.use_billing?
|
28
|
+
OrderDetails::AddressDecorator.new(__getobj__.shipping_address).to_html
|
29
|
+
end
|
30
|
+
|
31
|
+
def shipments
|
32
|
+
delivery = __getobj__.delivery
|
33
|
+
price = number_to_currency(delivery.price)
|
34
|
+
safe_join([price, delivery.title], BR)
|
35
|
+
end
|
36
|
+
|
37
|
+
def payment
|
38
|
+
card = __getobj__.credit_card
|
39
|
+
number = STARS + card.number.last(4)
|
40
|
+
date = card.expiration_date.strftime '%m/%Y'
|
41
|
+
safe_join [number, date], BR
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Shopper
|
2
|
+
module OrderSummary
|
3
|
+
class OrderDecorator < SimpleDelegator
|
4
|
+
include Shopper::ViewHelpers
|
5
|
+
|
6
|
+
def initialize(order, deficit_method: :show_zero, position: :right)
|
7
|
+
@deficit_method = deficit_method
|
8
|
+
@position = position
|
9
|
+
super order
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :position
|
13
|
+
|
14
|
+
def subtotal
|
15
|
+
@subtotal = wrap(__getobj__.subtotal)
|
16
|
+
end
|
17
|
+
|
18
|
+
def coupon
|
19
|
+
@coupon = wrap(__getobj__.saved_by_coupon)
|
20
|
+
end
|
21
|
+
|
22
|
+
def delivery
|
23
|
+
@delivery = wrap(__getobj__.delivery_price)
|
24
|
+
end
|
25
|
+
|
26
|
+
def order_total
|
27
|
+
@order_total = wrap(__getobj__.total)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def wrap(input)
|
33
|
+
return false if input.zero? && @deficit_method == :hide
|
34
|
+
helpers.number_to_currency(input)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Shopper
|
2
|
+
module OrdersPage
|
3
|
+
class OrderDecorator < SimpleDelegator
|
4
|
+
include Shopper::ViewHelpers
|
5
|
+
|
6
|
+
def self.for_collection(objects)
|
7
|
+
objects.map { |object| new(object) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def completed_at
|
11
|
+
time = __getobj__.completed_at
|
12
|
+
return 'Unknown' if time.nil?
|
13
|
+
time.strftime '%Y-%m-%d'
|
14
|
+
end
|
15
|
+
|
16
|
+
def state
|
17
|
+
I18n.t("order.states.#{__getobj__.state}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def state_classes
|
21
|
+
classes = 'font-16 font-weight-light'
|
22
|
+
classes + (__getobj__.delivered? ? 'text-success' : 'in-grey-900')
|
23
|
+
end
|
24
|
+
|
25
|
+
def total
|
26
|
+
price = __getobj__.order_items.inject(0) { |sum, item| sum + item.subtotal }
|
27
|
+
helpers.number_to_currency(price)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Shopper
|
2
|
+
class AddressForm < Rectify::Form
|
3
|
+
attribute :first_name, String
|
4
|
+
attribute :last_name, String
|
5
|
+
attribute :street, String
|
6
|
+
attribute :city, String
|
7
|
+
attribute :phone, String
|
8
|
+
attribute :phone, String
|
9
|
+
attribute :zip, Integer
|
10
|
+
|
11
|
+
attribute :country_id, Integer
|
12
|
+
|
13
|
+
validates :first_name,
|
14
|
+
:last_name,
|
15
|
+
:street,
|
16
|
+
:city,
|
17
|
+
:zip,
|
18
|
+
:phone,
|
19
|
+
:country_id,
|
20
|
+
presence: true
|
21
|
+
|
22
|
+
validates :zip, numericality: true
|
23
|
+
validates :phone, phone: true
|
24
|
+
|
25
|
+
validate :check_country
|
26
|
+
|
27
|
+
def check_country
|
28
|
+
return if Shopper::Country.exists?(country_id)
|
29
|
+
errors.add(:country_id, 'don\' exitst')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|