amex 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem 'httparty', '0.9.0'
4
- gem 'nokogiri', '1.5.6'
4
+ gem 'nokogiri', '1.5.6'
5
+ gem 'amex', '0.3.1'
@@ -1,6 +1,9 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ amex (0.3.0)
5
+ httparty
6
+ nokogiri
4
7
  httparty (0.9.0)
5
8
  multi_json (~> 1.0)
6
9
  multi_xml
@@ -12,5 +15,6 @@ PLATFORMS
12
15
  ruby
13
16
 
14
17
  DEPENDENCIES
18
+ amex (= 0.3.0)
15
19
  httparty (= 0.9.0)
16
20
  nokogiri (= 1.5.6)
data/README.md CHANGED
@@ -8,11 +8,18 @@ However, this one doesn't rely on nasty screen-scraping. Instead, it uses
8
8
  the previous unknown internal API used by American Express for their
9
9
  "Amex UK" iPhone app. I suspect it works elsewhere, but I haven't tested.
10
10
 
11
+ This allows you to fetch the details of all the cards on your American
12
+ Express login, as well as the most recent statement's transactions.
13
+
11
14
  ### Changelog
12
15
 
13
16
  __v0.1.0__ - Original version
14
17
  __v0.2.0__ - Support for multiple American Express cards, parsing using
15
18
  Nokogiri
19
+ __v0.3.0__ - Adds support for loading the transactions from the most recent statement
20
+ (but it's broken because I forgot to change something from testing :( )
21
+ __v0.3.1__ - Working version of v0.3.0 that will successfully load transactions
22
+ from the most recent statement
16
23
 
17
24
  ### Usage
18
25
 
@@ -42,8 +49,10 @@ only supports one card at a time right now.
42
49
 
43
50
  ```
44
51
  accounts = client.accounts
45
- puts account.first.product
46
- puts account.first.type
52
+ my_account = accounts.first
53
+ puts my_account.product
54
+ puts my_account.type
55
+ puts my_account.transactions.inspect
47
56
  ```
48
57
 
49
58
  ### Data models
@@ -55,6 +64,8 @@ An __Amex::CardAccount__ instance has the following attributes:
55
64
 
56
65
  * __product (string)__ - the type of card (e.g. "The Preferred Rewards Gold Card®")
57
66
  * __card_number_suffix (string)__ - the last five digits of your card number
67
+ * __card_index (integer)__ - the internal number of the card on your account
68
+ in the Amex system, starting from 0 *(used internally by the gem)*
58
69
  * __lending_type (string)__ - either "Charge" or "Credit", depending on the type of credit arrangement you have
59
70
  * __card_member_name (string)__ - your name, as recorded as the account holder
60
71
  * __past_due (boolean)__ - is the card past its due payment date?
@@ -72,18 +83,36 @@ kind of loyalty scheme active? (e.g. Membership Rewards)
72
83
  * __total_balance (float)__ - what American Express refer to as your total balance, whatever that is!
73
84
  * __payment_due (float)__ - the amount of money you need to pay before `payment_due_date`
74
85
  * __payment_due_date (DateTime)__ - when the `payment_due` needs to be paid by
86
+ * __transactions (array)__ - an array of Amex::Transaction objects)
75
87
 
76
88
  There are lots of extra useful methods here to make accessing
77
89
  some of the various properties easier. They're very self-explanatory - check `lib/amex/card_account.rb`.
78
90
 
79
- A __LloydsTSB::LoyaltyProgramme has just two attributes:
91
+ A __Amex::LoyaltyProgramme has just two attributes:
80
92
 
81
93
  * __name (string)__ - The name of the programme, most often "Membership Rewards"
82
94
  * __balance (integer)__ - The balance of the programme
83
95
 
96
+ An __Amex::Transaction represents a transaction in your most recent American
97
+ Express statement. It has four attributes:
98
+
99
+ * __amount (float)__ - The amount of the transaction
100
+ * __date (Date)__ - The date that the transaction was recorded
101
+ * __narrative (string)__ - The description shown on your statement, usually
102
+ contaning the name of the merchant and their location
103
+ * __extra_details (hash)__ - This hash contains any extra pieces of information
104
+ provided by American Express...for instance, foreign transactions have their
105
+ exchange rate and commission. The name of the attribute will be the key, and
106
+ its formatted value in the value in the hash.
107
+
108
+ There's one helper method currently available here, `is_foreign_transaction?`,
109
+ which returns a boolean representing whether the transaction was foreign (i.e.
110
+ in a non-native currency).
111
+
84
112
  ### Limitations
85
113
 
86
- * There's no support for viewing transactions yet - watch this space!
114
+ * You can only view transactions from the most recent statement. I intend
115
+ to change this in due course to support pagination of some description.
87
116
 
88
117
  ### License
89
118
 
Binary file
Binary file
@@ -1,4 +1,5 @@
1
1
  require 'amex/card_account'
2
+ require 'amex/transaction'
2
3
  require 'amex/client'
3
4
  require 'amex/loyalty_programme'
4
5
  require 'amex/utils'
@@ -1,10 +1,11 @@
1
1
  module Amex
2
2
  class CardAccount
3
- attr_accessor :card_product, :card_number_suffix,
3
+ attr_accessor :card_product, :card_number_suffix, :card_index,
4
4
  :lending_type, :card_member_name, :past_due, :cancelled, :is_basic,
5
5
  :is_centurion, :is_platinum, :is_premium, :market, :card_art,
6
6
  :loyalty_indicator, :stmt_balance, :payment_credits, :recent_charges,
7
- :total_balance, :payment_due, :payment_due_date, :loyalty_programmes
7
+ :total_balance, :payment_due, :payment_due_date, :loyalty_programmes,
8
+ :transactions
8
9
 
9
10
  def initialize(options)
10
11
  options.each do |key, value|
@@ -12,6 +13,7 @@ module Amex
12
13
  send(key.underscore + "=", value) if respond_to? method.to_sym
13
14
  end
14
15
  @loyalty_programmes = []
16
+ @transactions = []
15
17
  end
16
18
 
17
19
  def statement_balance
@@ -1,6 +1,7 @@
1
1
  require 'erb'
2
2
  require 'httparty'
3
3
  require 'nokogiri'
4
+ require 'date'
4
5
 
5
6
  module Amex
6
7
  class Client
@@ -24,6 +25,15 @@ module Amex
24
25
  ERB.new(xml).result(binding)
25
26
  end
26
27
 
28
+ def statement_request_xml(card_index)
29
+ xml = File.read(
30
+ File.expand_path(File.dirname(__FILE__) + '/data/statement_request.xml')
31
+ )
32
+
33
+ security_token = @security_token
34
+ ERB.new(xml).result(binding)
35
+ end
36
+
27
37
  def accounts
28
38
  # This only supports one account for now, because I'm lazy and I
29
39
  # hate traversing XML...
@@ -38,6 +48,9 @@ module Amex
38
48
  if xml.css('ServiceResponse Status').text != "success"
39
49
  raise "There was a problem logging in to American Express."
40
50
  else
51
+ # Store the security token - we need this for further requests
52
+ @security_token = xml.css('ClientSecurityToken').text
53
+
41
54
  accounts = [] # We'll store all the accounts in here!
42
55
 
43
56
  xml.css('CardAccounts CardAccount').each do |item|
@@ -64,6 +77,21 @@ module Amex
64
77
  element.attr('label'), element.attr('formattedValue').gsub(",", "").to_i
65
78
  )
66
79
  end
80
+
81
+ # Now we can fetch the transactions...
82
+ options = { :body => {
83
+ "PayLoadText" => statement_request_xml(account.card_index)
84
+ }}
85
+ response = self.class.post(
86
+ '/myca/intl/moblclient/emea/ws.do?Face=en_GB', options
87
+ )
88
+ xml = Nokogiri::XML(response.body)
89
+ xml = xml.css("XMLResponse")
90
+
91
+ xml.css('Transaction').each do |transaction|
92
+ account.transactions << Amex::Transaction.new(transaction)
93
+ end
94
+
67
95
  accounts << account
68
96
 
69
97
  end
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <XMLRequest>
3
+ <ClientType>iPhone</ClientType>
4
+ <ClientVersion>1.8</ClientVersion>
5
+ <ServiceVersion>1.0</ServiceVersion>
6
+ <Locale>en_GB</Locale>
7
+ <ServiceName>StatementService</ServiceName>
8
+ <ClientSecurityToken><%= security_token %></ClientSecurityToken>
9
+ <StatementRequestData>
10
+ <BillingPeriod>0</BillingPeriod>
11
+ <CardIndex><%= card_index %></CardIndex>
12
+ </StatementRequestData>
13
+ </XMLRequest>
@@ -0,0 +1,25 @@
1
+ require 'nokogiri'
2
+
3
+ module Amex
4
+ class Transaction
5
+ attr_reader :date, :narrative, :amount, :extra_details
6
+
7
+ def initialize(transaction)
8
+ # Pass this a <Transaction> element, and it'll parse it
9
+ @date = Date.strptime(transaction.css('TransChargeDate').text, '%m/%d/%y')
10
+ @narrative = transaction.css('TransDesc').text
11
+ @amount = transaction.css('TransAmount').text.to_f
12
+
13
+ @extra_details = {}
14
+ transaction.css('TransExtDetail ExtDetailElement').each do |element|
15
+ @extra_details[element.attr('name')] = element.attr('formattedValue')
16
+ end
17
+ end
18
+
19
+ def is_foreign_transaction?
20
+ return true if @extra_details.has_key?('currencyRate')
21
+ false
22
+ end
23
+
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module Amex
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.1'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -56,13 +56,16 @@ files:
56
56
  - README.md
57
57
  - amex-0.1.0.gem
58
58
  - amex-0.2.0.gem
59
+ - amex-0.3.0.gem
59
60
  - amex.gemspec
60
61
  - example.rb
61
62
  - lib/amex.rb
62
63
  - lib/amex/card_account.rb
63
64
  - lib/amex/client.rb
64
65
  - lib/amex/data/request.xml
66
+ - lib/amex/data/statement_request.xml
65
67
  - lib/amex/loyalty_programme.rb
68
+ - lib/amex/transaction.rb
66
69
  - lib/amex/utils.rb
67
70
  - lib/amex/version.rb
68
71
  homepage: https://github.com/timrogers/amex