From c6d9cbd88d668aae744e1a1fc3ea279607d6df31 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 8 Sep 2016 14:26:21 +0200 Subject: [PATCH 01/56] add integration test for coupon --- test/integration/reservations/create_test.rb | 115 +- ...ng_coupon_retrieve_invoice_from_stripe.yml | 189 +++ ..._machine_and_plan_using_coupon_success.yml | 1334 +++++++++++++++++ 3 files changed, 1624 insertions(+), 14 deletions(-) create mode 100644 test/vcr_cassettes/reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe.yml create mode 100644 test/vcr_cassettes/reservations_machine_and_plan_using_coupon_success.yml diff --git a/test/integration/reservations/create_test.rb b/test/integration/reservations/create_test.rb index 29818a688..f8d28f466 100644 --- a/test/integration/reservations/create_test.rb +++ b/test/integration/reservations/create_test.rb @@ -5,7 +5,7 @@ module Reservations @user_with_subscription = User.with_role(:member).with_subscription.second end - test "user without subscription reserves a machine with success" do + test 'user without subscription reserves a machine with success' do login_as(@user_without_subscription, scope: :user) machine = Machine.find(6) @@ -15,8 +15,9 @@ module Reservations invoice_count = Invoice.count invoice_items_count = InvoiceItem.count users_credit_count = UsersCredit.count + subscriptions_count = Subscription.count - VCR.use_cassette("reservations_create_for_machine_without_subscription_success") do + VCR.use_cassette('reservations_create_for_machine_without_subscription_success') do post reservations_path, { reservation: { user_id: @user_without_subscription.id, reservable_id: machine.id, @@ -37,6 +38,7 @@ module Reservations assert_equal invoice_count + 1, Invoice.count assert_equal invoice_items_count + 1, InvoiceItem.count assert_equal users_credit_count, UsersCredit.count + assert_equal subscriptions_count, Subscription.count # reservation assertions reservation = Reservation.last @@ -65,7 +67,7 @@ module Reservations assert_not_empty Notification.where(attached_object: reservation) end - test "user without subscription reserves a machine with error" do + test 'user without subscription reserves a machine with error' do login_as(@user_without_subscription, scope: :user) machine = Machine.find(6) @@ -76,7 +78,7 @@ module Reservations invoice_items_count = InvoiceItem.count notifications_count = Notification.count - VCR.use_cassette("reservations_create_for_machine_without_subscription_error") do + VCR.use_cassette('reservations_create_for_machine_without_subscription_error') do post reservations_path, { reservation: { user_id: @user_without_subscription.id, reservable_id: machine.id, @@ -99,7 +101,7 @@ module Reservations assert_equal notifications_count, Notification.count end - test "user without subscription reserves a training with success" do + test 'user without subscription reserves a training with success' do login_as(@user_without_subscription, scope: :user) training = Training.first @@ -109,7 +111,7 @@ module Reservations invoice_count = Invoice.count invoice_items_count = InvoiceItem.count - VCR.use_cassette("reservations_create_for_training_without_subscription_success") do + VCR.use_cassette('reservations_create_for_training_without_subscription_success') do post reservations_path, { reservation: { user_id: @user_without_subscription.id, reservable_id: training.id, @@ -157,7 +159,7 @@ module Reservations assert_not_empty Notification.where(attached_object: reservation) end - test "user with subscription reserves a machine with success" do + test 'user with subscription reserves a machine with success' do login_as(@user_with_subscription, scope: :user) plan = @user_with_subscription.subscribed_plan @@ -169,7 +171,7 @@ module Reservations invoice_items_count = InvoiceItem.count users_credit_count = UsersCredit.count - VCR.use_cassette("reservations_create_for_machine_with_subscription_success") do + VCR.use_cassette('reservations_create_for_machine_with_subscription_success') do post reservations_path, { reservation: { user_id: @user_with_subscription.id, reservable_id: machine.id, @@ -230,7 +232,7 @@ module Reservations assert_not_empty Notification.where(attached_object: reservation) end - test "user with subscription reserves the FIRST training with success" do + test 'user with subscription reserves the FIRST training with success' do login_as(@user_with_subscription, scope: :user) plan = @user_with_subscription.subscribed_plan plan.update!(is_rolling: true) @@ -242,7 +244,7 @@ module Reservations invoice_count = Invoice.count invoice_items_count = InvoiceItem.count - VCR.use_cassette("reservations_create_for_training_with_subscription_success") do + VCR.use_cassette('reservations_create_for_training_with_subscription_success') do post reservations_path, { reservation: { user_id: @user_with_subscription.id, reservable_id: training.id, @@ -293,7 +295,7 @@ module Reservations assert_equal reservation.slots.first.start_at + plan.duration, @user_with_subscription.subscription.expired_at end - test "user reserves a machine and pay by wallet with success" do + test 'user reserves a machine and pay by wallet with success' do @vlonchamp = User.find_by(username: 'vlonchamp') login_as(@vlonchamp, scope: :user) @@ -306,7 +308,7 @@ module Reservations users_credit_count = UsersCredit.count wallet_transactions_count = WalletTransaction.count - VCR.use_cassette("reservations_create_for_machine_and_pay_wallet_success") do + VCR.use_cassette('reservations_create_for_machine_and_pay_wallet_success') do post reservations_path, { reservation: { user_id: @vlonchamp.id, reservable_id: machine.id, @@ -364,7 +366,7 @@ module Reservations assert_equal transaction.amount, invoice.wallet_amount / 100.0 end - test "user reserves a training and plan by wallet with success" do + test 'user reserves a training and plan by wallet with success' do @vlonchamp = User.find_by(username: 'vlonchamp') login_as(@vlonchamp, scope: :user) @@ -377,7 +379,7 @@ module Reservations invoice_items_count = InvoiceItem.count wallet_transactions_count = WalletTransaction.count - VCR.use_cassette("reservations_create_for_training_and_plan_by_pay_wallet_success") do + VCR.use_cassette('reservations_create_for_training_and_plan_by_pay_wallet_success') do post reservations_path, { reservation: { user_id: @user_without_subscription.id, reservable_id: training.id, @@ -430,5 +432,90 @@ module Reservations assert_equal transaction.amount, 10 assert_equal transaction.amount, invoice.wallet_amount / 100.0 end + + test 'user reserves a machine and a subscription using a coupon with success' do + login_as(@user_without_subscription, scope: :user) + + machine = Machine.find(6) + plan = Plan.where(group_id: @user_without_subscription.group_id).first + availability = machine.availabilities.first + + reservations_count = Reservation.count + invoice_count = Invoice.count + invoice_items_count = InvoiceItem.count + subscriptions_count = Subscription.count + users_credit_count = UsersCredit.count + + VCR.use_cassette('reservations_machine_and_plan_using_coupon_success') do + post reservations_path, { + reservation: { + user_id: @user_without_subscription.id, + reservable_id: machine.id, + reservable_type: machine.class.name, + card_token: stripe_card_token, + slots_attributes: [ + { start_at: availability.start_at.to_s(:iso8601), + end_at: (availability.start_at + 1.hour).to_s(:iso8601), + availability_id: availability.id + } + ], + plan_id: plan.id + }, + coupon_code: 'SUNNYFABLAB' + }.to_json, default_headers + end + + # general assertions + assert_equal 201, response.status + assert_equal reservations_count + 1, Reservation.count + assert_equal invoice_count + 1, Invoice.count + assert_equal invoice_items_count + 2, InvoiceItem.count + assert_equal users_credit_count, UsersCredit.count + assert_equal subscriptions_count + 1, Subscription.count + + # reservation assertions + reservation = Reservation.last + + assert reservation.invoice + refute reservation.stp_invoice_id.blank? + assert_equal 2, reservation.invoice.invoice_items.count + + # invoice assertions + invoice = reservation.invoice + + refute invoice.stp_invoice_id.blank? + refute invoice.total.blank? + + # invoice_items assertions + ## reservation + reservation_item = invoice.invoice_items.where(subscription_id: nil).first + + assert_not_nil reservation_item + assert reservation_item.stp_invoice_item_id + assert_equal reservation_item.amount, machine.prices.find_by(group_id: @user_without_subscription.group_id, plan_id: plan.id).amount + ## subscription + subscription_item = invoice.invoice_items.where.not(subscription_id: nil).first + + assert_not_nil subscription_item + + subscription = Subscription.find(subscription_item.subscription_id) + + assert subscription_item.stp_invoice_item_id + assert_equal subscription_item.amount, plan.amount + assert_equal subscription.plan_id, plan.id + + # invoice assertions + invoice = Invoice.find_by(invoiced: reservation) + assert_invoice_pdf invoice + + VCR.use_cassette('reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe') do + stp_invoice = Stripe::Invoice.retrieve(invoice.stp_invoice_id) + assert_equal stp_invoice.total, invoice.total + end + + # notifications + assert_not_empty Notification.where(attached_object: reservation) + assert_not_empty Notification.where(attached_object: subscription) + end end end diff --git a/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe.yml b/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe.yml new file mode 100644 index 000000000..561b23ffc --- /dev/null +++ b/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe.yml @@ -0,0 +1,189 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.stripe.com/v1/invoices/in_18rNSq2sOmf47Nz91hxlGSa7 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:25:04 GMT + Content-Type: + - application/json + Content-Length: + - '3362' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99l6aCLd0IC3Qn + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "in_18rNSq2sOmf47Nz91hxlGSa7", + "object": "invoice", + "amount_due": 3825, + "application_fee": null, + "attempt_count": 1, + "attempted": true, + "charge": "ch_18rNSq2sOmf47Nz9Z8EuuyI8", + "closed": true, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473321652, + "description": null, + "discount": null, + "ending_balance": 0, + "forgiven": false, + "lines": { + "object": "list", + "data": [ + { + "id": "ii_18rNSp2sOmf47Nz9S0rJVP2a", + "object": "line_item", + "amount": -450, + "currency": "usd", + "description": "coupon SUNNYFABLAB", + "discountable": false, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321651, + "end": 1473321651 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + }, + { + "id": "ii_18rNSm2sOmf47Nz9R11Svoer", + "object": "line_item", + "amount": -225, + "currency": "usd", + "description": "coupon SUNNYFABLAB", + "discountable": false, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321648, + "end": 1473321648 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + }, + { + "id": "ii_18rNSm2sOmf47Nz9avgL9KyW", + "object": "line_item", + "amount": 1500, + "currency": "usd", + "description": "FORM1+ imprimante 3D September 04, 2016 14:00 - 03:00 PM", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321648, + "end": 1473321648 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + }, + { + "id": "sub_99gqb47NmX9r79", + "object": "line_item", + "amount": 3000, + "currency": "usd", + "description": null, + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321652, + "end": 1475913652 + }, + "plan": { + "id": "mensuel-standard-month-20160404171519", + "object": "plan", + "amount": 3000, + "created": 1459782921, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "name": "Mensuel - standard, association - month", + "statement_descriptor": null, + "trial_period_days": null + }, + "proration": false, + "quantity": 1, + "subscription": null, + "type": "subscription" + } + ], + "has_more": false, + "total_count": 4, + "url": "/v1/invoices/in_18rNSq2sOmf47Nz91hxlGSa7/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "paid": true, + "period_end": 1473321652, + "period_start": 1473321652, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "subscription": "sub_99gqb47NmX9r79", + "subtotal": 3825, + "tax": null, + "tax_percent": null, + "total": 3825, + "webhooks_delivered_at": 1473321652 + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:25:04 GMT +recorded_with: VCR 3.0.1 diff --git a/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_success.yml b/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_success.yml new file mode 100644 index 000000000..6cce6aa21 --- /dev/null +++ b/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_success.yml @@ -0,0 +1,1334 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.stripe.com/v1/tokens + body: + encoding: UTF-8 + string: card[number]=4242424242424242&card[exp_month]=4&card[exp_year]=2017&card[cvc]=314 + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '81' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:47 GMT + Content-Type: + - application/json + Content-Length: + - '779' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqPbGrfRTnyZ + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "tok_18rNSl2sOmf47Nz9cTGhyMRc", + "object": "token", + "card": { + "id": "card_18rNSk2sOmf47Nz9h1cf7obf", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "cvc_check": "unchecked", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + }, + "client_ip": "90.52.44.103", + "created": 1473321647, + "livemode": false, + "type": "card", + "used": false + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:47 GMT +- request: + method: post + uri: https://api.stripe.com/v1/invoiceitems + body: + encoding: UTF-8 + string: customer=cus_8Di1wjdVktv5kt&amount=1500¤cy=usd&description=FORM1%2B+imprimante+3D+September+04%2C+2016+14%3A00+-+03%3A00+PM + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '129' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:48 GMT + Content-Type: + - application/json + Content-Length: + - '473' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqddasRVKxn2 + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "ii_18rNSm2sOmf47Nz9avgL9KyW", + "object": "invoiceitem", + "amount": 1500, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473321648, + "description": "FORM1+ imprimante 3D September 04, 2016 14:00 - 03:00 PM", + "discountable": true, + "invoice": null, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321648, + "end": 1473321648 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:48 GMT +- request: + method: post + uri: https://api.stripe.com/v1/invoiceitems + body: + encoding: UTF-8 + string: customer=cus_8Di1wjdVktv5kt&amount=-225¤cy=usd&description=coupon+SUNNYFABLAB + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '83' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:48 GMT + Content-Type: + - application/json + Content-Length: + - '436' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gq27qOy8Dn3R + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "ii_18rNSm2sOmf47Nz9R11Svoer", + "object": "invoiceitem", + "amount": -225, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473321648, + "description": "coupon SUNNYFABLAB", + "discountable": false, + "invoice": null, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321648, + "end": 1473321648 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:49 GMT +- request: + method: get + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:49 GMT + Content-Type: + - application/json + Content-Length: + - '1408' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqbfntlVYpyJ + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "cus_8Di1wjdVktv5kt", + "object": "customer", + "account_balance": 0, + "created": 1459948888, + "currency": "usd", + "default_source": "card_17z7CT2sOmf47Nz9wtWkhGor", + "delinquent": false, + "description": "Jean Dupond", + "discount": null, + "email": "jean.dupond@gmail.com", + "livemode": false, + "metadata": {}, + "shipping": null, + "sources": { + "object": "list", + "data": [ + { + "id": "card_17z7CT2sOmf47Nz9wtWkhGor", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_8Di1wjdVktv5kt", + "cvc_check": "pass", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/sources" + }, + "subscriptions": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/subscriptions" + } + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:49 GMT +- request: + method: get + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:50 GMT + Content-Type: + - application/json + Content-Length: + - '1408' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqvJz00HL9sk + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "cus_8Di1wjdVktv5kt", + "object": "customer", + "account_balance": 0, + "created": 1459948888, + "currency": "usd", + "default_source": "card_17z7CT2sOmf47Nz9wtWkhGor", + "delinquent": false, + "description": "Jean Dupond", + "discount": null, + "email": "jean.dupond@gmail.com", + "livemode": false, + "metadata": {}, + "shipping": null, + "sources": { + "object": "list", + "data": [ + { + "id": "card_17z7CT2sOmf47Nz9wtWkhGor", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_8Di1wjdVktv5kt", + "cvc_check": "pass", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/sources" + }, + "subscriptions": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/subscriptions" + } + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:50 GMT +- request: + method: post + uri: https://api.stripe.com/v1/invoiceitems + body: + encoding: UTF-8 + string: customer=cus_8Di1wjdVktv5kt&amount=-450¤cy=usd&description=coupon+SUNNYFABLAB + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '83' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:51 GMT + Content-Type: + - application/json + Content-Length: + - '436' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gq9UCtuA1CdL + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "ii_18rNSp2sOmf47Nz9S0rJVP2a", + "object": "invoiceitem", + "amount": -450, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473321651, + "description": "coupon SUNNYFABLAB", + "discountable": false, + "invoice": null, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321651, + "end": 1473321651 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:51 GMT +- request: + method: post + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt/subscriptions + body: + encoding: UTF-8 + string: plan=mensuel-standard-month-20160404171519&source=tok_18rNSl2sOmf47Nz9cTGhyMRc + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '78' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:52 GMT + Content-Type: + - application/json + Content-Length: + - '867' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gq6A5SZpD1qQ + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "sub_99gqb47NmX9r79", + "object": "subscription", + "application_fee_percent": null, + "cancel_at_period_end": false, + "canceled_at": null, + "created": 1473321652, + "current_period_end": 1475913652, + "current_period_start": 1473321652, + "customer": "cus_8Di1wjdVktv5kt", + "discount": null, + "ended_at": null, + "livemode": false, + "metadata": {}, + "plan": { + "id": "mensuel-standard-month-20160404171519", + "object": "plan", + "amount": 3000, + "created": 1459782921, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "name": "Mensuel - standard, association - month", + "statement_descriptor": null, + "trial_period_days": null + }, + "quantity": 1, + "start": 1473321652, + "status": "active", + "tax_percent": null, + "trial_end": null, + "trial_start": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:53 GMT +- request: + method: get + uri: https://api.stripe.com/v1/invoices?customer=cus_8Di1wjdVktv5kt&limit=1 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:53 GMT + Content-Type: + - application/json + Content-Length: + - '3963' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqm0fOzzj3EP + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "object": "list", + "data": [ + { + "id": "in_18rNSq2sOmf47Nz91hxlGSa7", + "object": "invoice", + "amount_due": 3825, + "application_fee": null, + "attempt_count": 1, + "attempted": true, + "charge": "ch_18rNSq2sOmf47Nz9Z8EuuyI8", + "closed": true, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473321652, + "description": null, + "discount": null, + "ending_balance": 0, + "forgiven": false, + "lines": { + "object": "list", + "data": [ + { + "id": "ii_18rNSp2sOmf47Nz9S0rJVP2a", + "object": "line_item", + "amount": -450, + "currency": "usd", + "description": "coupon SUNNYFABLAB", + "discountable": false, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321651, + "end": 1473321651 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + }, + { + "id": "ii_18rNSm2sOmf47Nz9R11Svoer", + "object": "line_item", + "amount": -225, + "currency": "usd", + "description": "coupon SUNNYFABLAB", + "discountable": false, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321648, + "end": 1473321648 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + }, + { + "id": "ii_18rNSm2sOmf47Nz9avgL9KyW", + "object": "line_item", + "amount": 1500, + "currency": "usd", + "description": "FORM1+ imprimante 3D September 04, 2016 14:00 - 03:00 PM", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321648, + "end": 1473321648 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + }, + { + "id": "sub_99gqb47NmX9r79", + "object": "line_item", + "amount": 3000, + "currency": "usd", + "description": null, + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321652, + "end": 1475913652 + }, + "plan": { + "id": "mensuel-standard-month-20160404171519", + "object": "plan", + "amount": 3000, + "created": 1459782921, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "name": "Mensuel - standard, association - month", + "statement_descriptor": null, + "trial_period_days": null + }, + "proration": false, + "quantity": 1, + "subscription": null, + "type": "subscription" + } + ], + "has_more": false, + "total_count": 4, + "url": "/v1/invoices/in_18rNSq2sOmf47Nz91hxlGSa7/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "paid": true, + "period_end": 1473321652, + "period_start": 1473321652, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "subscription": "sub_99gqb47NmX9r79", + "subtotal": 3825, + "tax": null, + "tax_percent": null, + "total": 3825, + "webhooks_delivered_at": 1473321652 + } + ], + "has_more": true, + "url": "/v1/invoices" + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:53 GMT +- request: + method: get + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:54 GMT + Content-Type: + - application/json + Content-Length: + - '2490' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqRCzmQJIDBa + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "cus_8Di1wjdVktv5kt", + "object": "customer", + "account_balance": 0, + "created": 1459948888, + "currency": "usd", + "default_source": "card_18rNSk2sOmf47Nz9h1cf7obf", + "delinquent": false, + "description": "Jean Dupond", + "discount": null, + "email": "jean.dupond@gmail.com", + "livemode": false, + "metadata": {}, + "shipping": null, + "sources": { + "object": "list", + "data": [ + { + "id": "card_18rNSk2sOmf47Nz9h1cf7obf", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_8Di1wjdVktv5kt", + "cvc_check": "pass", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/sources" + }, + "subscriptions": { + "object": "list", + "data": [ + { + "id": "sub_99gqb47NmX9r79", + "object": "subscription", + "application_fee_percent": null, + "cancel_at_period_end": false, + "canceled_at": null, + "created": 1473321652, + "current_period_end": 1475913652, + "current_period_start": 1473321652, + "customer": "cus_8Di1wjdVktv5kt", + "discount": null, + "ended_at": null, + "livemode": false, + "metadata": {}, + "plan": { + "id": "mensuel-standard-month-20160404171519", + "object": "plan", + "amount": 3000, + "created": 1459782921, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "name": "Mensuel - standard, association - month", + "statement_descriptor": null, + "trial_period_days": null + }, + "quantity": 1, + "start": 1473321652, + "status": "active", + "tax_percent": null, + "trial_end": null, + "trial_start": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/subscriptions" + } + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:54 GMT +- request: + method: get + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt/subscriptions/sub_99gqb47NmX9r79 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:55 GMT + Content-Type: + - application/json + Content-Length: + - '867' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqhmTVKcwbNF + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "sub_99gqb47NmX9r79", + "object": "subscription", + "application_fee_percent": null, + "cancel_at_period_end": false, + "canceled_at": null, + "created": 1473321652, + "current_period_end": 1475913652, + "current_period_start": 1473321652, + "customer": "cus_8Di1wjdVktv5kt", + "discount": null, + "ended_at": null, + "livemode": false, + "metadata": {}, + "plan": { + "id": "mensuel-standard-month-20160404171519", + "object": "plan", + "amount": 3000, + "created": 1459782921, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "name": "Mensuel - standard, association - month", + "statement_descriptor": null, + "trial_period_days": null + }, + "quantity": 1, + "start": 1473321652, + "status": "active", + "tax_percent": null, + "trial_end": null, + "trial_start": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:55 GMT +- request: + method: delete + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt/subscriptions/sub_99gqb47NmX9r79?at_period_end=true + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:56 GMT + Content-Type: + - application/json + Content-Length: + - '872' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqPGBMCEM6Ec + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "sub_99gqb47NmX9r79", + "object": "subscription", + "application_fee_percent": null, + "cancel_at_period_end": true, + "canceled_at": 1473321656, + "created": 1473321652, + "current_period_end": 1475913652, + "current_period_start": 1473321652, + "customer": "cus_8Di1wjdVktv5kt", + "discount": null, + "ended_at": null, + "livemode": false, + "metadata": {}, + "plan": { + "id": "mensuel-standard-month-20160404171519", + "object": "plan", + "amount": 3000, + "created": 1459782921, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "name": "Mensuel - standard, association - month", + "statement_descriptor": null, + "trial_period_days": null + }, + "quantity": 1, + "start": 1473321652, + "status": "active", + "tax_percent": null, + "trial_end": null, + "trial_start": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:57 GMT +- request: + method: get + uri: https://api.stripe.com/v1/invoiceitems/ii_18rNSm2sOmf47Nz9avgL9KyW + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:58 GMT + Content-Type: + - application/json + Content-Length: + - '498' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqJIQqEHVCZ0 + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "ii_18rNSm2sOmf47Nz9avgL9KyW", + "object": "invoiceitem", + "amount": 1500, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473321648, + "description": "FORM1+ imprimante 3D September 04, 2016 14:00 - 03:00 PM", + "discountable": true, + "invoice": "in_18rNSq2sOmf47Nz91hxlGSa7", + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321648, + "end": 1473321648 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:58 GMT +- request: + method: get + uri: https://api.stripe.com/v1/invoiceitems/ii_18rNSm2sOmf47Nz9avgL9KyW + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 08:00:58 GMT + Content-Type: + - application/json + Content-Length: + - '498' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99gqJk9T35fI7H + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "ii_18rNSm2sOmf47Nz9avgL9KyW", + "object": "invoiceitem", + "amount": 1500, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473321648, + "description": "FORM1+ imprimante 3D September 04, 2016 14:00 - 03:00 PM", + "discountable": true, + "invoice": "in_18rNSq2sOmf47Nz91hxlGSa7", + "livemode": false, + "metadata": {}, + "period": { + "start": 1473321648, + "end": 1473321648 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 08:00:58 GMT +recorded_with: VCR 3.0.1 From d2332974698581ec040dbd9ef4dbfd9b79d1bfb0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 8 Sep 2016 15:19:12 +0200 Subject: [PATCH 02/56] check coupon code server side + integration test for wrong copon --- .../api/reservations_controller.rb | 28 +- app/exceptions/invalid_coupon_error.rb | 3 + app/models/reservation.rb | 28 +- app/models/subscription.rb | 30 +- config/secrets.yml | 4 +- test/integration/reservations/create_test.rb | 37 + ...ons_training_with_expired_coupon_error.yml | 997 ++++++++++++++++++ 7 files changed, 1092 insertions(+), 35 deletions(-) create mode 100644 app/exceptions/invalid_coupon_error.rb create mode 100644 test/vcr_cassettes/reservations_training_with_expired_coupon_error.yml diff --git a/app/controllers/api/reservations_controller.rb b/app/controllers/api/reservations_controller.rb index 2cfa41a1e..65ebfb9ec 100644 --- a/app/controllers/api/reservations_controller.rb +++ b/app/controllers/api/reservations_controller.rb @@ -20,19 +20,23 @@ class API::ReservationsController < API::ApiController end def create - if current_user.is_admin? - @reservation = Reservation.new(reservation_params) - is_reserve = @reservation.save_with_local_payment(coupon_params[:coupon_code]) - else - @reservation = Reservation.new(reservation_params.merge(user_id: current_user.id)) - is_reserve = @reservation.save_with_payment(coupon_params[:coupon_code]) - end - if is_reserve - SubscriptionExtensionAfterReservation.new(@reservation).extend_subscription_if_eligible + begin + if current_user.is_admin? + @reservation = Reservation.new(reservation_params) + is_reserve = @reservation.save_with_local_payment(coupon_params[:coupon_code]) + else + @reservation = Reservation.new(reservation_params.merge(user_id: current_user.id)) + is_reserve = @reservation.save_with_payment(coupon_params[:coupon_code]) + end + if is_reserve + SubscriptionExtensionAfterReservation.new(@reservation).extend_subscription_if_eligible - render :show, status: :created, location: @reservation - else - render json: @reservation.errors, status: :unprocessable_entity + render :show, status: :created, location: @reservation + else + render json: @reservation.errors, status: :unprocessable_entity + end + rescue InvalidCouponError + render json: {coupon_code: 'wrong coupon code or expired'}, status: :unprocessable_entity end end diff --git a/app/exceptions/invalid_coupon_error.rb b/app/exceptions/invalid_coupon_error.rb new file mode 100644 index 000000000..6ed9f0e1a --- /dev/null +++ b/app/exceptions/invalid_coupon_error.rb @@ -0,0 +1,3 @@ +# Raised when using a coupon expired or invalid +class InvalidCouponError < StandardError +end diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 53a78fe4b..13e1dd13d 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -131,14 +131,18 @@ class Reservation < ActiveRecord::Base # === Coupon === unless coupon_code.nil? cp = Coupon.find_by_code(coupon_code) - total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) - unless on_site - invoice_items << Stripe::InvoiceItem.create( - customer: user.stp_customer_id, - amount: -(total * cp.percent_off / 100).to_i, - currency: Rails.application.secrets.stripe_currency, - description: "coupon #{cp.code}" - ) + if not cp.nil? and cp.status(user.id) == 'active' + total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) + unless on_site + invoice_items << Stripe::InvoiceItem.create( + customer: user.stp_customer_id, + amount: -(total * cp.percent_off / 100).to_i, + currency: Rails.application.secrets.stripe_currency, + description: "coupon #{cp.code}" + ) + end + else + raise InvalidCouponError end end @@ -410,8 +414,12 @@ class Reservation < ActiveRecord::Base unless coupon_code.nil? cp = Coupon.find_by_code(coupon_code) - total = total - (total * cp.percent_off / 100.0) - self.invoice.coupon_id = cp.id + if not cp.nil? and cp.status(user.id) == 'active' + total = total - (total * cp.percent_off / 100.0) + self.invoice.coupon_id = cp.id + else + raise InvalidCouponError + end end self.invoice.total = total diff --git a/app/models/subscription.rb b/app/models/subscription.rb index a25f9e70f..e6cbf9ac0 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -36,6 +36,23 @@ class Subscription < ActiveRecord::Base unless coupon_code.nil? cp = Coupon.find_by_code(coupon_code) + if not cp.nil? and cp.status(user.id) == 'active' + total = plan.amount + Stripe::InvoiceItem.create( + customer: user.stp_customer_id, + amount: -(total * cp.percent_off / 100.0).to_i, + currency: Rails.application.secrets.stripe_currency, + description: "coupon #{cp.code}" + ) + else + raise InvalidCouponError + end + end + elsif coupon_code != nil + # this case applies if a subscription was took in addition of a reservation, so we create a second + # stripe coupon to apply the discount on the subscription item for the stripe's invoice. + cp = Coupon.find_by_code(coupon_code) + if not cp.nil? and cp.status(user.id) == 'active' total = plan.amount Stripe::InvoiceItem.create( customer: user.stp_customer_id, @@ -43,18 +60,9 @@ class Subscription < ActiveRecord::Base currency: Rails.application.secrets.stripe_currency, description: "coupon #{cp.code}" ) + else + raise InvalidCouponError end - elsif coupon_code != nil - # this case applies if a subscription was took in addition of a reservation, so we create a second - # stripe coupon to apply the discount on the subscription item for the stripe's invoice. - cp = Coupon.find_by_code(coupon_code) - total = plan.amount - Stripe::InvoiceItem.create( - customer: user.stp_customer_id, - amount: -(total * cp.percent_off / 100.0).to_i, - currency: Rails.application.secrets.stripe_currency, - description: "coupon #{cp.code}" - ) end new_subscription = customer.subscriptions.create(plan: plan.stp_plan_id, source: card_token) diff --git a/config/secrets.yml b/config/secrets.yml index 9a835c283..7cdbff4cf 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -40,8 +40,8 @@ development: test: secret_key_base: 83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30 - stripe_api_key: <%= ENV["STRIPE_API_KEY"] %> - stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %> + stripe_api_key: sk_test_mGokO9TGtrVxMOyK4yZiktBE + stripe_publishable_key: pk_test_aScrMu3y4AocfCN5XLJjGzmQ stripe_currency: usd disqus_shortname: fablab-sleede fablab_without_plans: false diff --git a/test/integration/reservations/create_test.rb b/test/integration/reservations/create_test.rb index f8d28f466..f3eb2a43e 100644 --- a/test/integration/reservations/create_test.rb +++ b/test/integration/reservations/create_test.rb @@ -517,5 +517,42 @@ module Reservations assert_not_empty Notification.where(attached_object: reservation) assert_not_empty Notification.where(attached_object: subscription) end + + test 'user reserves a training with an expired coupon with error' do + login_as(@user_without_subscription, scope: :user) + + training = Training.find(1) + availability = training.availabilities.first + + reservations_count = Reservation.count + invoice_count = Invoice.count + invoice_items_count = InvoiceItem.count + notifications_count = Notification.count + + VCR.use_cassette('reservations_training_with_expired_coupon_error') do + post reservations_path, { + reservation: { + user_id: @user_without_subscription.id, + reservable_id: training.id, + reservable_type: training.class.name, + card_token: stripe_card_token, + slots_attributes: [ + { start_at: availability.start_at.to_s(:iso8601), + end_at: (availability.start_at + 1.hour).to_s(:iso8601), + availability_id: availability.id + } + ], + }, + coupon_code: 'XMAS10' + }.to_json, default_headers + end + + # general assertions + assert_equal 422, response.status + assert_equal reservations_count, Reservation.count + assert_equal invoice_count, Invoice.count + assert_equal invoice_items_count, InvoiceItem.count + assert_equal notifications_count, Notification.count + end end end diff --git a/test/vcr_cassettes/reservations_training_with_expired_coupon_error.yml b/test/vcr_cassettes/reservations_training_with_expired_coupon_error.yml new file mode 100644 index 000000000..72efab379 --- /dev/null +++ b/test/vcr_cassettes/reservations_training_with_expired_coupon_error.yml @@ -0,0 +1,997 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.stripe.com/v1/tokens + body: + encoding: UTF-8 + string: card[number]=4242424242424242&card[exp_month]=4&card[exp_year]=2017&card[cvc]=314 + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '81' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:30 GMT + Content-Type: + - application/json + Content-Length: + - '779' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lRiyf8VYL75c + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "tok_18rRuI2sOmf47Nz9nl5ZXXoF", + "object": "token", + "card": { + "id": "card_18rRuI2sOmf47Nz9TSeEZ96o", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "cvc_check": "unchecked", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + }, + "client_ip": "90.52.44.103", + "created": 1473338730, + "livemode": false, + "type": "card", + "used": false + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:30 GMT +- request: + method: post + uri: https://api.stripe.com/v1/invoiceitems + body: + encoding: UTF-8 + string: customer=cus_8Di1wjdVktv5kt&amount=5100¤cy=usd&description=Formation+Imprimante+3D+September+05%2C+2016+08%3A00+-+09%3A00+AM + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '130' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:31 GMT + Content-Type: + - application/json + Content-Length: + - '476' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lRUe4QZ7kRSv + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "ii_18rRuJ2sOmf47Nz9S7f6qfEz", + "object": "invoiceitem", + "amount": 5100, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473338731, + "description": "Formation Imprimante 3D September 05, 2016 08:00 - 09:00 AM", + "discountable": true, + "invoice": null, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473338731, + "end": 1473338731 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:31 GMT +- request: + method: post + uri: https://api.stripe.com/v1/invoiceitems + body: + encoding: UTF-8 + string: customer=cus_8Di1wjdVktv5kt&amount=-510¤cy=usd&description=coupon+XMAS10 + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '78' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:32 GMT + Content-Type: + - application/json + Content-Length: + - '431' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lR2huyf6IhVY + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "ii_18rRuK2sOmf47Nz9ye4DQ4PM", + "object": "invoiceitem", + "amount": -510, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473338732, + "description": "coupon XMAS10", + "discountable": false, + "invoice": null, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473338732, + "end": 1473338732 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:32 GMT +- request: + method: get + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:33 GMT + Content-Type: + - application/json + Content-Length: + - '2495' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lRlUD2wtvAmA + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "cus_8Di1wjdVktv5kt", + "object": "customer", + "account_balance": 0, + "created": 1459948888, + "currency": "usd", + "default_source": "card_18rNSk2sOmf47Nz9h1cf7obf", + "delinquent": false, + "description": "Jean Dupond", + "discount": null, + "email": "jean.dupond@gmail.com", + "livemode": false, + "metadata": {}, + "shipping": null, + "sources": { + "object": "list", + "data": [ + { + "id": "card_18rNSk2sOmf47Nz9h1cf7obf", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_8Di1wjdVktv5kt", + "cvc_check": "pass", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/sources" + }, + "subscriptions": { + "object": "list", + "data": [ + { + "id": "sub_99gqb47NmX9r79", + "object": "subscription", + "application_fee_percent": null, + "cancel_at_period_end": true, + "canceled_at": 1473321656, + "created": 1473321652, + "current_period_end": 1475913652, + "current_period_start": 1473321652, + "customer": "cus_8Di1wjdVktv5kt", + "discount": null, + "ended_at": null, + "livemode": false, + "metadata": {}, + "plan": { + "id": "mensuel-standard-month-20160404171519", + "object": "plan", + "amount": 3000, + "created": 1459782921, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "name": "Mensuel - standard, association - month", + "statement_descriptor": null, + "trial_period_days": null + }, + "quantity": 1, + "start": 1473321652, + "status": "active", + "tax_percent": null, + "trial_end": null, + "trial_start": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/subscriptions" + } + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:33 GMT +- request: + method: post + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt/sources + body: + encoding: UTF-8 + string: card=tok_18rRuI2sOmf47Nz9nl5ZXXoF + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '33' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:34 GMT + Content-Type: + - application/json + Content-Length: + - '577' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lReB7yNLWrc3 + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "card_18rRuI2sOmf47Nz9TSeEZ96o", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_8Di1wjdVktv5kt", + "cvc_check": "pass", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:34 GMT +- request: + method: post + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt + body: + encoding: UTF-8 + string: default_source=card_18rRuI2sOmf47Nz9TSeEZ96o + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '44' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:35 GMT + Content-Type: + - application/json + Content-Length: + - '3223' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lRU3qBXW4qEB + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "cus_8Di1wjdVktv5kt", + "object": "customer", + "account_balance": 0, + "created": 1459948888, + "currency": "usd", + "default_source": "card_18rRuI2sOmf47Nz9TSeEZ96o", + "delinquent": false, + "description": "Jean Dupond", + "discount": null, + "email": "jean.dupond@gmail.com", + "livemode": false, + "metadata": {}, + "shipping": null, + "sources": { + "object": "list", + "data": [ + { + "id": "card_18rRuI2sOmf47Nz9TSeEZ96o", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_8Di1wjdVktv5kt", + "cvc_check": "pass", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + }, + { + "id": "card_18rNSk2sOmf47Nz9h1cf7obf", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_8Di1wjdVktv5kt", + "cvc_check": "pass", + "dynamic_last4": null, + "exp_month": 4, + "exp_year": 2017, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + } + ], + "has_more": false, + "total_count": 2, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/sources" + }, + "subscriptions": { + "object": "list", + "data": [ + { + "id": "sub_99gqb47NmX9r79", + "object": "subscription", + "application_fee_percent": null, + "cancel_at_period_end": true, + "canceled_at": 1473321656, + "created": 1473321652, + "current_period_end": 1475913652, + "current_period_start": 1473321652, + "customer": "cus_8Di1wjdVktv5kt", + "discount": null, + "ended_at": null, + "livemode": false, + "metadata": {}, + "plan": { + "id": "mensuel-standard-month-20160404171519", + "object": "plan", + "amount": 3000, + "created": 1459782921, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "name": "Mensuel - standard, association - month", + "statement_descriptor": null, + "trial_period_days": null + }, + "quantity": 1, + "start": 1473321652, + "status": "active", + "tax_percent": null, + "trial_end": null, + "trial_start": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/customers/cus_8Di1wjdVktv5kt/subscriptions" + } + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:35 GMT +- request: + method: post + uri: https://api.stripe.com/v1/invoices + body: + encoding: UTF-8 + string: customer=cus_8Di1wjdVktv5kt + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '27' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:36 GMT + Content-Type: + - application/json + Content-Length: + - '1925' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lRjTRT4OH5kv + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "in_18rRuO2sOmf47Nz9qbfxBA0D", + "object": "invoice", + "amount_due": 4590, + "application_fee": null, + "attempt_count": 0, + "attempted": false, + "charge": null, + "closed": false, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473338736, + "description": null, + "discount": null, + "ending_balance": null, + "forgiven": false, + "lines": { + "object": "list", + "data": [ + { + "id": "ii_18rRuK2sOmf47Nz9ye4DQ4PM", + "object": "line_item", + "amount": -510, + "currency": "usd", + "description": "coupon XMAS10", + "discountable": false, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473338732, + "end": 1473338732 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + }, + { + "id": "ii_18rRuJ2sOmf47Nz9S7f6qfEz", + "object": "line_item", + "amount": 5100, + "currency": "usd", + "description": "Formation Imprimante 3D September 05, 2016 08:00 - 09:00 AM", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473338731, + "end": 1473338731 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + } + ], + "has_more": false, + "total_count": 2, + "url": "/v1/invoices/in_18rRuO2sOmf47Nz9qbfxBA0D/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": 1473342336, + "paid": false, + "period_end": 1473338736, + "period_start": 1473321652, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "subscription": null, + "subtotal": 4590, + "tax": null, + "tax_percent": null, + "total": 4590, + "webhooks_delivered_at": null + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:36 GMT +- request: + method: post + uri: https://api.stripe.com/v1/invoices/in_18rRuO2sOmf47Nz9qbfxBA0D/pay + body: + encoding: ASCII-8BIT + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + Content-Length: + - '0' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:37 GMT + Content-Type: + - application/json + Content-Length: + - '1944' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lRH31egNKaff + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "in_18rRuO2sOmf47Nz9qbfxBA0D", + "object": "invoice", + "amount_due": 4590, + "application_fee": null, + "attempt_count": 1, + "attempted": true, + "charge": "ch_18rRuP2sOmf47Nz9V3NRITbR", + "closed": true, + "currency": "usd", + "customer": "cus_8Di1wjdVktv5kt", + "date": 1473338736, + "description": null, + "discount": null, + "ending_balance": 0, + "forgiven": false, + "lines": { + "object": "list", + "data": [ + { + "id": "ii_18rRuK2sOmf47Nz9ye4DQ4PM", + "object": "line_item", + "amount": -510, + "currency": "usd", + "description": "coupon XMAS10", + "discountable": false, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473338732, + "end": 1473338732 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + }, + { + "id": "ii_18rRuJ2sOmf47Nz9S7f6qfEz", + "object": "line_item", + "amount": 5100, + "currency": "usd", + "description": "Formation Imprimante 3D September 05, 2016 08:00 - 09:00 AM", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "start": 1473338731, + "end": 1473338731 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null, + "type": "invoiceitem" + } + ], + "has_more": false, + "total_count": 2, + "url": "/v1/invoices/in_18rRuO2sOmf47Nz9qbfxBA0D/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "paid": true, + "period_end": 1473338736, + "period_start": 1473321652, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "subscription": null, + "subtotal": 4590, + "tax": null, + "tax_percent": null, + "total": 4590, + "webhooks_delivered_at": 1473338736 + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:37 GMT +- request: + method: delete + uri: https://api.stripe.com/v1/customers/cus_8Di1wjdVktv5kt/sources/card_18rRuI2sOmf47Nz9TSeEZ96o + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - "*/*; q=0.5, application/xml" + Accept-Encoding: + - gzip, deflate + User-Agent: + - Stripe/v1 RubyBindings/1.30.2 + Authorization: + - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"1.30.2","lang":"ruby","lang_version":"2.3.0 p0 (2015-12-25)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux + version 4.4.0-36-generic (buildd@lcy01-01) (gcc version 5.4.0 20160609 (Ubuntu + 5.4.0-6ubuntu1~16.04.2) ) #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016","hostname":"sylvain-sleede-pc"}' + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 08 Sep 2016 12:45:38 GMT + Content-Type: + - application/json + Content-Length: + - '63' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_99lRURlSYOVhs3 + Stripe-Version: + - '2015-10-16' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "deleted": true, + "id": "card_18rRuI2sOmf47Nz9TSeEZ96o" + } + http_version: + recorded_at: Thu, 08 Sep 2016 12:45:38 GMT +recorded_with: VCR 3.0.1 From 7592a778ae5e14f95fbd6c61ee9254e922a6bbc9 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 8 Sep 2016 15:51:59 +0200 Subject: [PATCH 03/56] clean cassettes --- config/secrets.yml | 4 +-- ...raining_and_plan_by_pay_wallet_success.yml | 24 ++++++++--------- ...ng_coupon_retrieve_invoice_from_stripe.yml | 2 +- ..._machine_and_plan_using_coupon_success.yml | 26 +++++++++---------- ...ons_training_with_expired_coupon_error.yml | 18 ++++++------- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/config/secrets.yml b/config/secrets.yml index 7cdbff4cf..9a835c283 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -40,8 +40,8 @@ development: test: secret_key_base: 83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30 - stripe_api_key: sk_test_mGokO9TGtrVxMOyK4yZiktBE - stripe_publishable_key: pk_test_aScrMu3y4AocfCN5XLJjGzmQ + stripe_api_key: <%= ENV["STRIPE_API_KEY"] %> + stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %> stripe_currency: usd disqus_shortname: fablab-sleede fablab_without_plans: false diff --git a/test/vcr_cassettes/reservations_create_for_training_and_plan_by_pay_wallet_success.yml b/test/vcr_cassettes/reservations_create_for_training_and_plan_by_pay_wallet_success.yml index 75511d46b..c4fbf5854 100644 --- a/test/vcr_cassettes/reservations_create_for_training_and_plan_by_pay_wallet_success.yml +++ b/test/vcr_cassettes/reservations_create_for_training_and_plan_by_pay_wallet_success.yml @@ -14,7 +14,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -106,7 +106,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -186,7 +186,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -266,7 +266,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -380,7 +380,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -494,7 +494,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -574,7 +574,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -708,7 +708,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -848,7 +848,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -926,7 +926,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -1004,7 +1004,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -1082,7 +1082,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: diff --git a/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe.yml b/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe.yml index 561b23ffc..9a9a8da5e 100644 --- a/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe.yml +++ b/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe.yml @@ -14,7 +14,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: diff --git a/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_success.yml b/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_success.yml index 6cce6aa21..8d24dd2db 100644 --- a/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_success.yml +++ b/test/vcr_cassettes/reservations_machine_and_plan_using_coupon_success.yml @@ -14,7 +14,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -106,7 +106,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -186,7 +186,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -266,7 +266,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -378,7 +378,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -490,7 +490,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -570,7 +570,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -664,7 +664,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -857,7 +857,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -1005,7 +1005,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -1097,7 +1097,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -1189,7 +1189,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -1267,7 +1267,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: diff --git a/test/vcr_cassettes/reservations_training_with_expired_coupon_error.yml b/test/vcr_cassettes/reservations_training_with_expired_coupon_error.yml index 72efab379..6b3432d1e 100644 --- a/test/vcr_cassettes/reservations_training_with_expired_coupon_error.yml +++ b/test/vcr_cassettes/reservations_training_with_expired_coupon_error.yml @@ -14,7 +14,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -106,7 +106,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -186,7 +186,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -266,7 +266,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -414,7 +414,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -498,7 +498,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -673,7 +673,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -810,7 +810,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: @@ -947,7 +947,7 @@ http_interactions: User-Agent: - Stripe/v1 RubyBindings/1.30.2 Authorization: - - Bearer sk_test_mGokO9TGtrVxMOyK4yZiktBE + - Bearer sk_test_testfaketestfaketestfake Content-Type: - application/x-www-form-urlencoded X-Stripe-Client-User-Agent: From 0307972f9c6d445638ffdc2ec6ab481172c48ac8 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 8 Sep 2016 16:58:18 +0200 Subject: [PATCH 04/56] add tests about event price's categories --- test/fixtures/event_price_categories.yml | 14 ++++++++------ test/fixtures/price_categories.yml | 6 ++++-- test/fixtures/tickets.yml | 10 ---------- test/models/event_price_category_test.rb | 8 +++++--- test/models/price_category_test.rb | 14 +++++++++++--- test/models/ticket_test.rb | 8 +++++--- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/test/fixtures/event_price_categories.yml b/test/fixtures/event_price_categories.yml index 402d0ac49..e99967bbe 100644 --- a/test/fixtures/event_price_categories.yml +++ b/test/fixtures/event_price_categories.yml @@ -1,11 +1,13 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - event_id: - price_category_id: - amount: 1 + id: 1 + event_id: 1 + price_category_id: 1 + amount: 1500 two: - event_id: - price_category_id: - amount: 1 + id: 2 + event_id: 2 + price_category_id: 1 + amount: 1700 diff --git a/test/fixtures/price_categories.yml b/test/fixtures/price_categories.yml index 693bc4376..f6380d579 100644 --- a/test/fixtures/price_categories.yml +++ b/test/fixtures/price_categories.yml @@ -1,9 +1,11 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: +youngs: + id: 1 name: "- de 25 ans" conditions: "Tarif réservé aux jeunes de moins de 25 ans" -two: +unemployed: + id: 2 name: "chômeurs" conditions: "Tarif préférentiel pour les demandeurs d'emploi pouvant justifier de leur situation." diff --git a/test/fixtures/tickets.yml b/test/fixtures/tickets.yml index 3146625ab..6fac8be98 100644 --- a/test/fixtures/tickets.yml +++ b/test/fixtures/tickets.yml @@ -1,11 +1 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - reservation_id: - event_price_category_id: - booked: 1 - -two: - reservation_id: - event_price_category_id: - booked: 1 diff --git a/test/models/event_price_category_test.rb b/test/models/event_price_category_test.rb index a4f4c3523..2b1733689 100644 --- a/test/models/event_price_category_test.rb +++ b/test/models/event_price_category_test.rb @@ -1,7 +1,9 @@ require 'test_helper' class EventPriceCategoryTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + test "event price's category cannot be empty" do + epc = EventPriceCategory.new({price_category_id: 1, event_id: 3}) + assert epc.invalid? + assert epc.errors[:amount].present? + end end diff --git a/test/models/price_category_test.rb b/test/models/price_category_test.rb index da8858024..374b6a916 100644 --- a/test/models/price_category_test.rb +++ b/test/models/price_category_test.rb @@ -1,7 +1,15 @@ require 'test_helper' class PriceCategoryTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + test 'price category name must be unique' do + pc = PriceCategory.new({name: '- DE 25 ANS', conditions: 'Tarif préférentiel pour les jeunes'}) + assert pc.invalid? + assert pc.errors[:name].present? + end + + test 'associated price category cannot be destroyed' do + pc = PriceCategory.find(1) + assert_not pc.safe_destroy + assert_not_empty PriceCategory.where(id: 1) + end end diff --git a/test/models/ticket_test.rb b/test/models/ticket_test.rb index a33a1b216..5007c4e81 100644 --- a/test/models/ticket_test.rb +++ b/test/models/ticket_test.rb @@ -1,7 +1,9 @@ require 'test_helper' class TicketTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + test "ticket must have at least 1 seat" do + t = Ticket.new({event_price_category_id: 1, booked: -1}) + assert t.invalid? + assert t.errors[:booked].present? + end end From 0f8f1a14b47080d5b22aee6830b4ae430b6ed0e6 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 8 Sep 2016 17:26:59 +0200 Subject: [PATCH 05/56] add test for event reservation with custom price's category --- test/integration/events_test.rb | 88 +++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/test/integration/events_test.rb b/test/integration/events_test.rb index d53647f0b..f15c69f8d 100644 --- a/test/integration/events_test.rb +++ b/test/integration/events_test.rb @@ -113,4 +113,92 @@ class EventsTest < ActionDispatch::IntegrationTest assert_equal 20, e.nb_total_places, 'Total number of places was not updated' assert_equal 18, e.nb_free_places, 'Number of free places was not updated' end + + test 'create event with custom price and reserve it with success' do + + price_category = PriceCategory.first + + # First, we create a new event + post '/api/events', + { + event: { + title: 'Electronics initiation', + description: 'A workshop about electronics and the abilities to create robots.', + start_date: 1.week.from_now.utc + 2.days, + start_time: 1.week.from_now.utc.change({hour: 18}) + 2.days, + end_date: 1.week.from_now.utc + 2.days, + end_time: 1.week.from_now.utc.change({hour: 22}) + 2.days, + category_id: Category.last.id, + amount: 20, + nb_total_places: 10, + event_price_categories_attributes: [ + { + price_category_id: price_category.id.to_s, + amount: 16.to_s + } + ] + } + }.to_json, + default_headers + + # Check response format & status + assert_equal 201, response.status, response.body + assert_equal Mime::JSON, response.content_type + + # Check the event was created correctly + event = json_response(response.body) + e = Event.where(id: event[:id]).first + assert_not_nil e, 'Event was not created in database' + + # Check the places numbers were set successfully + e = Event.where(id: event[:id]).first + assert_equal 10, e.nb_total_places, 'Total number of places was not updated' + assert_equal 10, e.nb_free_places, 'Number of free places was not updated' + + # Now, let's make a reservation on this event + post '/api/reservations', + { + reservation: { + user_id: User.find_by_username('lseguin').id, + reservable_id: e.id, + reservable_type: 'Event', + nb_reserve_places: 4, + slots_attributes: [ + { + start_at: e.availability.start_at, + end_at: e.availability.end_at, + availability_id: e.availability.id, + offered: false + } + ], + tickets_attributes: [ + { + event_price_category_id: e.event_price_categories.first.id, + booked: 4 + } + ] + } + }.to_json, + default_headers + + # Check response format & status + assert_equal 201, response.status, response.body + assert_equal Mime::JSON, response.content_type + + # Check the reservation match the required event + reservation = json_response(response.body) + r = Reservation.find(reservation[:id]) + + assert_equal e.id, r.reservable_id + assert_equal 'Event', r.reservable_type + + # Check the remaining places were updated successfully + e = Event.where(id: event[:id]).first + assert_equal 2, e.nb_free_places, 'Number of free places was not updated' + + # Check the resulting invoice generation and it has right price + assert_invoice_pdf r.invoice + assert_equal (4 * 20) + (4 * 16), r.invoice.total / 100.0 + + end end From 336158c5adc73a065448c4db491b8d08eddef50a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 12 Sep 2016 12:10:46 +0200 Subject: [PATCH 06/56] [SSO] fix re-mapping of avatar, address and organization --- app/models/user.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index cea52dbad..2985cad0c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -234,7 +234,18 @@ class User < ActiveRecord::Base if parsed[1] == 'user' self[parsed[2].to_sym] elsif parsed[1] == 'profile' - self.profile[parsed[2].to_sym] + case sso_mapping + when 'profile.avatar' + self.profile.user_avatar.remote_attachment_url + when 'profile.address' + self.profile.address.address + when 'profile.organization_name' + self.profile.organization.name + when 'profile.organization_address' + self.profile.organization.address.address + else + self.profile[parsed[2].to_sym] + end end end From 627073f2beef909275ad0c032c3ca29fcd0773ec Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 12 Sep 2016 17:29:44 +0200 Subject: [PATCH 07/56] [SSO] updated user's documentation --- README.md | 14 +- .../users/omniauth_callbacks_controller.rb | 4 +- doc/sso_authentication.md | 207 ++++++++++-------- doc/sso_with_github.md | 52 +++++ 4 files changed, 176 insertions(+), 101 deletions(-) create mode 100644 doc/sso_with_github.md diff --git a/README.md b/README.md index 3c5f452cb..465097cb3 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,9 @@ FabManager is the FabLab management solution. It is web-based, open-source and t 7.2.2. [Applying changes](#i18n-apply) 8. [Open Projects](#open-projects) 9. [Plugins](#plugins) -10. [Known issues](#known-issues) -11. [Related Documentation](#related-documentation) +10. [Single Sign-On](#sso) +11. [Known issues](#known-issues) +12. [Related Documentation](#related-documentation) @@ -619,6 +620,15 @@ To install a plugin, you just have to copy the plugin folder which contains its You can see an example on the [repo of navinum gamification plugin](https://github.com/LaCasemate/navinum-gamification) + +## Single Sign-On + +Fab-manager can be connected to a [Single Sign-On](https://en.wikipedia.org/wiki/Single_sign-on) server which will provide its own authentication for the platform's users. +Currently OAuth 2 is the only supported protocol for SSO authentication. + +For an example of how to use configure a SSO in Fab-manager, please read [sso_with_github.md](doc/sso_with_github.md). +Developers may find informations on how to implement their own authentication protocol in [sso_authentication.md](doc/sso_authentication.md). + ## Known issues diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index d15abe757..f11fe55c9 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -14,7 +14,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController @user.username = generate_unique_username(@user.username) end # If the email is mapped, we check its uniqueness. If the email is already in use, we mark it as duplicate with an - # unique random string because. + # unique random string, because: # - if it is the same user, his email will be filled from the SSO when he merge his accounts # - if it is not the same user, this will prevent the raise of PG::UniqueViolation if active_provider.sso_fields.include?('user.email') and email_exists?(@user.email) @@ -22,7 +22,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController @user.email = "<#{old_mail}>#{Devise.friendly_token}-duplicate" flash[:alert] = t('omniauth.email_already_linked_to_another_account_please_input_your_authentication_code', OLD_MAIL: old_mail) end - else + else # => update of an existing user if username_exists?(@user.username, @user.id) flash[:alert] = t('omniauth.your_username_is_already_linked_to_another_account_unable_to_update_it', USERNAME: @user.username) @user.username = User.find(@user.id).username diff --git a/doc/sso_authentication.md b/doc/sso_authentication.md index 96da056d7..a0f3fbbef 100644 --- a/doc/sso_authentication.md +++ b/doc/sso_authentication.md @@ -1,95 +1,105 @@ -# How to add an authentication method to the FabLab ? +# How to add an authentication method to the Fab-Manager ? -First, take a look at the [OmniAuth list of strategies](https://github.com/intridea/omniauth/wiki/List-of-Strategies) - for the Strategy or Developer Strategy you want to add to the FabLab. +First, take a look at the [OmniAuth list of strategies](https://github.com/intridea/omniauth/wiki/List-of-Strategies) for the Strategy or Developer Strategy you want to add to the Fab-Manager. For this guide, we will consider you want to add a generic *developer strategy*, like LDAP. Create the OmniAuth implementation ( **lib/omni_auth/strategies/ldap_provider.rb** ) - # first require the OmniAuth gem you added to your Gemfile (see the link above for a list of gems) - require 'omniauth-ldap' - module OmniAuth - module Strategies - # in the class name, replace Ldap with the kind of authentication you are implementing - class SsoLdapProvider < OmniAuth::Strategies::LDAP - # implement the logic here, see the gem specific documentation for more details - end - end +```ruby +# first require the OmniAuth gem you added to your Gemfile (see the link above for a list of gems) +require 'omniauth-ldap' +module OmniAuth + module Strategies + # in the class name, replace Ldap with the kind of authentication you are implementing + class SsoLdapProvider < OmniAuth::Strategies::LDAP + # implement the logic here, see the gem specific documentation for more details end + end +end +``` Create the ActiveRecord models ( **from the terminal** ) - # in the models names, replace Ldap with the kind of authentication you are implementing - # replace ldap_fields with the fields you need for implementing LDAP or whatever you are implementing - $ rails g model LdapProvider ...ldap_fields - $ rails g model LdapMapping ldap_provider:belongs_to local_field:string local_model:string ...ldap_fields +```bash +# in the models names, replace Ldap with the kind of authentication you are implementing +# replace ldap_fields with the fields you need for implementing LDAP or whatever you are implementing +rails g model LdapProvider ...ldap_fields +rails g model LdapMapping ldap_provider:belongs_to local_field:string local_model:string ...ldap_fields +``` Complete the Provider Model ( **app/model/ldap_provider.rb** ) - class LdapProvider < ActiveRecord::Base - has_one :auth_provider, as: :providable - has_many :ldap_mappings, dependent: :destroy - accepts_nested_attributes_for :ldap_mappings, allow_destroy: true - - # return here the fields you want to protect from being directly on the FabLab, typically mapped fields - def protected_fields - fields = [] - ldap_mappings.each do |mapping| - fields.push(mapping.local_model+'.'+mapping.local_field) - end - fields - end - - # return here the link the current users will have to follow to edit his profile on the SSO - def profile_url - # you can also create a profile_url field in the Database model - end +```ruby +class LdapProvider < ActiveRecord::Base + has_one :auth_provider, as: :providable + has_many :ldap_mappings, dependent: :destroy + accepts_nested_attributes_for :ldap_mappings, allow_destroy: true + + # return here the fields you want to protect from being directly on the Fab-Manager, typically mapped fields + def protected_fields + fields = [] + ldap_mappings.each do |mapping| + fields.push(mapping.local_model+'.'+mapping.local_field) end - + fields + end + + # return here the link the current users will have to follow to edit his profile on the SSO + def profile_url + # you can also create a profile_url field in the Database model + end +end +``` Whitelist your implementation fields in the controller ( **app/controllers/api/auth_providers_controller.rb** ) - class API::AuthProvidersController < API::ApiController - ... - private - def provider_params - if params['auth_provider']['providable_type'] == DatabaseProvider.name - ... - elsif if params['auth_provider']['providable_type'] == LdapProvider.name - params.require(:auth_provider).permit(:name, :providable_type, providable_attributes: [ - # list here your LdapProvider model's fields, followed by the mappings : - ldap_mappings_attributes: [ - :id, :local_model, :local_field, ... - # add your other customs LdapMapping fields, don't forget the :_destroy symbol if - # you want your admin to be able to remove mappings - ] - ]) - end - end - end - -List the fields to display in the JSON API view ( **app/views/api/auth_providers/show.json.jbuilder** ) - - json.partial! 'api/auth_providers/auth_provider', auth_provider: @provider - - ... - - if @provider.providable_type == LdapProvider.name - json.providable_attributes do - json.extract! @provider.providable, :id, ... # list LdapProvider fields here - json.ldap_mappings_attributes @provider.providable.ldap_mappings do |m| - json.extract! m, :id, :local_model, :local_field, ... # list LdapMapping fields here - end +```ruby +class API::AuthProvidersController < API::ApiController + ... + private + def provider_params + if params['auth_provider']['providable_type'] == DatabaseProvider.name + ... + elsif if params['auth_provider']['providable_type'] == LdapProvider.name + params.require(:auth_provider).permit(:name, :providable_type, providable_attributes: [ + # list here your LdapProvider model's fields, followed by the mappings : + ldap_mappings_attributes: [ + :id, :local_model, :local_field, ... + # add your other customs LdapMapping fields, don't forget the :_destroy symbol if + # you want your admin to be able to remove mappings + ] + ]) end end +end +``` + +List the fields to display in the JSON API view ( **app/views/api/auth_providers/show.json.jbuilder** ) + +```ruby +json.partial! 'api/auth_providers/auth_provider', auth_provider: @provider + +... + +if @provider.providable_type == LdapProvider.name + json.providable_attributes do + json.extract! @provider.providable, :id, ... # list LdapProvider fields here + json.ldap_mappings_attributes @provider.providable.ldap_mappings do |m| + json.extract! m, :id, :local_model, :local_field, ... # list LdapMapping fields here + end + end +end +``` Configure the initializer ( **config/initializers/devise.rb** ) - require_relative '../../lib/omni_auth/omni_auth' - ... - elsif active_provider.providable_type == LdapProvider.name - config.omniauth OmniAuth::Strategies::SsoLdapProvider.name.to_sym, # pass here the required parameters, see the gem documentation for details - end +```ruby +require_relative '../../lib/omni_auth/omni_auth' +... +elsif active_provider.providable_type == LdapProvider.name + config.omniauth OmniAuth::Strategies::SsoLdapProvider.name.to_sym, # pass here the required parameters, see the gem documentation for details +end +``` Finally you have to create an admin interface with AngularJS: @@ -97,34 +107,37 @@ Finally you have to create an admin interface with AngularJS: - **app/assets/templates/admin/authentifications/_ldap_mapping.html.erb** must contains html partial to configure the LdapMappings, see _oauth2_mapping.html.erb for a working example - **app/assets/javascript/controllers/admin/authentifications.coffee** +```coffeescript +## list of supported authentication methods +METHODS = { + ... + 'LdapProvider' : 'LDAP' # add the name of your ActiveRecord model class here as a hash key, associated with a human readable name as a hash value (string) +} - ## list of supported authentication methods - METHODS = { - ... - 'LdapProvider' : 'LDAP' # add the name of your ActiveRecord model class here as a hash key, associated with a human readable name as a hash value (string) - } +Application.Controllers.controller "newAuthentificationController", ... + +$scope.updateProvidable = -> + ... + if $scope.provider.providable_type == 'LdapProvider' + # you may want to do some stuff to initialize your provider here - Application.Controllers.controller "newAuthentificationController", ... - - $scope.updateProvidable = -> - ... - if $scope.provider.providable_type == 'LdapProvider' - # you may want to do some stuff to initialize your provider here - - $scope.registerProvider = -> - ... - # === LdapProvider === - else if $scope.provider.providable_type == 'LdapProvider' - # here you may want to do some data validation - # then: save the settings - AuthProvider.save auth_provider: $scope.provider, (provider) -> - # register was a success, display a message, redirect, etc. - +$scope.registerProvider = -> + ... + # === LdapProvider === + else if $scope.provider.providable_type == 'LdapProvider' + # here you may want to do some data validation + # then: save the settings + AuthProvider.save auth_provider: $scope.provider, (provider) -> + # register was a success, display a message, redirect, etc. +``` + And to include this interface into the existing one ( **app/assets/templates/admin/authentifications/edit.html.erb**) -
- ... - - -
+```html +
+ ... + + +
+``` \ No newline at end of file diff --git a/doc/sso_with_github.md b/doc/sso_with_github.md new file mode 100644 index 000000000..2d9e4d046 --- /dev/null +++ b/doc/sso_with_github.md @@ -0,0 +1,52 @@ +# How to configure Fab-manager to use a Single Sign-On authentication? + +For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as an authentication provider, because it has a standard implementation of the protocol and it is free to use. + +- First, you must have a GitHub account. This is free, so create one if you don't have any. + Visit https://github.com/join?source=login to create an account. + +- Secondly, you will need to register your fab-manager instance as an application in GitHub. + Visit https://github.com/settings/applications/new to register your instance. + - In `Homepage URL`, put the public URL where your fab-manager's instance is located (eg. https://example.com). + - In `Authorization callback URL`, you must specify an URL that will match this scheme: https://example.com/users/auth/oauth2-github/callback (replace my-fablab.example.com with your own fab-manager's address). + +- You'll be redirected to a page displaying to important informations: your **Client ID** and your **Client Secret**. + +- Now go to your fab-manager's instance, login as an administrator, go to `Users management` and `Authentication`. + Click `Add a new authentication provider`, and select _OAuth 2.0_ in the `Authentication type` drop-down list. + As a name, you can set whatever you want but, you must be aware that: + 1. You will need to type this name in a terminal to activate the provider, so prefer avoiding chars that must be escaped. + 2. This name will be occasionally displayed to end users, so prefer sweet names. + +- Fulfill the form with the following parameters: + - **Common URL**: `https://github.com/login/oauth` This is the common part in the URL of the two following parameters. + - **Authorization endpoint**: `/authorize` This URL can be found [here](https://developer.github.com/v3/oauth/). + - **Token Acquisition Endpoint**: `/access_token` This URL can be found [here](https://developer.github.com/v3/oauth/). + - **Profile edition URL**: `https://github.com/settings/profile` This is the URL where you are directed when you click on `Edit profile` in your GitHub dashboard. + - **Client identifier**: Your Client ID, collected just before. + - **Client secret**: Your Client Secret, collected just before. + +- Then you will need to define the matching of the fields between the data used in fab-manager and the data that the external SSO can provide. + Note that the only mandatory field is User.uid. + To continue with our GitHub example, you will need to look at [this documentation page](https://developer.github.com/v3/users/#get-the-authenticated-user) to know witch field can be mapped and how and [this one](https://developer.github.com/v3/) to know the root URL of the API. + - **Model**: `User` + - **Field**: `uid` + - **API endpoint URL**: `https://api.github.com/user` Here you can set a complete URL **OR** only an endpoint referring to the previously set **Common URL**. + - **API type**: `JSON` Only JSON API are currently supported + - **API fields**: `id` According to the GitHub API documentation, this is the name of the JSON field which uniquely identify the user. + You are free to map more fields, like `Profile.github` to `html_url`, or `Profile.avatar` to `avatar_url`... + +- Once you are done, your newly created authentication provider, will be marked as **Pending** in the authentication providers list. + To set it as the current active provider, you must open a terminal on the hosting server (and/or container) and run the following commands: + +```bash +# replace GitHub with the name of the provider you just created +rake fablab:switch_auth_provider[GitHub] +``` + +- As the command just prompted you, you have to re-compile the assets (with eg, `rake tmp:clear` - this vary with the method you used to deploy your instance) +- Then restart the web-server or the container. +- Finally, to notify all existing users about the changement and send them their migration code/link, run: +```bash +rake fablab:notify_auth_changed +``` From 2d2a5812eb4dbdd469fee785a4263b1a29371d40 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 12 Sep 2016 17:55:07 +0200 Subject: [PATCH 08/56] fix typos --- doc/sso_authentication.md | 9 +++++---- doc/sso_with_github.md | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/doc/sso_authentication.md b/doc/sso_authentication.md index a0f3fbbef..62e6da0ce 100644 --- a/doc/sso_authentication.md +++ b/doc/sso_authentication.md @@ -36,7 +36,7 @@ class LdapProvider < ActiveRecord::Base has_many :ldap_mappings, dependent: :destroy accepts_nested_attributes_for :ldap_mappings, allow_destroy: true - # return here the fields you want to protect from being directly on the Fab-Manager, typically mapped fields + # return the fields you want to protect from being directly managed by the Fab-Manager, typically mapped fields def protected_fields fields = [] ldap_mappings.each do |mapping| @@ -45,13 +45,13 @@ class LdapProvider < ActiveRecord::Base fields end - # return here the link the current users will have to follow to edit his profile on the SSO + # return the link, that the current user will have to follow, to edit his profile on the SSO def profile_url # you can also create a profile_url field in the Database model end end ``` -Whitelist your implementation fields in the controller ( **app/controllers/api/auth_providers_controller.rb** ) +Whitelist your implementation's fields in the controller ( **app/controllers/api/auth_providers_controller.rb** ) ```ruby class API::AuthProvidersController < API::ApiController @@ -140,4 +140,5 @@ And to include this interface into the existing one ( **app/assets/templates/adm ``` - \ No newline at end of file + +Do not forget that you can find examples and inspiration in the OAuth 2.0 implementation. \ No newline at end of file diff --git a/doc/sso_with_github.md b/doc/sso_with_github.md index 2d9e4d046..d30ef1986 100644 --- a/doc/sso_with_github.md +++ b/doc/sso_with_github.md @@ -10,31 +10,32 @@ For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as - In `Homepage URL`, put the public URL where your fab-manager's instance is located (eg. https://example.com). - In `Authorization callback URL`, you must specify an URL that will match this scheme: https://example.com/users/auth/oauth2-github/callback (replace my-fablab.example.com with your own fab-manager's address). -- You'll be redirected to a page displaying to important informations: your **Client ID** and your **Client Secret**. +- You'll be redirected to a page displaying two important informations: your **Client ID** and your **Client Secret**. - Now go to your fab-manager's instance, login as an administrator, go to `Users management` and `Authentication`. Click `Add a new authentication provider`, and select _OAuth 2.0_ in the `Authentication type` drop-down list. - As a name, you can set whatever you want but, you must be aware that: + In `name`, you can set whatever you want, but you must be aware that: 1. You will need to type this name in a terminal to activate the provider, so prefer avoiding chars that must be escaped. - 2. This name will be occasionally displayed to end users, so prefer sweet names. + 2. This name will be occasionally displayed to end users, so prefer sweet and speaking names. - Fulfill the form with the following parameters: - - **Common URL**: `https://github.com/login/oauth` This is the common part in the URL of the two following parameters. + - **Common URL**: `https://github.com/login/oauth` This is the common part in the URLs of the two following parameters. - **Authorization endpoint**: `/authorize` This URL can be found [here](https://developer.github.com/v3/oauth/). - **Token Acquisition Endpoint**: `/access_token` This URL can be found [here](https://developer.github.com/v3/oauth/). - **Profile edition URL**: `https://github.com/settings/profile` This is the URL where you are directed when you click on `Edit profile` in your GitHub dashboard. - **Client identifier**: Your Client ID, collected just before. - **Client secret**: Your Client Secret, collected just before. -- Then you will need to define the matching of the fields between the data used in fab-manager and the data that the external SSO can provide. - Note that the only mandatory field is User.uid. - To continue with our GitHub example, you will need to look at [this documentation page](https://developer.github.com/v3/users/#get-the-authenticated-user) to know witch field can be mapped and how and [this one](https://developer.github.com/v3/) to know the root URL of the API. +- Then you will need to define the matching of the fields between the fab-manager and what the external SSO can provide. + Please note that the only mandatory field is `User.uid`. + To continue with our GitHub example, you will need to look at [this documentation page](https://developer.github.com/v3/users/#get-the-authenticated-user) to know witch field can be mapped and how, and [this one](https://developer.github.com/v3/) to know the root URL of the API. - **Model**: `User` - **Field**: `uid` - **API endpoint URL**: `https://api.github.com/user` Here you can set a complete URL **OR** only an endpoint referring to the previously set **Common URL**. - **API type**: `JSON` Only JSON API are currently supported - **API fields**: `id` According to the GitHub API documentation, this is the name of the JSON field which uniquely identify the user. - You are free to map more fields, like `Profile.github` to `html_url`, or `Profile.avatar` to `avatar_url`... + + Now, you are free to map more fields, like `Profile.github` to `html_url`, or `Profile.avatar` to `avatar_url`... - Once you are done, your newly created authentication provider, will be marked as **Pending** in the authentication providers list. To set it as the current active provider, you must open a terminal on the hosting server (and/or container) and run the following commands: @@ -46,7 +47,7 @@ rake fablab:switch_auth_provider[GitHub] - As the command just prompted you, you have to re-compile the assets (with eg, `rake tmp:clear` - this vary with the method you used to deploy your instance) - Then restart the web-server or the container. -- Finally, to notify all existing users about the changement and send them their migration code/link, run: +- Finally, to notify all existing users about the change (and send them their migration code/link), run: ```bash rake fablab:notify_auth_changed ``` From caa3cd44176832522ba552005605f4550ba1eb56 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 12 Sep 2016 17:57:04 +0200 Subject: [PATCH 09/56] fix typo --- doc/sso_with_github.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sso_with_github.md b/doc/sso_with_github.md index d30ef1986..dcebc0c17 100644 --- a/doc/sso_with_github.md +++ b/doc/sso_with_github.md @@ -8,7 +8,7 @@ For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as - Secondly, you will need to register your fab-manager instance as an application in GitHub. Visit https://github.com/settings/applications/new to register your instance. - In `Homepage URL`, put the public URL where your fab-manager's instance is located (eg. https://example.com). - - In `Authorization callback URL`, you must specify an URL that will match this scheme: https://example.com/users/auth/oauth2-github/callback (replace my-fablab.example.com with your own fab-manager's address). + - In `Authorization callback URL`, you must specify an URL that will match this scheme: https://example.com/users/auth/oauth2-github/callback (replace example.com with your own fab-manager's address). - You'll be redirected to a page displaying two important informations: your **Client ID** and your **Client Secret**. From b7c41e89c49fc20279725e8c3daf1fb4c9a07a43 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 13 Sep 2016 10:48:07 +0200 Subject: [PATCH 10/56] fix locale of notify_auth_changed mails --- config/locales/mails.en.yml | 2 +- lib/tasks/fablab.rake | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index 3f62c56d1..e9e2e6071 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -214,7 +214,7 @@ en: is_changing_its_auth_system_and_will_now_use: "is actually changing its user identification system and will use" instead_of: "instead of" consequence_of_the_modification: "Because of this change you won't be able to login to the website with your actual usernames" - to_use_the_platform_thanks_for: "to keep on using the website, please" + to_use_the_platform_thanks_for: "To keep on using the website, please" create_an_account_on: "create an account on" or_use_an_existing_account_clicking_here: "or use an existing account by clicking here" in_case_of_problem_enter_the_following_code: "In case of problem with this link, you can enter the following code at your first connection attempt in order to migrate your actual account into the new authentification system:" diff --git a/lib/tasks/fablab.rake b/lib/tasks/fablab.rake index 6ac805c85..907b61713 100644 --- a/lib/tasks/fablab.rake +++ b/lib/tasks/fablab.rake @@ -215,6 +215,8 @@ namespace :fablab do desc 'notify users that the auth provider has changed' task notify_auth_changed: :environment do + I18n.locale = I18n.default_locale + # notify every users if the provider is not local database provider if AuthProvider.active.providable_type != DatabaseProvider.name User.all.each do |user| From d62803dda5ba94c931e9e1cb3f10b0b5efc6ebdc Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 13 Sep 2016 11:25:07 +0200 Subject: [PATCH 11/56] [doc] fix github oauth endpoints --- doc/sso_with_github.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/sso_with_github.md b/doc/sso_with_github.md index dcebc0c17..77e6ca20f 100644 --- a/doc/sso_with_github.md +++ b/doc/sso_with_github.md @@ -19,9 +19,9 @@ For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as 2. This name will be occasionally displayed to end users, so prefer sweet and speaking names. - Fulfill the form with the following parameters: - - **Common URL**: `https://github.com/login/oauth` This is the common part in the URLs of the two following parameters. - - **Authorization endpoint**: `/authorize` This URL can be found [here](https://developer.github.com/v3/oauth/). - - **Token Acquisition Endpoint**: `/access_token` This URL can be found [here](https://developer.github.com/v3/oauth/). + - **Common URL**: `https://github.com/login/oauth/` This is the common part in the URLs of the two following parameters. + - **Authorization endpoint**: `authorize` This URL can be found [here](https://developer.github.com/v3/oauth/). + - **Token Acquisition Endpoint**: `access_token` This URL can be found [here](https://developer.github.com/v3/oauth/). - **Profile edition URL**: `https://github.com/settings/profile` This is the URL where you are directed when you click on `Edit profile` in your GitHub dashboard. - **Client identifier**: Your Client ID, collected just before. - **Client secret**: Your Client Secret, collected just before. From 6b46c175cacd5b1a3b7c2a13161e0c645355ae5a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 13 Sep 2016 11:30:52 +0200 Subject: [PATCH 12/56] improved sso doc --- doc/sso_with_github.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/sso_with_github.md b/doc/sso_with_github.md index 77e6ca20f..b6f0a42a9 100644 --- a/doc/sso_with_github.md +++ b/doc/sso_with_github.md @@ -7,6 +7,7 @@ For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as - Secondly, you will need to register your fab-manager instance as an application in GitHub. Visit https://github.com/settings/applications/new to register your instance. + - In `Application name`, we advise you to set the same name as your fab-manager's instance title. - In `Homepage URL`, put the public URL where your fab-manager's instance is located (eg. https://example.com). - In `Authorization callback URL`, you must specify an URL that will match this scheme: https://example.com/users/auth/oauth2-github/callback (replace example.com with your own fab-manager's address). From ec0f5970671c63763b77e18325c1cd09cb44954d Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 13 Sep 2016 12:06:25 +0200 Subject: [PATCH 13/56] [sso] display strategy name in providers list --- .../templates/admin/authentications/index.html.erb | 3 +++ .../api/auth_providers/_auth_provider.json.jbuilder | 2 +- config/locales/app.admin.en.yml | 1 + config/locales/app.admin.fr.yml | 1 + doc/sso_with_github.md | 9 +++++++-- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/templates/admin/authentications/index.html.erb b/app/assets/templates/admin/authentications/index.html.erb index d0d9b5313..4b0a75bc5 100644 --- a/app/assets/templates/admin/authentications/index.html.erb +++ b/app/assets/templates/admin/authentications/index.html.erb @@ -14,6 +14,8 @@ {{ 'name' }} + {{ 'strategy_name' }} + {{ 'type' }} {{ 'state' }} @@ -24,6 +26,7 @@ {{ provider.name }} + {{ provider.strategy_name }} {{ getType(provider.providable_type) }} {{ getState(provider.status) }} diff --git a/app/views/api/auth_providers/_auth_provider.json.jbuilder b/app/views/api/auth_providers/_auth_provider.json.jbuilder index 6ff652c0b..70eb85290 100644 --- a/app/views/api/auth_providers/_auth_provider.json.jbuilder +++ b/app/views/api/auth_providers/_auth_provider.json.jbuilder @@ -1 +1 @@ -json.extract! auth_provider, :id, :name, :status, :providable_type \ No newline at end of file +json.extract! auth_provider, :id, :name, :status, :providable_type, :strategy_name \ No newline at end of file diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index f28ab7444..570833c41 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -354,6 +354,7 @@ en: an_error_occurred_and_the_tag_deletion_failed: "An error occurred and the tag deletion failed." search_for_an_authentication_provider: "Search for an authentication provider" add_a_new_authentication_provider: "Add a new authentication provider" + strategy_name: "Strategy's name" state: "State" unknown: "Unknown: " active: "Active" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 061c9abbd..8200c6990 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -354,6 +354,7 @@ fr: an_error_occurred_and_the_tag_deletion_failed: "Une erreur est survenue et l'étiquette n'a pas pu être supprimé." search_for_an_authentication_provider: "Recherchez un fournisseur d'authentification" add_a_new_authentication_provider: "Ajouter un nouveau fournisseur d'authentification" + strategy_name: "Nom de la stratégie" state: "État" unknown: "Inconnu : " active: "Actif" diff --git a/doc/sso_with_github.md b/doc/sso_with_github.md index b6f0a42a9..791293e85 100644 --- a/doc/sso_with_github.md +++ b/doc/sso_with_github.md @@ -1,6 +1,6 @@ # How to configure Fab-manager to use a Single Sign-On authentication? -For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as an authentication provider, because it has a standard implementation of the protocol and it is free to use. +For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as an example authentication provider, because uses OAuth 2.0 which is currently implemented in fab-manager, it has a standard implementation of that protocol and it is free to use for everyone. - First, you must have a GitHub account. This is free, so create one if you don't have any. Visit https://github.com/join?source=login to create an account. @@ -9,7 +9,11 @@ For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as Visit https://github.com/settings/applications/new to register your instance. - In `Application name`, we advise you to set the same name as your fab-manager's instance title. - In `Homepage URL`, put the public URL where your fab-manager's instance is located (eg. https://example.com). - - In `Authorization callback URL`, you must specify an URL that will match this scheme: https://example.com/users/auth/oauth2-github/callback (replace example.com with your own fab-manager's address). + - In `Authorization callback URL`, you must specify an URL that will match this scheme: https://example.com/users/auth/oauth2-github/callback + - **example.com** is your own fab-manager's address + - **oauth2-github** match the provider's "strategy name" in the fab-manager. + It is composed of: **SSO's protocol**, _dash_, **slug of the provider's name**. + If you have a doubt about what it will be, start by creating the authentication provider in your fab-manager (see below), then the strategy's name will be shown in the providers list. - You'll be redirected to a page displaying two important informations: your **Client ID** and your **Client Secret**. @@ -18,6 +22,7 @@ For this guide, we will use [GitHub](https://developer.github.com/v3/oauth/) as In `name`, you can set whatever you want, but you must be aware that: 1. You will need to type this name in a terminal to activate the provider, so prefer avoiding chars that must be escaped. 2. This name will be occasionally displayed to end users, so prefer sweet and speaking names. + 3. The slug of this name is used in the callback URL provided to the SSO server (eg. /users/auth/oauth2-**github**/callback) - Fulfill the form with the following parameters: - **Common URL**: `https://github.com/login/oauth/` This is the common part in the URLs of the two following parameters. From 019cfcadea52899f82f6304aca85be758890cb03 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 13 Sep 2016 12:09:55 +0200 Subject: [PATCH 14/56] [sso] fix endpoint regexp --- app/assets/javascripts/directives/validators.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/directives/validators.coffee b/app/assets/javascripts/directives/validators.coffee index 71f19c80a..871b6e947 100644 --- a/app/assets/javascripts/directives/validators.coffee +++ b/app/assets/javascripts/directives/validators.coffee @@ -18,7 +18,7 @@ Application.Directives.directive 'url', [ -> Application.Directives.directive 'endpoint', [ -> - ENDPOINT_REGEXP = /^\/([-._~:?#\[\]@!$&'()*+,;=%\w]+\/?)*$/ + ENDPOINT_REGEXP = /^\/?([-._~:?#\[\]@!$&'()*+,;=%\w]+\/?)*$/ { require: 'ngModel' link: (scope, element, attributes, ctrl) -> From e3cb5456ed797755ad6c1018210cc45761958530 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 13 Sep 2016 13:05:36 +0200 Subject: [PATCH 15/56] updated CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b96b5402d..cfbf3b2aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,10 +34,14 @@ - Admins can toggle reminders on/off and customize the delay - More file types allowed as project CAD attachements - Project CAD attachements are now checked by MIME type in addition of extension check +- Display strategy's name in SSO providers list +- SSO documentation improved with an usage example - Fix a bug: project drafts are shown on public profiles - Fix a bug: event category disappear when editing the event - Fix a bug: machine name is not shown in plan edition - Fix a bug: machine slots with tags are not displayed correctly on reservation calendar +- Fix a bug: avatar, address and organization details mapping from SSO were broken +- Fix a bug: in SSO configuration some valid endpoints were recognized as erroneous - [TODO DEPLOY] `rake fablab:es_build_availabilities_index` - [TODO DEPLOY] `rake fablab:es_add_event_filters` - [TODO DEPLOY] `rake db:migrate` From 50543b8d09e4c1aba165ac11b4e91a975f1115bd Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 14 Sep 2016 16:41:45 +0200 Subject: [PATCH 16/56] [sso] give informations about the expected data in sso mapping --- ...ions.coffee => authentications.coffee.erb} | 28 +++++++++++++++++-- .../authentications/_data_mapping.html.erb | 15 ++++++++++ .../authentications/_oauth2_mapping.html.erb | 7 +++-- app/models/profile.rb | 12 ++++++++ app/models/user.rb | 11 ++++++++ .../mapping_fields.json.jbuilder | 9 ++---- config/locales/app.shared.en.yml | 2 ++ config/locales/app.shared.fr.yml | 2 ++ 8 files changed, 75 insertions(+), 11 deletions(-) rename app/assets/javascripts/controllers/admin/{authentications.coffee => authentications.coffee.erb} (88%) create mode 100644 app/assets/templates/admin/authentications/_data_mapping.html.erb diff --git a/app/assets/javascripts/controllers/admin/authentications.coffee b/app/assets/javascripts/controllers/admin/authentications.coffee.erb similarity index 88% rename from app/assets/javascripts/controllers/admin/authentications.coffee rename to app/assets/javascripts/controllers/admin/authentications.coffee.erb index 0c6055f21..75f9ad02d 100644 --- a/app/assets/javascripts/controllers/admin/authentications.coffee +++ b/app/assets/javascripts/controllers/admin/authentications.coffee.erb @@ -103,8 +103,8 @@ Application.Controllers.controller "AuthentificationController", ["$scope", "$st ## # Page to add a new authentication provider ## -Application.Controllers.controller "NewAuthenticationController", ["$scope", "$state", "$rootScope", "dialogs", "growl", "mappingFieldsPromise", "authProvidersPromise", "AuthProvider", '_t' -, ($scope, $state, $rootScope, dialogs, growl, mappingFieldsPromise, authProvidersPromise, AuthProvider, _t) -> +Application.Controllers.controller "NewAuthenticationController", ["$scope", "$state", "$rootScope", "$uibModal", "dialogs", "growl", "mappingFieldsPromise", "authProvidersPromise", "AuthProvider", '_t' +, ($scope, $state, $rootScope, $uibModal, dialogs, growl, mappingFieldsPromise, authProvidersPromise, AuthProvider, _t) -> $scope.authMethods = METHODS @@ -178,6 +178,30 @@ Application.Controllers.controller "NewAuthenticationController", ["$scope", "$s $scope.cancel = -> $state.go('app.admin.members') + + + ## + # Open a modal allowing to specify the data mapping for the given field + ## + $scope.defineDataMapping = (mapping) -> + $uibModal.open + templateUrl: '<%= asset_path "admin/authentications/_data_mapping.html" %>' + size: 'md' + resolve: + field: -> mapping + controller: ['$scope', '$uibModalInstance', 'field', ($scope, $uibModalInstance, field) -> + ## parent field + $scope.field = field + + ## close and save the modifications + $scope.ok = -> + console.log('TODO') + $uibModalInstance.close() + + ## do not save the modifications + $scope.cancel = -> + $uibModalInstance.dismiss() + ] ] diff --git a/app/assets/templates/admin/authentications/_data_mapping.html.erb b/app/assets/templates/admin/authentications/_data_mapping.html.erb new file mode 100644 index 000000000..2f5aef2e6 --- /dev/null +++ b/app/assets/templates/admin/authentications/_data_mapping.html.erb @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/app/assets/templates/admin/authentications/_oauth2_mapping.html.erb b/app/assets/templates/admin/authentications/_oauth2_mapping.html.erb index bb16e52f1..4785bdb72 100644 --- a/app/assets/templates/admin/authentications/_oauth2_mapping.html.erb +++ b/app/assets/templates/admin/authentications/_oauth2_mapping.html.erb @@ -16,11 +16,14 @@ {{m.local_model}} - {{m.local_field}} + {{m.local_field[0]}} {{m.api_endpoint}} {{m.api_data_type}} {{m.api_field}} + @@ -38,7 +41,7 @@ diff --git a/app/models/profile.rb b/app/models/profile.rb index aa3097f4c..483fbee71 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -37,4 +37,16 @@ class Profile < ActiveRecord::Base def str_gender gender ? 'male' : 'female' end + + def self.mapping + # we protect some fields as they are designed to be managed by the system and must not be updated externally + blacklist = %w(id user_id created_at updated_at) + # model-relationships must be added manually + additional = [%w(avatar string), %w(address string), %w(organization_name string), %w(organization_address string)] + Profile.column_types + .map{|k,v| [k, v.type.to_s]} + .delete_if { |col| blacklist.include?(col[0]) } + .concat(additional) + end + end diff --git a/app/models/user.rb b/app/models/user.rb index 2985cad0c..af040ab7c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -336,6 +336,17 @@ class User < ActiveRecord::Base end end + def self.mapping + # we protect some fields as they are designed to be managed by the system and must not be updated externally + blacklist = %w(id encrypted_password reset_password_token reset_password_sent_at remember_created_at + sign_in_count current_sign_in_at last_sign_in_at current_sign_in_ip last_sign_in_ip confirmation_token confirmed_at + confirmation_sent_at unconfirmed_email failed_attempts unlock_token locked_at created_at updated_at stp_customer_id slug + provider auth_token merged_at) + User.column_types + .map{|k,v| [k, v.type.to_s]} + .delete_if { |col| blacklist.include?(col[0]) } + end + protected def confirmation_required? false diff --git a/app/views/api/auth_providers/mapping_fields.json.jbuilder b/app/views/api/auth_providers/mapping_fields.json.jbuilder index b2683f523..7eb9615bf 100644 --- a/app/views/api/auth_providers/mapping_fields.json.jbuilder +++ b/app/views/api/auth_providers/mapping_fields.json.jbuilder @@ -1,9 +1,4 @@ -# we protect some fields are they are designed to be managed by the system and must not be updated externally +json.user User.mapping -json.user User.column_names - %w(id encrypted_password reset_password_token reset_password_sent_at remember_created_at -sign_in_count current_sign_in_at last_sign_in_at current_sign_in_ip last_sign_in_ip confirmation_token confirmed_at -confirmation_sent_at unconfirmed_email failed_attempts unlock_token locked_at created_at updated_at stp_customer_id slug -provider auth_token merged_at) - -json.profile Profile.column_names - %w(id user_id created_at updated_at) + %w(avatar address organization_name organization_address) \ No newline at end of file +json.profile Profile.mapping \ No newline at end of file diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 34de8a913..1014fc37c 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -248,6 +248,8 @@ en: provider_name_is_required: "Provider name is required." authentication_type: "Authentication type" authentication_type_is_required: "Authentication type is required." + data_mapping: "Data mapping" + expected_data_type: "Expected data type" oauth2: # edition/creation form of an OAuth2 authentication provider diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index a6c7afc6f..b21ebb954 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -248,6 +248,8 @@ fr: provider_name_is_required: "Le nom du fournisseur est requis." authentication_type: "Type d'authentification" authentication_type_is_required: "Le type d'authentification est requis." + data_mapping: "Correspondance des données" + expected_data_type: "Type de données attendues" oauth2: # formulaire d'édition/création d'un fournisseur d'authentification de type OAuth2 From dc4c4b678f1daa8ce2c628d8036efc5e84f401ea Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 14 Sep 2016 17:13:07 +0200 Subject: [PATCH 17/56] [sso] fix create mapping w/ datatype display --- .../controllers/admin/authentications.coffee.erb | 9 ++++++++- .../admin/authentications/_data_mapping.html.erb | 4 ++-- .../admin/authentications/_oauth2_mapping.html.erb | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/authentications.coffee.erb b/app/assets/javascripts/controllers/admin/authentications.coffee.erb index 75f9ad02d..94555f464 100644 --- a/app/assets/javascripts/controllers/admin/authentications.coffee.erb +++ b/app/assets/javascripts/controllers/admin/authentications.coffee.erb @@ -189,9 +189,16 @@ Application.Controllers.controller "NewAuthenticationController", ["$scope", "$s size: 'md' resolve: field: -> mapping - controller: ['$scope', '$uibModalInstance', 'field', ($scope, $uibModalInstance, field) -> + datatype: -> + for field in $scope.mappingFields[mapping.local_model] + if field[0] == mapping.local_field + return field[1] + + controller: ['$scope', '$uibModalInstance', 'field', 'datatype', ($scope, $uibModalInstance, field, datatype) -> ## parent field $scope.field = field + ## expected data type + $scope.datatype = datatype ## close and save the modifications $scope.ok = -> diff --git a/app/assets/templates/admin/authentications/_data_mapping.html.erb b/app/assets/templates/admin/authentications/_data_mapping.html.erb index 2f5aef2e6..2e6bb6d87 100644 --- a/app/assets/templates/admin/authentications/_data_mapping.html.erb +++ b/app/assets/templates/admin/authentications/_data_mapping.html.erb @@ -1,9 +1,9 @@ + + +
+

{{ 'transformation_rules' }}

+ + + + + +
+ + +
+

{{ 'transformation_rules' }}

+ + +
+
-
- {{ 'i_have_read_and_accept_' | translate }} {{ '_the_general_terms_and_conditions' }} +
+ +
From 64bae54bce18cd1a3c4ffb409a3b35de0ec96f1a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 15 Sep 2016 18:43:27 +0200 Subject: [PATCH 29/56] [Bug] move event reservation is not limited by admin settings (prior-delay & disable) --- CHANGELOG.md | 1 + .../javascripts/controllers/events.coffee.erb | 15 ++++++++++++--- app/assets/javascripts/router.coffee.erb | 3 +++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c284f6de..7d5f74e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ - Fix a bug: avatar, address and organization details mapping from SSO were broken - Fix a bug: in SSO configuration some valid endpoints were recognized as erroneous - Fix a bug: clicking on the text in stripe's payment modal, does not validate the checkbox +- Fix a bug: move event reservation is not limited by admin settings (prior-delay & disable) - [TODO DEPLOY] `rake fablab:es_build_availabilities_index` - [TODO DEPLOY] `rake fablab:es_add_event_filters` - [TODO DEPLOY] `rake db:migrate` diff --git a/app/assets/javascripts/controllers/events.coffee.erb b/app/assets/javascripts/controllers/events.coffee.erb index 4279e7ed4..d8a177c46 100644 --- a/app/assets/javascripts/controllers/events.coffee.erb +++ b/app/assets/javascripts/controllers/events.coffee.erb @@ -132,8 +132,8 @@ Application.Controllers.controller "EventsController", ["$scope", "$state", 'Eve -Application.Controllers.controller "ShowEventController", ["$scope", "$state", "$stateParams", "Event", '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'eventPromise', 'growl', '_t', 'Wallet', 'helpers', 'priceCategoriesPromise', -($scope, $state, $stateParams, Event, $uibModal, Member, Reservation, Price, CustomAsset, eventPromise, growl, _t, Wallet, helpers, priceCategoriesPromise) -> +Application.Controllers.controller "ShowEventController", ["$scope", "$state", "$stateParams", "Event", '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'eventPromise', 'growl', '_t', 'Wallet', 'helpers', 'priceCategoriesPromise', 'settingsPromise', +($scope, $state, $stateParams, Event, $uibModal, Member, Reservation, Price, CustomAsset, eventPromise, growl, _t, Wallet, helpers, priceCategoriesPromise, settingsPromise) -> @@ -166,6 +166,12 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " ## List of price categories for the events $scope.priceCategories = priceCategoriesPromise + ## Global config: is the user authorized to change his bookings slots? + $scope.enableBookingMove = (settingsPromise.booking_move_enable == "true") + + ## Global config: delay in hours before a booking while changing the booking slot is forbidden + $scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay) + ## @@ -374,10 +380,13 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", " # @param reservation {{total_booked_seats:number}} ## $scope.reservationCanModify = (reservation)-> + slotStart = moment(reservation.slots[0].start_at) + now = moment() + isAble = false angular.forEach $scope.event.recurrence_events, (e)-> isAble = true if e.nb_free_places >= reservation.total_booked_seats - isAble + return (isAble and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay) diff --git a/app/assets/javascripts/router.coffee.erb b/app/assets/javascripts/router.coffee.erb index 2ce8f411c..8d5984205 100644 --- a/app/assets/javascripts/router.coffee.erb +++ b/app/assets/javascripts/router.coffee.erb @@ -515,6 +515,9 @@ angular.module('application.router', ['ui.router']). priceCategoriesPromise: ['PriceCategory', (PriceCategory) -> PriceCategory.query().$promise ] + settingsPromise: ['Setting', (Setting)-> + Setting.query(names: "['booking_move_enable', 'booking_move_delay']").$promise + ] translations: [ 'Translations', (Translations) -> Translations.query(['app.public.events_show', 'app.shared.member_select', 'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.wallet', 'app.shared.coupon_input']).$promise From 5d6aa163b52f43e6b0d43d6006915b929cf304c5 Mon Sep 17 00:00:00 2001 From: Peng DU Date: Tue, 20 Sep 2016 11:39:49 +0200 Subject: [PATCH 30/56] change groups cache key name --- app/views/api/groups/index.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/api/groups/index.json.jbuilder b/app/views/api/groups/index.json.jbuilder index fb5c92e77..6a8f3029e 100644 --- a/app/views/api/groups/index.json.jbuilder +++ b/app/views/api/groups/index.json.jbuilder @@ -1,3 +1,3 @@ -json.cache! @groups do +json.cache! ['v1', @groups] do json.partial! 'api/groups/group', collection: @groups, as: :group end From f883f8c344b0397f2c474401f28dc9a0bfe22090 Mon Sep 17 00:00:00 2001 From: Peng DU Date: Tue, 20 Sep 2016 12:02:08 +0200 Subject: [PATCH 31/56] update Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5f74e55..3266588ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog Fab Manager ## next release +- Add a checkbox "I accept to receive informations from the FabLab" on Sign up and Profil of user +- Share project by facebook/twitter - Load translation locales from subdirectories - Add wallet to user, client can pay total/partial reservation or subscription by wallet - Public calendar for show all trainings/machines/events From e5341b0b89067ec8bda7268ba9a9093ad9ca6c9a Mon Sep 17 00:00:00 2001 From: Peng DU Date: Tue, 20 Sep 2016 14:37:35 +0200 Subject: [PATCH 32/56] fix bug: cant subscribe a plan --- app/assets/javascripts/controllers/plans.coffee.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/controllers/plans.coffee.erb b/app/assets/javascripts/controllers/plans.coffee.erb index adc69ee83..8fe90c35d 100644 --- a/app/assets/javascripts/controllers/plans.coffee.erb +++ b/app/assets/javascripts/controllers/plans.coffee.erb @@ -232,7 +232,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop else $scope.attempting = true Subscription.save - coupon_code: coupon.code + coupon_code: (coupon.code if coupon) subscription: plan_id: selectedPlan.id user_id: member.id @@ -305,7 +305,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop $scope.ok = -> $scope.attempting = true Subscription.save - coupon_code: coupon.code + coupon_code: (coupon.code if coupon) subscription: plan_id: selectedPlan.id user_id: member.id From c98a497b211bc8d769c45b52f506aa49f99ac71f Mon Sep 17 00:00:00 2001 From: cyril Date: Tue, 20 Sep 2016 14:45:14 +0200 Subject: [PATCH 33/56] update docker readme --- docker/README.md | 3 ++- docker/env.example | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docker/README.md b/docker/README.md index 588a5e710..ad53b0587 100644 --- a/docker/README.md +++ b/docker/README.md @@ -363,5 +363,6 @@ docker-compose pull fabmanager docker-compose stop fabmanager sudo rm -rf fabmanager/public/assets docker-compose run --rm fabmanager bundle exec rake assets:precompile -docker-compose start fabmanager +docker-compose down +docker-compose up -d ``` diff --git a/docker/env.example b/docker/env.example index f81ef266d..f96460eb4 100644 --- a/docker/env.example +++ b/docker/env.example @@ -16,7 +16,7 @@ DEFAULT_HOST=demo.fab-manager.com DEFAULT_PROTOCOL=http DELIVERY_METHOD=smtp -SMTP_ADDRESS=smtp.mailgun.org +SMTP_ADDRESS=smtp.sendgrid.net SMTP_PORT=587 SMTP_USER_NAME= SMTP_PASSWORD= @@ -44,10 +44,13 @@ TIME_ZONE=Paris WEEK_STARTING_DAY=monday D3_DATE_FORMAT=%d/%m/%y UIB_DATE_FORMAT=dd/MM/yyyy +EXCEL_DATE_FORMAT=dd/mm/yyyy -OPENLAB_APP_SECRET=fSF9jZEWxjHyqjAzzg34jd92 -OPENLAB_APP_ID=xLn9CmryyURNNHZiDRYVRXbv - +OPENLAB_APP_SECRET= +OPENLAB_APP_ID= +OPENLAB_BASE_URI=https://openprojects.fab-manager.com NAVINUM_API_LOGIN: NAVINUM_API_PASSWORD: + +LOG_LEVEL=debug \ No newline at end of file From 802e239d5bf297d70ccae3b60a8f177a4ffb421c Mon Sep 17 00:00:00 2001 From: Peng DU Date: Tue, 20 Sep 2016 16:54:23 +0200 Subject: [PATCH 34/56] calcul total price of reservation with coupon when use wallet --- app/models/reservation.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 13e1dd13d..95c87f762 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -132,6 +132,7 @@ class Reservation < ActiveRecord::Base unless coupon_code.nil? cp = Coupon.find_by_code(coupon_code) if not cp.nil? and cp.status(user.id) == 'active' + @coupon = cp total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) unless on_site invoice_items << Stripe::InvoiceItem.create( @@ -389,6 +390,9 @@ class Reservation < ActiveRecord::Base plan = Plan.find(plan_id) total += plan.amount end + if @coupon + total = (total - (total * @coupon.percent_off / 100.0)).to_i + end wallet_amount = (user.wallet.amount * 100).to_i wallet_amount >= total ? total : wallet_amount From afb026bdc929cb50e9436b7103cab3f74194a8cd Mon Sep 17 00:00:00 2001 From: Peng DU Date: Tue, 20 Sep 2016 17:42:30 +0200 Subject: [PATCH 35/56] clear invoice_item of wallet/group if payment has a error --- app/models/reservation.rb | 2 +- app/models/subscription.rb | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 95c87f762..d17a2b98b 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -139,7 +139,7 @@ class Reservation < ActiveRecord::Base customer: user.stp_customer_id, amount: -(total * cp.percent_off / 100).to_i, currency: Rails.application.secrets.stripe_currency, - description: "coupon #{cp.code}" + description: "coupon #{cp.code} - reservation" ) end else diff --git a/app/models/subscription.rb b/app/models/subscription.rb index e6cbf9ac0..e4c6dd1b7 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -21,12 +21,13 @@ class Subscription < ActiveRecord::Base def save_with_payment(invoice = true, coupon_code = nil) if valid? customer = Stripe::Customer.retrieve(user.stp_customer_id) + invoice_items = [] begin # dont add a wallet invoice item if pay subscription by reservation if invoice @wallet_amount_debit = get_wallet_amount_debit if @wallet_amount_debit != 0 - Stripe::InvoiceItem.create( + invoice_items << Stripe::InvoiceItem.create( customer: user.stp_customer_id, amount: -@wallet_amount_debit, currency: Rails.application.secrets.stripe_currency, @@ -38,7 +39,7 @@ class Subscription < ActiveRecord::Base cp = Coupon.find_by_code(coupon_code) if not cp.nil? and cp.status(user.id) == 'active' total = plan.amount - Stripe::InvoiceItem.create( + invoice_items << Stripe::InvoiceItem.create( customer: user.stp_customer_id, amount: -(total * cp.percent_off / 100.0).to_i, currency: Rails.application.secrets.stripe_currency, @@ -54,11 +55,11 @@ class Subscription < ActiveRecord::Base cp = Coupon.find_by_code(coupon_code) if not cp.nil? and cp.status(user.id) == 'active' total = plan.amount - Stripe::InvoiceItem.create( + invoice_items << Stripe::InvoiceItem.create( customer: user.stp_customer_id, amount: -(total * cp.percent_off / 100.0).to_i, currency: Rails.application.secrets.stripe_currency, - description: "coupon #{cp.code}" + description: "coupon #{cp.code} - subscription" ) else raise InvalidCouponError @@ -89,32 +90,38 @@ class Subscription < ActiveRecord::Base cancel return true rescue Stripe::CardError => card_error + clear_wallet_and_goupon_invoice_items(invoice_items) logger.error card_error errors[:card] << card_error.message return false rescue Stripe::InvalidRequestError => e + clear_wallet_and_goupon_invoice_items(invoice_items) # Invalid parameters were supplied to Stripe's API logger.error e errors[:payment] << e.message return false rescue Stripe::AuthenticationError => e + clear_wallet_and_goupon_invoice_items(invoice_items) # Authentication with Stripe's API failed # (maybe you changed API keys recently) logger.error e errors[:payment] << e.message return false rescue Stripe::APIConnectionError => e + clear_wallet_and_goupon_invoice_items(invoice_items) # Network communication with Stripe failed logger.error e errors[:payment] << e.message return false rescue Stripe::StripeError => e + clear_wallet_and_goupon_invoice_items(invoice_items) # Display a very generic error to the user, and maybe send # yourself an email logger.error e errors[:payment] << e.message return false rescue => e + clear_wallet_and_goupon_invoice_items(invoice_items) # Something else happened, completely unrelated to Stripe logger.error e errors[:payment] << e.message @@ -293,4 +300,20 @@ class Subscription < ActiveRecord::Base return WalletService.new(user: user, wallet: user.wallet).debit(amount, self) end end + + def clear_wallet_and_goupon_invoice_items(invoice_items) + begin + invoice_items.each(&:delete) + rescue Stripe::InvalidRequestError => e + logger.error e + rescue Stripe::AuthenticationError => e + logger.error e + rescue Stripe::APIConnectionError => e + logger.error e + rescue Stripe::StripeError => e + logger.error e + rescue => e + logger.error e + end + end end From cfd92cdc40a96649fa9e2be5263b05f4cc3d0a3b Mon Sep 17 00:00:00 2001 From: Peng DU Date: Tue, 20 Sep 2016 18:39:28 +0200 Subject: [PATCH 36/56] fix bug: code promotion change slot's price original --- app/assets/javascripts/controllers/trainings.coffee.erb | 1 + app/assets/javascripts/directives/coupon.coffee.erb | 6 ++++++ app/assets/templates/machines/reserve.html.erb | 2 +- app/assets/templates/shared/_coupon.html.erb | 4 ++-- app/assets/templates/trainings/reserve.html.erb | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/controllers/trainings.coffee.erb b/app/assets/javascripts/controllers/trainings.coffee.erb index 4f73efe9a..e7e955351 100644 --- a/app/assets/javascripts/controllers/trainings.coffee.erb +++ b/app/assets/javascripts/controllers/trainings.coffee.erb @@ -400,6 +400,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta if $scope.ctrl.member # reserve a training if this training will not be reserved and is not about to move and not is completed if !event.is_reserved && !$scope.slotToModify && !event.is_completed + $scope.coupon.applied = null if event != $scope.selectedTraining $scope.selectedTraining = event $scope.selectedTraining.offered = false diff --git a/app/assets/javascripts/directives/coupon.coffee.erb b/app/assets/javascripts/directives/coupon.coffee.erb index 531116100..2d1aa5812 100644 --- a/app/assets/javascripts/directives/coupon.coffee.erb +++ b/app/assets/javascripts/directives/coupon.coffee.erb @@ -5,6 +5,7 @@ Application.Directives.directive 'coupon', [ 'Coupon', 'growl', '_t', (Coupon, g show: '=' coupon: '=' userId: '@' + hasSelectSlot: '=' templateUrl: '<%= asset_path "shared/_coupon.html" %>' link: ($scope, element, attributes) -> @@ -18,6 +19,11 @@ Application.Directives.directive 'coupon', [ 'Coupon', 'growl', '_t', (Coupon, g # Binding for the code inputed $scope.couponCode = null + $scope.$watch 'hasSelectSlot', (newValue) -> + unless newValue + $scope.coupon = null + $scope.couponCode = null + $scope.code.input = false ## diff --git a/app/assets/templates/machines/reserve.html.erb b/app/assets/templates/machines/reserve.html.erb index f80ffb700..246df7ba5 100644 --- a/app/assets/templates/machines/reserve.html.erb +++ b/app/assets/templates/machines/reserve.html.erb @@ -78,7 +78,7 @@
- +
diff --git a/app/assets/templates/shared/_coupon.html.erb b/app/assets/templates/shared/_coupon.html.erb index d6661868b..7e00a29f5 100644 --- a/app/assets/templates/shared/_coupon.html.erb +++ b/app/assets/templates/shared/_coupon.html.erb @@ -1,5 +1,5 @@
- {{ 'i_have_a_coupon' }} + {{ 'i_have_a_coupon' }}
@@ -18,4 +18,4 @@
-
\ No newline at end of file +
diff --git a/app/assets/templates/trainings/reserve.html.erb b/app/assets/templates/trainings/reserve.html.erb index 5f03078f4..34adf87e6 100644 --- a/app/assets/templates/trainings/reserve.html.erb +++ b/app/assets/templates/trainings/reserve.html.erb @@ -82,7 +82,7 @@ {{ 'remove_this_slot' }}
- +
From 1008a6dd1b11bf2b99cbd072811b7918ed9ace16 Mon Sep 17 00:00:00 2001 From: Peng DU Date: Wed, 21 Sep 2016 13:09:10 +0200 Subject: [PATCH 37/56] fix bug: cant show correct payment modal when pay by wallet and code promo --- app/assets/javascripts/controllers/plans.coffee.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/controllers/plans.coffee.erb b/app/assets/javascripts/controllers/plans.coffee.erb index 8fe90c35d..14c74443e 100644 --- a/app/assets/javascripts/controllers/plans.coffee.erb +++ b/app/assets/javascripts/controllers/plans.coffee.erb @@ -83,7 +83,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop ## $scope.openSubscribePlanModal = -> Wallet.getWalletByUser {user_id: $scope.ctrl.member.id}, (wallet) -> - amountToPay = helpers.getAmountToPay($scope.selectedPlan.amount, wallet.amount) + amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount) if $scope.currentUser.role isnt 'admin' and amountToPay > 0 payByStripe() else From 270b279ffba6ff634efd719c54a4540438bc6b7c Mon Sep 17 00:00:00 2001 From: Peng DU Date: Wed, 21 Sep 2016 16:19:04 +0200 Subject: [PATCH 38/56] user can use a code promo for pay le plan by wallet and carte visa --- .../api/subscriptions_controller.rb | 11 +--- app/models/subscription.rb | 55 +++++++++---------- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb index 017c9b0d3..acddf7497 100644 --- a/app/controllers/api/subscriptions_controller.rb +++ b/app/controllers/api/subscriptions_controller.rb @@ -18,16 +18,9 @@ class API::SubscriptionsController < API::ApiController @subscription.attributes = subscription_params is_subscribe = @subscription.save_with_local_payment(!User.find(subscription_params[:user_id]).invoicing_disabled?, coupon_params[:coupon_code]) else - member = User.find(subscription_params[:user_id]) - plan = Plan.find(subscription_params[:plan_id]) @subscription = Subscription.find_or_initialize_by(user_id: current_user.id) - if valid_card_token?(subscription_params[:card_token]) or (member.wallet.amount >= plan.amount / 100.0) - @subscription.update_column(:expired_at, nil) unless @subscription.new_record? # very important - @subscription.attributes = subscription_params.merge(user_id: current_user.id) - is_subscribe = @subscription.save_with_payment(true, coupon_params[:coupon_code]) - else - is_subscribe = false - end + @subscription.attributes = subscription_params + is_subscribe = @subscription.save_with_payment(true, coupon_params[:coupon_code]) end if is_subscribe render :show, status: :created, location: @subscription diff --git a/app/models/subscription.rb b/app/models/subscription.rb index e4c6dd1b7..afa27177f 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -20,40 +20,14 @@ class Subscription < ActiveRecord::Base # Stripe subscription payment def save_with_payment(invoice = true, coupon_code = nil) if valid? - customer = Stripe::Customer.retrieve(user.stp_customer_id) - invoice_items = [] begin - # dont add a wallet invoice item if pay subscription by reservation - if invoice - @wallet_amount_debit = get_wallet_amount_debit - if @wallet_amount_debit != 0 - invoice_items << Stripe::InvoiceItem.create( - customer: user.stp_customer_id, - amount: -@wallet_amount_debit, - currency: Rails.application.secrets.stripe_currency, - description: "wallet -#{@wallet_amount_debit / 100.0}" - ) - end + customer = Stripe::Customer.retrieve(user.stp_customer_id) + invoice_items = [] - unless coupon_code.nil? - cp = Coupon.find_by_code(coupon_code) - if not cp.nil? and cp.status(user.id) == 'active' - total = plan.amount - invoice_items << Stripe::InvoiceItem.create( - customer: user.stp_customer_id, - amount: -(total * cp.percent_off / 100.0).to_i, - currency: Rails.application.secrets.stripe_currency, - description: "coupon #{cp.code}" - ) - else - raise InvalidCouponError - end - end - elsif coupon_code != nil - # this case applies if a subscription was took in addition of a reservation, so we create a second - # stripe coupon to apply the discount on the subscription item for the stripe's invoice. + unless coupon_code.nil? cp = Coupon.find_by_code(coupon_code) if not cp.nil? and cp.status(user.id) == 'active' + @coupon = cp total = plan.amount invoice_items << Stripe::InvoiceItem.create( customer: user.stp_customer_id, @@ -66,7 +40,25 @@ class Subscription < ActiveRecord::Base end end + # only add a wallet invoice item if pay subscription + # dont add if pay subscription + reservation + if invoice + @wallet_amount_debit = get_wallet_amount_debit + if @wallet_amount_debit != 0 + invoice_items << Stripe::InvoiceItem.create( + customer: user.stp_customer_id, + amount: -@wallet_amount_debit, + currency: Rails.application.secrets.stripe_currency, + description: "wallet -#{@wallet_amount_debit / 100.0}" + ) + end + end + new_subscription = customer.subscriptions.create(plan: plan.stp_plan_id, source: card_token) + # very important to set expired_at to nil that can allow method is_new? to return true + # for send the notification + # TODO: Refactoring + update_column(:expired_at, nil) unless new_record? self.stp_subscription_id = new_subscription.id self.canceled_at = nil self.expired_at = Time.at(new_subscription.current_period_end) @@ -290,6 +282,9 @@ class Subscription < ActiveRecord::Base def get_wallet_amount_debit total = plan.amount + if @coupon + total = (total - (total * @coupon.percent_off / 100.0)).to_i + end wallet_amount = (user.wallet.amount * 100).to_i return wallet_amount >= total ? total : wallet_amount end From 6401b321ef9672d97c2da42be47d361794b12d47 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 22 Sep 2016 16:46:14 +0200 Subject: [PATCH 39/56] [feature] display fab-manager's version, when logged as admin --- .fabmanager-version | 1 + CHANGELOG.md | 5 +++-- .../javascripts/controllers/application.coffee.erb | 10 ++++++++-- app/assets/javascripts/services/version.coffee | 5 +++++ app/assets/stylesheets/app.components.scss | 5 +++++ app/controllers/api/version_controller.rb | 10 ++++++++++ app/policies/version_policy.rb | 5 +++++ app/views/application/index.html.erb | 7 ++++++- config/locales/app.public.en.yml | 3 +++ config/locales/app.public.fr.yml | 2 ++ config/routes.rb | 3 +++ 11 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 .fabmanager-version create mode 100644 app/assets/javascripts/services/version.coffee create mode 100644 app/controllers/api/version_controller.rb create mode 100644 app/policies/version_policy.rb diff --git a/.fabmanager-version b/.fabmanager-version new file mode 100644 index 000000000..a021d2559 --- /dev/null +++ b/.fabmanager-version @@ -0,0 +1 @@ +2.4.0-dev \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3266588ba..95c07f4d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Changelog Fab Manager ## next release -- Add a checkbox "I accept to receive informations from the FabLab" on Sign up and Profil of user -- Share project by facebook/twitter +- Add a checkbox "I accept to receive informations from the FabLab" on Sign-up dialog and user's profile +- Share project with Facebook/Twitter +- Display fab-manager's version in "Powered by" label, when logged as admin - Load translation locales from subdirectories - Add wallet to user, client can pay total/partial reservation or subscription by wallet - Public calendar for show all trainings/machines/events diff --git a/app/assets/javascripts/controllers/application.coffee.erb b/app/assets/javascripts/controllers/application.coffee.erb index fafd0605a..d91cc3722 100644 --- a/app/assets/javascripts/controllers/application.coffee.erb +++ b/app/assets/javascripts/controllers/application.coffee.erb @@ -1,7 +1,7 @@ 'use strict' -Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "$window", "Session", "AuthService", "Auth", "$uibModal", "$state", 'growl', 'Notification', '$interval', "Setting", '_t' -, ($rootScope, $scope, $window, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t) -> +Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "$window", "Session", "AuthService", "Auth", "$uibModal", "$state", 'growl', 'Notification', '$interval', "Setting", '_t', 'Version' +, ($rootScope, $scope, $window, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t, Version) -> @@ -14,6 +14,10 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco ### PUBLIC SCOPE ### + ## Fab-manager's version + $scope.version = + version: '' + ## # Set the current user to the provided value and initialize the session # @param user {Object} Rails/Devise user @@ -207,6 +211,8 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco $scope.setCurrentUser(user) if user.need_completion $state.transitionTo('app.logged.profileCompletion') + if user.role == 'admin' + $scope.version = Version.get() , (error) -> # Authentication failed... $rootScope.toCheckNotifications = false diff --git a/app/assets/javascripts/services/version.coffee b/app/assets/javascripts/services/version.coffee new file mode 100644 index 000000000..dbed564b3 --- /dev/null +++ b/app/assets/javascripts/services/version.coffee @@ -0,0 +1,5 @@ +'use strict' + +Application.Services.factory 'Version', ["$resource", ($resource)-> + $resource "/api/version" +] diff --git a/app/assets/stylesheets/app.components.scss b/app/assets/stylesheets/app.components.scss index 216608a52..1fc857f85 100644 --- a/app/assets/stylesheets/app.components.scss +++ b/app/assets/stylesheets/app.components.scss @@ -499,6 +499,11 @@ padding: 10px; @media only screen and (max-width: 768px) { display: none; } + + .app-version { + margin-right: 10px; + color: #999; + } } .disabling-overlay { diff --git a/app/controllers/api/version_controller.rb b/app/controllers/api/version_controller.rb new file mode 100644 index 000000000..be963ebdb --- /dev/null +++ b/app/controllers/api/version_controller.rb @@ -0,0 +1,10 @@ + +class API::VersionController < API::ApiController + before_action :authenticate_user! + + def show + authorize :version + version = File.read('.fabmanager-version') + render json: {version: version}, status: :ok + end +end \ No newline at end of file diff --git a/app/policies/version_policy.rb b/app/policies/version_policy.rb new file mode 100644 index 000000000..e3f8d38ec --- /dev/null +++ b/app/policies/version_policy.rb @@ -0,0 +1,5 @@ +class VersionPolicy < ApplicationPolicy + def show? + user.is_admin? + end +end diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index 0d1b9d1da..488a7734a 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -101,7 +101,12 @@ -
Powered by Fab Manager
+
+ + + + Powered by Fab Manager +
<%= javascript_include_tag 'application' %> diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index 743ff673e..411ff6589 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -100,6 +100,9 @@ en: your_email_address_is_unknown: "Your e-mail address is unknown." you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "You will receive in a moment, an e-mail with instructions to reset your password." + # Fab-manager's version + version: "Version:" + about: # about page read_the_fablab_policy: "Read the FabLab policy" diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index 000aff18c..10e227a3f 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -100,6 +100,8 @@ fr: your_email_address_is_unknown: "Votre adresse de courriel est inconnue." you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "Vous allez recevoir sous quelques minutes un courriel vous indiquant comment réinitialiser votre mot de passe." + # Fab-manager's version + version: "Version :" about: # page à propos read_the_fablab_policy: "Consulter les règles d'utilisation du Fab Lab" diff --git a/config/routes.rb b/config/routes.rb index 3c7f355e3..66ac9ce89 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -126,6 +126,9 @@ Rails.application.routes.draw do # XLSX exports get 'exports/:id/download' => 'exports#download' post 'exports/status' => 'exports#status' + + # Fab-manager's version + get 'version' => 'version#show' end # open_api From 205c45060adc2240cada97a551c273ee289e5f21 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 22 Sep 2016 17:01:40 +0200 Subject: [PATCH 40/56] update version on user login according to their role --- app/assets/javascripts/controllers/application.coffee.erb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/controllers/application.coffee.erb b/app/assets/javascripts/controllers/application.coffee.erb index d91cc3722..043bfbab6 100644 --- a/app/assets/javascripts/controllers/application.coffee.erb +++ b/app/assets/javascripts/controllers/application.coffee.erb @@ -26,6 +26,11 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco $rootScope.currentUser = user Session.create(user); getNotifications() + # fab-manager's app-version + if user.role == 'admin' + $scope.version = Version.get() + else + $scope.version = {version: ''} ## @@ -209,10 +214,9 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco # try to retrieve any currently logged user Auth.login().then (user) -> $scope.setCurrentUser(user) + # force users to complete their profile if they are not if user.need_completion $state.transitionTo('app.logged.profileCompletion') - if user.role == 'admin' - $scope.version = Version.get() , (error) -> # Authentication failed... $rootScope.toCheckNotifications = false From 81a6b996ee03fbe452eb485e4cf920ed5312dcab Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 22 Sep 2016 17:32:25 +0200 Subject: [PATCH 41/56] fix sso data mmaing: if no mapping were defined for an integer value, set the raw value --- lib/omni_auth/strategies/sso_oauth2_provider.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/omni_auth/strategies/sso_oauth2_provider.rb b/lib/omni_auth/strategies/sso_oauth2_provider.rb index 4ea519c4f..5c5aea1d7 100644 --- a/lib/omni_auth/strategies/sso_oauth2_provider.rb +++ b/lib/omni_auth/strategies/sso_oauth2_provider.rb @@ -65,6 +65,10 @@ module OmniAuth break end end + # if no transformation had set any value, set the raw value + unless @parsed_info[local_sym(mapping)] + @parsed_info[local_sym(mapping)] = raw_info[mapping.api_endpoint.to_sym][mapping.api_field] + end ## BOOLEAN when 'boolean' From 972a5e0a89d7bc9f40806351cc0545804aa79fa5 Mon Sep 17 00:00:00 2001 From: Peng DU Date: Thu, 22 Sep 2016 18:03:33 +0200 Subject: [PATCH 42/56] fix bug: admin cant pay a plan with code promo by wallet --- .../api/subscriptions_controller.rb | 1 - app/models/subscription.rb | 42 ++++++++++++------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb index acddf7497..cd16e617c 100644 --- a/app/controllers/api/subscriptions_controller.rb +++ b/app/controllers/api/subscriptions_controller.rb @@ -14,7 +14,6 @@ class API::SubscriptionsController < API::ApiController else if current_user.is_admin? @subscription = Subscription.find_or_initialize_by(user_id: subscription_params[:user_id]) - @subscription.update_column(:expired_at, nil) unless @subscription.new_record? # very important @subscription.attributes = subscription_params is_subscribe = @subscription.save_with_local_payment(!User.find(subscription_params[:user_id]).invoicing_disabled?, coupon_params[:coupon_code]) else diff --git a/app/models/subscription.rb b/app/models/subscription.rb index afa27177f..76a4fcaeb 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -124,24 +124,31 @@ class Subscription < ActiveRecord::Base def save_with_local_payment(invoice = true, coupon_code = nil) if valid? - @wallet_amount_debit = get_wallet_amount_debit if invoice - + # very important to set expired_at to nil that can allow method is_new? to return true + # for send the notification + # TODO: Refactoring + update_column(:expired_at, nil) unless new_record? self.stp_subscription_id = nil self.canceled_at = nil set_expired_at - save! - UsersCredits::Manager.new(user: self.user).reset_credits if expired_date_changed - if invoice - invoc = generate_invoice(nil, coupon_code) - # debit wallet - wallet_transaction = debit_user_wallet - if wallet_transaction - invoc.wallet_amount = @wallet_amount_debit - invoc.wallet_transaction_id = wallet_transaction.id + if save + UsersCredits::Manager.new(user: self.user).reset_credits if expired_date_changed + if invoice + invoc = generate_invoice(nil, coupon_code) + @wallet_amount_debit = get_wallet_amount_debit + + # debit wallet + wallet_transaction = debit_user_wallet + if wallet_transaction + invoc.wallet_amount = @wallet_amount_debit + invoc.wallet_transaction_id = wallet_transaction.id + end + invoc.save end - invoc.save + return true + else + return false end - return true else return false end @@ -152,9 +159,12 @@ class Subscription < ActiveRecord::Base total = plan.amount unless coupon_code.nil? - coupon = Coupon.find_by_code(coupon_code) - coupon_id = coupon.id - total = plan.amount - (plan.amount * coupon.percent_off / 100.0) + cp = Coupon.find_by_code(coupon_code) + if not cp.nil? and cp.status(user.id) == 'active' + @coupon = cp + coupon_id = cp.id + total = plan.amount - (plan.amount * cp.percent_off / 100.0) + end end invoice = Invoice.new(invoiced_id: id, invoiced_type: 'Subscription', user: user, total: total, stp_invoice_id: stp_invoice_id, coupon_id: coupon_id) From 0f7238bbbaab224514c8e50cb99a1801179d1549 Mon Sep 17 00:00:00 2001 From: Peng DU Date: Fri, 23 Sep 2016 11:46:58 +0200 Subject: [PATCH 43/56] fix wallet amount display error --- app/assets/templates/shared/_wallet_amount_info.html.erb | 8 ++++---- app/assets/templates/stripe/payment_modal.html.erb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/templates/shared/_wallet_amount_info.html.erb b/app/assets/templates/shared/_wallet_amount_info.html.erb index 2eeb7b4e9..84c2d0bd5 100644 --- a/app/assets/templates/shared/_wallet_amount_info.html.erb +++ b/app/assets/templates/shared/_wallet_amount_info.html.erb @@ -1,10 +1,10 @@
-

{{ 'you_have_amount_in_wallet' | translate:{ amount: numberFilter(walletAmount, 2), currency: currencySymbol } }}

+

{{'wallet_pay_reservation' | translate}}

-

{{'credit_amount_for_pay_reservation' | translate:{ amount: numberFilter(amount, 2), currency: currencySymbol } }}

+

-

{{ 'client_have_amount_in_wallet' | translate:{ amount: numberFilter(walletAmount, 2), currency: currencySymbol } }}

+

{{'client_wallet_pay_reservation' | translate}}

-

{{'client_credit_amount_for_pay_reservation' | translate:{ amount: numberFilter(amount, 2), currency: currencySymbol } }}

+

diff --git a/app/assets/templates/stripe/payment_modal.html.erb b/app/assets/templates/stripe/payment_modal.html.erb index 5c0aa11a9..20bebea04 100644 --- a/app/assets/templates/stripe/payment_modal.html.erb +++ b/app/assets/templates/stripe/payment_modal.html.erb @@ -10,8 +10,8 @@
-

{{ 'you_have_amount_in_wallet' | translate:{ amount: numberFilter(walletAmount, 2), currency: currencySymbol } }}

-

{{'credit_amount_for_pay_reservation' | translate:{ amount: numberFilter(amount, 2), currency: currencySymbol } }}

+

+

From fc9aeb00a2ea20e98db28eb32f4609b381cbbf8a Mon Sep 17 00:00:00 2001 From: Peng DU Date: Fri, 23 Sep 2016 12:52:43 +0200 Subject: [PATCH 44/56] remove edit wallet reference from facture --- app/assets/templates/admin/invoices/index.html.erb | 2 +- app/models/invoice.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/templates/admin/invoices/index.html.erb b/app/assets/templates/admin/invoices/index.html.erb index 6ba4bda8a..c4e47ff7d 100644 --- a/app/assets/templates/admin/invoices/index.html.erb +++ b/app/assets/templates/admin/invoices/index.html.erb @@ -210,7 +210,7 @@
  • {{ 'day' | translate }}
  • {{ '#_of_invoice' | translate }}
  • {{ 'online_sales' | translate }}
  • -
  • {{ 'wallet' | translate }}
  • + <%#
  • {{ 'wallet' | translate }}
  • %>
  • {{ 'refund' | translate }}
  • diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 6eb2e7bec..5970283df 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -69,7 +69,7 @@ class Invoice < ActiveRecord::Base end # information about wallet (W[text]) - reference.gsub!(/W\[([^\]]+)\]/, ''.to_s) + #reference.gsub!(/W\[([^\]]+)\]/, ''.to_s) # remove information about refunds (R[text]) reference.gsub!(/R\[([^\]]+)\]/, ''.to_s) From 8a68609b4b942319d092e81977c9a21553de1b19 Mon Sep 17 00:00:00 2001 From: cyril Date: Fri, 23 Sep 2016 14:39:10 +0200 Subject: [PATCH 45/56] update docker/env.example --- docker/env.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/env.example b/docker/env.example index f96460eb4..5498510ce 100644 --- a/docker/env.example +++ b/docker/env.example @@ -31,6 +31,8 @@ TWITTER_CONSUMER_SECRET= TWITTER_ACCESS_TOKEN= TWITTER_ACCESS_TOKEN_SECRET= +FACEBOOK_APP_ID= + RAILS_LOCALE=fr MOMENT_LOCALE=fr SUMMERNOTE_LOCALE=fr-FR From eed8f6519ea6314aee5cdfade9d42d9023b579cd Mon Sep 17 00:00:00 2001 From: cyril Date: Fri, 23 Sep 2016 14:40:41 +0200 Subject: [PATCH 46/56] display caret icon for small screen in profil nav --- app/assets/templates/shared/header.html.erb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/templates/shared/header.html.erb b/app/assets/templates/shared/header.html.erb index 2fae7886f..ed5bfef09 100644 --- a/app/assets/templates/shared/header.html.erb +++ b/app/assets/templates/shared/header.html.erb @@ -27,11 +27,11 @@