Skip to content

Commit

Permalink
Merge pull request #12 from DanielL/user-add-credit
Browse files Browse the repository at this point in the history
User add credit
  • Loading branch information
dlam-blinkbox committed Jan 12, 2015
2 parents bd99806 + 6547d89 commit abdd6e5
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 10 deletions.
63 changes: 53 additions & 10 deletions lib/blinkbox/user.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
require 'blinkbox/user/device'
require 'blinkbox/user/zuul_client'
require 'blinkbox/user/credit_card_service_client'
require 'blinkbox/user/braintree_encode'
require 'yaml'
require 'blinkbox/user/braintree_keys'

module Blinkbox
class User
attr_accessor :username, :password, :grant_type, :first_name, :last_name, :allow_marketing_communications, :accepted_terms_and_conditions

def initialize(params, client = ZuulClient)
def initialize(params, auth_client = ZuulClient, cc_service_client = CreditCardServiceClient)
@grant_type = params[:grant_type] || "password"
@username = params[:username]
@password = params[:password]
Expand All @@ -16,18 +20,20 @@ def initialize(params, client = ZuulClient)
@accepted_terms_and_conditions = params[:accepted_terms_and_conditions] || true
@allow_marketing_communications = params[:allow_marketing_communications] || false

server_uri = params[:server_uri] || "https://auth.dev.bbbtest2.com"
auth_server_uri = params[:server_uri] || "https://auth.dev.bbbtest2.com"
@auth_client = auth_client.new(auth_server_uri, params[:proxy_uri])

@client = client.new(server_uri, params[:proxy_uri])
credit_card_service_uri = params[:credit_card_service_uri] || "https://api.dev.bbbtest2.com"
@cc_service_client = cc_service_client.new(credit_card_service_uri, params[:proxy_uri])
end

def register
@client.register_user(self)
@auth_client.register_user(self)
end

def authenticate
@client.authenticate(user_credentials)
res = @client.last_response(:format => "json")
@auth_client.authenticate(user_credentials)
res = @auth_client.last_response(:format => "json")

res.keys.each do |key|
instance_eval %Q{
Expand All @@ -38,20 +44,20 @@ def authenticate
end

def get_devices
@client.get_clients_info @access_token
@auth_client.get_clients_info @access_token
@device_clients = []
@client.last_response(:format => "json")['clients'].each do |dc|
@auth_client.last_response(:format => "json")['clients'].each do |dc|
@device_clients.push(Device.new(dc))
end
@device_clients
end

def register_device(params)
@client.register_client(params, @access_token)
@auth_client.register_client(params, @access_token)
end

def deregister_device(device)
@client.deregister_client(device.id, @access_token)
@auth_client.deregister_client(device.id, @access_token)
end

def deregister_all_devices
Expand All @@ -60,6 +66,43 @@ def deregister_all_devices
end
end

def add_default_credit_card(opts = {})
# setting up defaults
opts[:braintree_env] ||= ENV['SERVER'] || 'dev_int'
opts[:card_type] ||= 'mastercard'

braintree_public_key = BRAINTREE_KEYS[opts[:braintree_env].to_sym]

card_number_map = {
'mastercard' => '5555555555554444',
'visa' => '4111111111111111',
'amex' => '378282246310005',
'discover' => '6011111111111117',
'jcb' => '3530111333300000'
}
card_number = card_number_map[opts[:card_type]]
raise "Unrecognised card_type: #{opts[:card_type]}. Please use one of #{card_number_map.keys}" if card_number.nil?

cvv = opts[:card_type].eql?('amex') ? '1234' : '123'

@encrypted_card_number ||= BraintreeEncryption.encrypt(card_number, braintree_public_key)
@encrypted_cvv ||= BraintreeEncryption.encrypt(cvv, braintree_public_key)
@encrypted_expiration_month ||= BraintreeEncryption.encrypt('8', braintree_public_key)
@necrypted_expiration_year ||= BraintreeEncryption.encrypt('2020', braintree_public_key)

card_details = {
default: true,
cc_number: @encrypted_card_number,
cvv: @encrypted_cvv,
expiration_month: @encrypted_expiration_month,
expiration_year: @necrypted_expiration_year,
card_holder_name: 'Jimmy Jib',
billing_address: {line1:"48 dollis rd", locality:"London", postcode:"n3 1rd"}
}

@cc_service_client.add_credit_card(@access_token, card_details)
end

private

def user_credentials
Expand Down
33 changes: 33 additions & 0 deletions lib/blinkbox/user/braintree_encode.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require "base64" # gem: rubysl-base64
require "openssl"
require "securerandom"

module BraintreeEncryption
def self.encrypt(value, public_key)
# reverse engineered from https://github.com/braintree/braintree.js/blob/master/lib/braintree.js
# this may need to be updated if braintree change their client-side encryption scripts

return nil if value.nil?
return "" if value.respond_to?(:empty?) && value.empty?

raise "The Braintree client key is not configured" if public_key.nil?
raw_key = Base64.strict_decode64(public_key)
rsa = OpenSSL::PKey::RSA.new(raw_key)

aes = OpenSSL::Cipher::AES256.new(:CBC).encrypt
aes_key, aes_iv = aes.random_key, aes.random_iv
encrypted_value = aes.update(value.to_s) + aes.final
ciphertext = aes_iv + encrypted_value
encoded_ciphertext = Base64.strict_encode64(ciphertext)

hmac_key = SecureRandom.random_bytes(32)
hmac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, hmac_key, ciphertext)
signature = Base64.strict_encode64(hmac)

combined_key = aes_key + hmac_key
encoded_key = Base64.strict_encode64(combined_key)
encrypted_key = Base64.strict_encode64(rsa.public_encrypt(encoded_key))

"$bt4|javascript_1_3_9$#{encrypted_key}$#{encoded_ciphertext}$#{signature}"
end
end
59 changes: 59 additions & 0 deletions lib/blinkbox/user/credit_card_service_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require 'httparty'
require 'multi_json'
require 'net/http/capture'

module Blinkbox
class CreditCardServiceClient
include HTTParty
attr_accessor :headers

def initialize(server_uri, proxy_uri = nil)
self.class.base_uri(server_uri.to_s)
self.class.http_proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password) if proxy_uri
self.class.debug_output($stderr) if ENV['DEBUG']
@headers = {}
end

def use_proxy(proxy_uri)
proxy_uri = URI.parse(proxy_uri) if proxy_uri.is_a?(String)
self.class.http_proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
end

def add_credit_card(access_token, card_details = {})
body = {
default: card_details[:default],
number: card_details[:cc_number],
cvv: card_details[:cvv],
expirationMonth: card_details[:expiration_month],
expirationYear: card_details[:expiration_year],
cardholderName: card_details[:card_holder_name],
billingAddress: card_details[:billing_address]
}

retries_left = 10
card_id = nil
while card_id == nil and retries_left > 0
response = http_post "/service/my/creditcards", body, access_token
card_id = MultiJson.load(response.body)['id']
retries_left -= 1
end

fail 'Adding credit card failed' unless card_id

card_id
end

private

def http_post(uri, body, access_token = nil)
http_send(:post, uri, body, access_token)
end

def http_send(verb, uri, post_body, access_token = nil)
headers = {"Content-Type" => "application/vnd.blinkboxbooks.data.v1+json" }.merge(@headers)
headers["Authorization"] = "Bearer #{access_token}" if access_token
self.class.send(verb, uri.to_s, headers: headers, body: post_body.to_json)
HttpCapture::RESPONSES.last
end
end
end
7 changes: 7 additions & 0 deletions spec/blinkbox/creditcardserviceclient_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require_relative '../spec_helper'

describe Blinkbox::CreditCardServiceClient.new(SERVER_URI, nil) do
it { is_expected.to respond_to(:use_proxy).with(1).argument }
it { is_expected.to respond_to(:add_credit_card).with(1).argument }
it { is_expected.to respond_to(:add_credit_card).with(2).arguments }
end
8 changes: 8 additions & 0 deletions spec/blinkbox/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
expect(@user).to respond_to(:deregister_device).with(1).argument
end

it "should be able to add a default credit card for a user" do
expect(@user).to respond_to(:add_default_credit_card).with(0).argument
end

it "should be able to add a default credit card of a specified type for a user" do
expect(@user).to respond_to(:add_default_credit_card).with(1).arguments
end

it "Should store authentication data" do
@user.authenticate
expect(@user.access_token).to eq(MockClient::TESTDATA[:access_token])
Expand Down

0 comments on commit abdd6e5

Please sign in to comment.