diff --git a/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee index 8a4f187773..3397077a00 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee @@ -22,6 +22,6 @@ Darkswarm.controller "CheckoutCtrl", ($scope, localStorageService, Checkout, Cur event.preventDefault() $scope.submitted = true if form.$valid - $scope.Checkout.submit() + $scope.Checkout.purchase() else $scope.$broadcast 'purchaseFormInvalid', form diff --git a/app/assets/javascripts/darkswarm/services/checkout.js.coffee b/app/assets/javascripts/darkswarm/services/checkout.js.coffee index 8f43b783a2..8df1caf754 100644 --- a/app/assets/javascripts/darkswarm/services/checkout.js.coffee +++ b/app/assets/javascripts/darkswarm/services/checkout.js.coffee @@ -1,10 +1,16 @@ -Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $http, Navigation, CurrentHub, RailsFlashLoader, Loading)-> +Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeJS, PaymentMethods, $http, Navigation, CurrentHub, RailsFlashLoader, Loading)-> new class Checkout errors: {} secrets: {} order: CurrentOrder.order - submit: -> + purchase: -> + if @paymentMethod()?.method_type == 'stripe' + StripeJS.requestToken(@secrets, @submit) + else + @submit() + + submit: => Loading.message = t 'submitting_order' $http.put('/checkout', {order: @preprocess()}).success (data, status)=> Navigation.go data.path @@ -53,6 +59,16 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h last_name: @order.bill_address.lastname } + if @paymentMethod()?.method_type == 'stripe' + angular.extend munged_order.payments_attributes[0], { + source_attributes: + gateway_payment_profile_id: @secrets.token + cc_type: @secrets.cc_type + last_digits: @secrets.card.last4 + month: @secrets.card.exp_month + year: @secrets.card.exp_year + } + munged_order shippingMethod: -> diff --git a/app/assets/javascripts/darkswarm/services/stripe.js.coffee b/app/assets/javascripts/darkswarm/services/stripe.js.coffee new file mode 100644 index 0000000000..706be74300 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/stripe.js.coffee @@ -0,0 +1,36 @@ +Darkswarm.factory 'StripeJS', ($rootScope, Loading, RailsFlashLoader) -> + new class StripeJS + requestToken: (secrets, submit) -> + Loading.message = "Processing Payment..." + params = + number: secrets.card_number + cvc: secrets.card_verification_value + exp_month: secrets.card_month or 0 + exp_year: secrets.card_year or 0 + + # This is the global Stripe object created by Stripe.js, included in the _stripe partial + Stripe.card.createToken params, (status, response) => + if response.error + $rootScope.$apply -> + Loading.clear() + RailsFlashLoader.loadFlash({error: "Error: #{response.error.message}"}) + else + secrets.token = response['id'] + secrets.cc_type = @mapCC(response.card.brand) + secrets.card = response.card + submit() + + mapCC: (ccType) -> + if ccType == 'MasterCard' + return 'mastercard' + else if ccType == 'Visa' + return 'visa' + else if ccType == 'American Express' + return 'amex' + else if ccType == 'Discover' + return 'discover' + else if ccType == 'Diners Club' + return 'dinersclub' + else if ccType == 'JCB' + return 'jcb' + return diff --git a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee index f3ad508c4d..75d6fd8d36 100644 --- a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee @@ -16,6 +16,7 @@ describe "CheckoutCtrl", -> $provide.value "CurrentHub", CurrentHubMock null Checkout = + purchase: -> submit: -> navigate: -> bindFieldsToLocalStorage: -> @@ -42,17 +43,17 @@ describe "CheckoutCtrl", -> preventDefault: -> beforeEach -> - spyOn(Checkout, "submit") + spyOn(Checkout, "purchase") scope.submitted = false it "delegates to the service when valid", -> scope.purchase(event, {$valid: true}) - expect(Checkout.submit).toHaveBeenCalled() + expect(Checkout.purchase).toHaveBeenCalled() expect(scope.submitted).toBe(true) it "does nothing when invalid", -> scope.purchase(event, {$valid: false}) - expect(Checkout.submit).not.toHaveBeenCalled() + expect(Checkout.purchase).not.toHaveBeenCalled() expect(scope.submitted).toBe(true) it "is enabled", -> diff --git a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee index 78fd10f054..95ee9e8416 100644 --- a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee @@ -15,6 +15,11 @@ describe 'Checkout service', -> id: 123 test: "bar" method_type: "check" + }, + { + id: 666 + test: "qux" + method_type: "stripe" }] shippingMethods = [ { @@ -46,6 +51,7 @@ describe 'Checkout service', -> $provide.value "currentOrder", orderData $provide.value "shippingMethods", shippingMethods $provide.value "paymentMethods", paymentMethods + $provide.value "StripeInstancePublishableKey", "instance_publishable_key" null inject ($injector, _$httpBackend_, $rootScope)-> @@ -83,31 +89,41 @@ describe 'Checkout service', -> Checkout.order.payment_method_id = 99 expect(Checkout.paymentMethod()).toEqual paymentMethods[0] - it "Posts the Checkout to the server", -> - $httpBackend.expectPUT("/checkout", {order: Checkout.preprocess()}).respond 200, {path: "test"} - Checkout.submit() - $httpBackend.flush() - - describe "when there is an error", -> - it "redirects when a redirect is given", -> - $httpBackend.expectPUT("/checkout").respond 400, {path: 'path'} + describe "submitting", -> + it "Posts the Checkout to the server", -> + $httpBackend.expectPUT("/checkout", {order: Checkout.preprocess()}).respond 200, {path: "test"} Checkout.submit() $httpBackend.flush() - expect(Navigation.go).toHaveBeenCalledWith 'path' - it "sends flash messages to the flash service", -> - spyOn(FlashLoaderMock, "loadFlash") # Stubbing out writes to window.location - $httpBackend.expectPUT("/checkout").respond 400, {flash: {error: "frogs"}} - Checkout.submit() + describe "when there is an error", -> + it "redirects when a redirect is given", -> + $httpBackend.expectPUT("/checkout").respond 400, {path: 'path'} + Checkout.submit() + $httpBackend.flush() + expect(Navigation.go).toHaveBeenCalledWith 'path' - $httpBackend.flush() - expect(FlashLoaderMock.loadFlash).toHaveBeenCalledWith {error: "frogs"} + it "sends flash messages to the flash service", -> + spyOn(FlashLoaderMock, "loadFlash") # Stubbing out writes to window.location + $httpBackend.expectPUT("/checkout").respond 400, {flash: {error: "frogs"}} + Checkout.submit() - it "puts errors into the scope", -> - $httpBackend.expectPUT("/checkout").respond 400, {errors: {error: "frogs"}} - Checkout.submit() - $httpBackend.flush() - expect(Checkout.errors).toEqual {error: "frogs"} + $httpBackend.flush() + expect(FlashLoaderMock.loadFlash).toHaveBeenCalledWith {error: "frogs"} + + it "puts errors into the scope", -> + $httpBackend.expectPUT("/checkout").respond 400, {errors: {error: "frogs"}} + Checkout.submit() + $httpBackend.flush() + expect(Checkout.errors).toEqual {error: "frogs"} + + describe "when using the Stripe Connect gateway", -> + beforeEach inject ($injector, StripeJS) -> + Checkout.order.payment_method_id = 666 + + it "requests a Stripe token before submitting", inject (StripeJS) -> + spyOn(StripeJS, "requestToken") + Checkout.purchase() + expect(StripeJS.requestToken).toHaveBeenCalled() describe "data preprocessing", -> beforeEach -> @@ -155,3 +171,23 @@ describe 'Checkout service', -> Checkout.order.payment_method_id = 123 source_attributes = Checkout.preprocess().payments_attributes[0].source_attributes expect(source_attributes).not.toBeDefined() + + describe "when the payment method is the Stripe Connect gateway", -> + beforeEach -> + Checkout.order.payment_method_id = 666 + Checkout.secrets = + token: "stripe_token" + cc_type: "mastercard" + card: + last4: "1234" + exp_year: "2099" + exp_month: "10" + + it "creates source attributes for the submitted card", -> + source_attributes = Checkout.preprocess().payments_attributes[0].source_attributes + expect(source_attributes).toBeDefined() + expect(source_attributes.gateway_payment_profile_id).toBe "stripe_token" + expect(source_attributes.cc_type).toBe "mastercard" + expect(source_attributes.last_digits).toBe "1234" + expect(source_attributes.year).toBe "2099" + expect(source_attributes.month).toBe "10" diff --git a/spec/javascripts/unit/darkswarm/services/stripe_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/stripe_spec.js.coffee new file mode 100644 index 0000000000..1cf883b316 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/stripe_spec.js.coffee @@ -0,0 +1,47 @@ +describe 'StripeJS Service', -> + $httpBackend = StripeJS = null + StripeMock = { card: {} } + + beforeEach -> + module 'Darkswarm' + module ($provide) -> + $provide.value "railsFlash", null + null + + inject (_StripeJS_, _$httpBackend_) -> + $httpBackend = _$httpBackend_ + StripeJS = _StripeJS_ + + describe "requestToken", -> + secrets = {} + submit = null + response = null + + beforeEach inject ($window) -> + $window.Stripe = StripeMock + + describe "with satifactory data", -> + beforeEach -> + submit = jasmine.createSpy() + response = { id: "token", card: { brand: 'MasterCard', last4: "5678", exp_month: 10, exp_year: 2099 } } + StripeMock.card.createToken = (params, callback) => callback(200, response) + + it "saves the response data to secrets, and submits the form", -> + StripeJS.requestToken(secrets, submit) + expect(secrets.token).toEqual "token" + expect(secrets.cc_type).toEqual "mastercard" + expect(submit).toHaveBeenCalled() + + describe "with unsatifactory data", -> + beforeEach -> + submit = jasmine.createSpy() + response = { id: "token", error: { message: 'There was a problem' } } + StripeMock.card.createToken = (params, callback) => callback(400, response) + + it "doesn't submit the form, shows an error message instead", inject (Loading, RailsFlashLoader) -> + spyOn(Loading, "clear") + spyOn(RailsFlashLoader, "loadFlash") + StripeJS.requestToken(secrets, submit) + expect(submit).not.toHaveBeenCalled() + expect(Loading.clear).toHaveBeenCalled() + expect(RailsFlashLoader.loadFlash).toHaveBeenCalledWith({error: "Error: There was a problem"})