your_membership 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +261 -0
- data/Rakefile +2 -0
- data/lib/httparty/patch.rb +33 -0
- data/lib/your_membership/base.rb +197 -0
- data/lib/your_membership/commerce.rb +25 -0
- data/lib/your_membership/config.rb +41 -0
- data/lib/your_membership/convert.rb +24 -0
- data/lib/your_membership/error.rb +21 -0
- data/lib/your_membership/events.rb +60 -0
- data/lib/your_membership/feeds.rb +37 -0
- data/lib/your_membership/member.rb +397 -0
- data/lib/your_membership/members.rb +124 -0
- data/lib/your_membership/people.rb +38 -0
- data/lib/your_membership/profile.rb +85 -0
- data/lib/your_membership/sa.rb +6 -0
- data/lib/your_membership/sa_auth.rb +34 -0
- data/lib/your_membership/sa_certifications.rb +22 -0
- data/lib/your_membership/sa_commerce.rb +22 -0
- data/lib/your_membership/sa_events.rb +66 -0
- data/lib/your_membership/sa_export.rb +195 -0
- data/lib/your_membership/sa_groups.rb +30 -0
- data/lib/your_membership/sa_member.rb +49 -0
- data/lib/your_membership/sa_members.rb +179 -0
- data/lib/your_membership/sa_nonmembers.rb +41 -0
- data/lib/your_membership/sa_people.rb +92 -0
- data/lib/your_membership/session.rb +147 -0
- data/lib/your_membership/version.rb +3 -0
- data/lib/your_membership.rb +37 -0
- data/spec/lib/profile_spec.rb +197 -0
- data/spec/spec_helper.rb +78 -0
- data/your_membership.gemspec +30 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e167609f71a1eb9be5e05117ef8baa58039ba2c9
|
4
|
+
data.tar.gz: 46cb16feb9c4cf28145a18b4ef23a2ebc8f79f4e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a9d00bfa496efd4a21c47bbc0a613f5fab98f245a3d87d3ca252837ebd9cdafc8fa5a4af2ca6a7c0a62e231d870833f55337ce00959fd8db22894523335063fb
|
7
|
+
data.tar.gz: c73d3f40ea5053b5dbebc54309be0bca063befcc2c6fa371007c393b8ff5840c166ad00969d40b6564fd5888c141a4deaac4265c0f9e1b2f9e37f4ae22ffd32d
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 ECHO Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
YourMembership: Ruby SDK for the YourMembership.Com XML API
|
2
|
+
===========================================
|
3
|
+
|
4
|
+
_This SDK for Version 2.00 of the YourMembership.com API_
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'your_membership'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install your_membership
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Every effort has been made to expose the YourMembership.com API as transparently as possible while still providing a natively Ruby interface.
|
23
|
+
|
24
|
+
### Scope and Naming
|
25
|
+
|
26
|
+
Everything is scoped under the `YourMembership` namespace.
|
27
|
+
|
28
|
+
Methods are named according to the naming conventions used in the API mapped to appropriate Ruby conventions:
|
29
|
+
|
30
|
+
`Events.All.Search` becomes `YourMembership::Events.all_search`
|
31
|
+
`Member.Commerce.Store.GetOrderIDs` becomes `YourMembership::Member.commerce_store_getOrderIDs`
|
32
|
+
`Sa.Members.All.GetIDs` becomes `YourMembership::Sa::Members.all_getIDs`
|
33
|
+
|
34
|
+
Note that the dot `.` notation has been converted to Ruby's preferred all-lowercase underscore "snake" `_` conventions, but that the method name camelCasing has been preserved for all characters other than the first.
|
35
|
+
|
36
|
+
### Configuration
|
37
|
+
|
38
|
+
You can configure the gem in one of two ways:
|
39
|
+
|
40
|
+
#### YAML
|
41
|
+
|
42
|
+
Pass a YAML file through `YourMembership.configure_with(YourFilename.YAML)`
|
43
|
+
|
44
|
+
_Example YAML File_
|
45
|
+
|
46
|
+
```YAML
|
47
|
+
---
|
48
|
+
# Configuration settings for YourWebsiteHere.com
|
49
|
+
publicKey: 45G2E6DC-98NA-45W7-8493-D97C4E2C156A
|
50
|
+
privateKey: D74H44B2-2348-4ACT-B531-45W385TGB966
|
51
|
+
saPasscode: WPIkriJtqS4m
|
52
|
+
baseUri: 'https://api.yourmembership.com'
|
53
|
+
version: '2.00'
|
54
|
+
```
|
55
|
+
|
56
|
+
#### Configuration Hash
|
57
|
+
|
58
|
+
Pass in one or more parameters through a hash of configuration keys
|
59
|
+
|
60
|
+
_Example Configuration Hash_
|
61
|
+
|
62
|
+
```RUBY
|
63
|
+
YourMembership.configure :publicKey => 45G2E6DC-98NA-45W7-8493-D97C4E2C156A, :privateKey => D74H44B2-2348-4ACT-B531-45W385TGB966, :saPasscode => WPIkriJtqS4m
|
64
|
+
```
|
65
|
+
|
66
|
+
*Note that the baseUri and version are both defaulted to the current API for the release version.
|
67
|
+
|
68
|
+
### Method Arguments and Returned Value Types
|
69
|
+
|
70
|
+
#### Arguments
|
71
|
+
|
72
|
+
Method arguments marked as required in the YourMembership.com API must are implemented as required arguments to the SDK methods in this pattern:
|
73
|
+
|
74
|
+
`YourMembership::Namespace.some_methodName(sessionArgument, requiredArgument(s), optionsHash)`
|
75
|
+
|
76
|
+
The `sessionArgument` is required for most method calls in the API, but is sometimes not required. The ::Sa:: (System Administrator) namespaced methods usually do not require a session.
|
77
|
+
|
78
|
+
The options hash is present any time optional arguments can be passed to a method. These are sent as key-value pairs with the keys being `:symbol` objects named and cased in the way the API requires. For instance:
|
79
|
+
|
80
|
+
`YourMembership::Events.all_search` takes one required argument (a session object/key) and up to three optional arguments passed as a hash.
|
81
|
+
|
82
|
+
```RUBY
|
83
|
+
session = YourMembership::Session.new
|
84
|
+
options = {:SearchText => "A string to search for", :PageSize => 5, :StartRecord => 6}
|
85
|
+
YourMembership::Events.all_search session, options
|
86
|
+
```
|
87
|
+
**Note:** _Arguments passed as symbols need to be capitalized in the way that the API expects them. Many Rubyists prefer camelCased symbols, but in the case of the SDK you'll need to provide first-letter-capitalized CamelCase arguments._
|
88
|
+
|
89
|
+
You can pass strings as keys if you do not prefer to use Symbols.
|
90
|
+
|
91
|
+
##### SDK-Specific Implementation of Argument Types
|
92
|
+
|
93
|
+
You should use the standard Ruby types to represent data, for instance:
|
94
|
+
|
95
|
+
`:Timestamp` and other date or time arguments should be passed as DateTime objects rather than formatted strings, the formatting will be done for you.
|
96
|
+
|
97
|
+
**A little added Syntactic Sugar:** Where a limited scope of static options is present some handy symbols have been added for convenience. For instance:
|
98
|
+
|
99
|
+
`YourMembership::Member.commerce_store_getOrderIDs` takes an optional `:Timestamp` argument (which should be passed as a Ruby `DateTime` object) and an optional `:Status` argument. The `:Status` argument can either be an integer that represents a status as documented in the API or you can use a symbol that will automatically be mapped through a helper method. In this case you can pass `:open`, `:processed`, `:shipped`, `:closed`, or `:cancelled`.
|
100
|
+
|
101
|
+
#### Returned Values
|
102
|
+
|
103
|
+
The SDK translates the returned data into Ruby Objects of only a few types. Nearly all methods return an Array, this may be an Array of Strings or, more likely, it will be an Array of Hashes.
|
104
|
+
|
105
|
+
If only a single record can ever be retrieved at a time through a method a Hash object will be returned instead of an Array.
|
106
|
+
|
107
|
+
In the case that multiple records could be returned, but no records are returned you should receive an empty Array.
|
108
|
+
|
109
|
+
The return type you should expect is documented along with every method in the source.
|
110
|
+
|
111
|
+
**Special Case:** The `YourMembership::Feeds.feed_get` method returns a Nokogiri::XML object
|
112
|
+
|
113
|
+
--------------------------------------------------------------------------------
|
114
|
+
|
115
|
+
### SDK Special Objects
|
116
|
+
|
117
|
+
The SDK implements some representative objects that provide convenience.
|
118
|
+
|
119
|
+
#### YourMembership::Session
|
120
|
+
|
121
|
+
**Note:** *It is important to note that the Auth Namespace has been consumed by Sessions in the SDK as sessions and authentication are inextricably linked.*
|
122
|
+
|
123
|
+
Session objects encapsulate the creation, storage, authentication, maintenance, and destruction of sessions in the YourMembership.com API.
|
124
|
+
|
125
|
+
Sessions can be **generic** (unauthenticated), **authenticated**, or **abandoned**.
|
126
|
+
|
127
|
+
+ **Generic sessions** are used extensively whenever the scope of a specific user is not necessary.
|
128
|
+
+ **Authenticated sessions** are used when the called method requires the scope of a specific user.
|
129
|
+
+ **Abandoned sessions** are no longer usable and are essentially the same as logging out. A session can be forcibly abandoned and is automatically considered abandoned if it is not used for more than 20 minutes. If a session is needed for a length of more than 20 minutes the `ping` method can be called to extend the life-cycle for another 20 minutes.
|
130
|
+
|
131
|
+
##### Examples:
|
132
|
+
```RUBY
|
133
|
+
# Generic (unauthenticated) Session
|
134
|
+
session = YourMembership::Session.new
|
135
|
+
|
136
|
+
# Authenticated Session
|
137
|
+
auth_session = YourMembership::Session.new 'username', 'password'
|
138
|
+
|
139
|
+
# Sessions can also be authenticated after creation in one of two ways:
|
140
|
+
|
141
|
+
# By providing Credentials
|
142
|
+
session.authenticate 'username', 'password'
|
143
|
+
|
144
|
+
# Or through Token Authentication
|
145
|
+
token = session.createToken
|
146
|
+
```
|
147
|
+
|
148
|
+
##### Additional Methods
|
149
|
+
These additional methods report on the status of the session on the YourMembership.com server.
|
150
|
+
|
151
|
+
+ `.valid?` indicates if a session is still _alive_ and able to be used to make calls
|
152
|
+
+ `.authenticated?` indicates if a session is authenticated
|
153
|
+
|
154
|
+
#### YourMembership::Member
|
155
|
+
|
156
|
+
The Member object builds upon the Session object to more fully represent an authenticated session and provide an easy way to encapsulate the Member methods within the session's authenticated scope.
|
157
|
+
|
158
|
+
##### Instantiation:
|
159
|
+
```RUBY
|
160
|
+
# Members can be created by authenticating directly with the YourMembership API,
|
161
|
+
# the session is automatically created and bound to the Member instance.
|
162
|
+
member = YourMembership::Member.create_by_authentication 'username', 'password'
|
163
|
+
|
164
|
+
# Members can also be created by passing in an already existing Session instance
|
165
|
+
# this is especially useful when Sessions are authenticated through token
|
166
|
+
# authentication.
|
167
|
+
auth_session = YourMembership::Session.new 'username', 'password'
|
168
|
+
member = YourMembership::Member.create_from_session auth_session
|
169
|
+
```
|
170
|
+
_Member objects can be created directly without a bound Session, but there is (as of yet) very little use for this._
|
171
|
+
|
172
|
+
#### Making use of a Member Instance
|
173
|
+
|
174
|
+
When a Member object is instantiated some basic properties of the represented member are cached in the object:
|
175
|
+
+ id - The member's API ID String
|
176
|
+
+ website_id - The member's unique identifier within your community
|
177
|
+
+ first_name
|
178
|
+
+ last_name
|
179
|
+
+ email
|
180
|
+
|
181
|
+
These fields are useful in reducing the number of round trips to the API for some commonly used attributes, but the real benefit of the Member instance is to be able to access all of the Member namespaced methods within the scope of the member.
|
182
|
+
|
183
|
+
Any method in the Member Namespace can be called like these examples:
|
184
|
+
```RUBY
|
185
|
+
member = YourMembership::Member.create_by_authentication 'username', 'password'
|
186
|
+
|
187
|
+
profile = member.profile_get
|
188
|
+
sent_messages = member.messages_getSent
|
189
|
+
...
|
190
|
+
order = member.commerce_store_order_get member.commerce_store_getOrderIDs[0]
|
191
|
+
```
|
192
|
+
**NOTE:** *Of course all of the methods of the Member namespace can be called without a Member instance, but an authorized session will need to be passed as the first argument.*
|
193
|
+
|
194
|
+
##### Member Session Management:
|
195
|
+
|
196
|
+
The Member instance's bound Session instance is accessible as an attribute.
|
197
|
+
|
198
|
+
#### YourMembership::Profile
|
199
|
+
|
200
|
+
The Profile object provides a convenient abstraction that encapsulates a person's profile allowing clear and concise access to both the core fields provided by YourMembership and the custom fields added by site administrators.
|
201
|
+
|
202
|
+
The API has some required fields for new members so a specific method (Profile.create_new) exists for the convenience of quickly creating a new profile for a new person.
|
203
|
+
|
204
|
+
A profile can be loaded by passing a hash directly to the initializer (Profile.new) method. This can be useful in creating a profile object from an API response. This is how profile objects are created internally through the YourMembership::Member.profile_get, YourMembership::People.profile_get, and YourMembership::Sa::People.profile_get methods.
|
205
|
+
|
206
|
+
A profile can be created empty or by passing a hash for standard fields. This is useful for updating an existing profile without changing unnecessary records.
|
207
|
+
|
208
|
+
An *outdated* list of standard fields can be found here: https://api.yourmembership.com/reference/YM_API_Member_Field_Documentation.pdf
|
209
|
+
|
210
|
+
##### Data Access
|
211
|
+
+ **Profile#data** `Hash` This internal hash can be read and written to and should contain only fields that are standard in the YourMembership system.
|
212
|
+
+ **Profile#custom_data** `Hash` This internal hash can be read and written to and should contain the fields that are specified as custom responses in a specific YourMembership community.
|
213
|
+
+ **Profile#to_h** `Hash` is a read-only method for returning a single nested hash of both standard and custom fields.
|
214
|
+
|
215
|
+
#### YourMembership::Export
|
216
|
+
|
217
|
+
Export objects are returned when any YourMembership::Sa::Export method is called. This provides an easy way to keep track of the status of the export request. Export requests are handled asynchronously and therefore should be polled until the results are ready.
|
218
|
+
|
219
|
+
The Export object provides the Export#status method. It is necessary to call the Export#status method at least once in the export objects lifecycle as the export_uri will not be made available until status is called.
|
220
|
+
|
221
|
+
**Example:**
|
222
|
+
|
223
|
+
```RUBY
|
224
|
+
donations = YourMembership::Sa::Export.donations_transactions(DateTime.now, True)
|
225
|
+
|
226
|
+
# Implement a loop to check status
|
227
|
+
|
228
|
+
case donations.status
|
229
|
+
when :failure, :error
|
230
|
+
# Stop looping, the process failed
|
231
|
+
when :unknown, :working
|
232
|
+
# Continue looping
|
233
|
+
when :complete
|
234
|
+
# Export is ready
|
235
|
+
donations.export_uri
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
## About The Author(s)
|
240
|
+
|
241
|
+
![ECHO Inc.](http://static.squarespace.com/static/516da119e4b00686219e2473/t/51e95357e4b0db0cdaadcb4d/1407936664333/?format=1500w)
|
242
|
+
|
243
|
+
This library is an open-source project built by ECHO, an international nonprofit working to help those who are teaching farmers around the world know how to be more effective in producing enough to meet the needs of their families and their communities. ECHO is an information hub for international development practitioners specifically focused on sustainable, small-scale, agriculture. Through educational programs, publications, and networking ECHO is sharing solutions from around the world that are solving hunger problems.
|
244
|
+
|
245
|
+
Charity Navigator ranks ECHO as the #1 International Charity in the state of Florida (where their US operations are based) and among the top 100 in the US.
|
246
|
+
|
247
|
+
Thanks to grants and donations from people like you, ECHO is able to connect trainers and farmers with valuable (and often hard-to-find) resources. One of ECHO's greatest resources is the network of development practitioners, around the globe, that share help and specialized knowledge with each other. ECHO uses the YourMembership product to help facilitate these connections.
|
248
|
+
|
249
|
+
To find out more about ECHO, or to help with the work that is being done worldwide please visit http://www.echonet.org
|
250
|
+
|
251
|
+
## Contributing
|
252
|
+
|
253
|
+
If you find a problem with this library or would like to contribute an improvement please fork and submit a pull request.
|
254
|
+
|
255
|
+
If you're a developer that would like to help solve world hunger problems with some of your spare time, we are always looking for talented volunteers. Contact Nate Flood by email: nate [at] echonet [dot] org
|
256
|
+
|
257
|
+
1. Fork it ( https://github.com/ECHOInternational/your_membership/fork )
|
258
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
259
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
260
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
261
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
# Monkey Patch for the HTTParty Gem
|
4
|
+
module HTTParty
|
5
|
+
# In order to parse the HTML encoded documents returned by the YourMembership
|
6
|
+
# API we need to HTML decode the <![CDATA[ tags. This is a bug workaround.
|
7
|
+
class Request
|
8
|
+
# This is the encode_body method from HTTParty's Request Class adding an additional method call to fix
|
9
|
+
# the CDATA elements that are improperly formatted in the YourMembership API's XML. This is done here to ensure that
|
10
|
+
# the fix is in place before the data is parsed.
|
11
|
+
def encode_body(body)
|
12
|
+
body = fix_cdata body
|
13
|
+
if ''.respond_to?(:encoding)
|
14
|
+
_encode_body(body)
|
15
|
+
else
|
16
|
+
body
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Bug Fix for HTML encoded < and > in XML body.
|
21
|
+
# @param body [String] an XML document that needs to be checked for this specific issue.
|
22
|
+
# @return [String] If the HTML encoding issue is found it is repaired and the document is returned.
|
23
|
+
def fix_cdata(body)
|
24
|
+
# <![CDATA[ = <![CDATA[
|
25
|
+
# ]]> = ]]>
|
26
|
+
if body.include? '<![CDATA['
|
27
|
+
body.gsub! '<![CDATA[', '<![CDATA['
|
28
|
+
body.gsub! ']]>', ']]>'
|
29
|
+
end
|
30
|
+
body
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module YourMembership
|
2
|
+
# Base Class inherited by all Your Membership SDK Classes.
|
3
|
+
class Base
|
4
|
+
include HTTParty
|
5
|
+
|
6
|
+
base_uri YourMembership.config[:baseUri]
|
7
|
+
|
8
|
+
# rubocop:disable Style/ClassVars
|
9
|
+
|
10
|
+
# Call IDs are usually tied to sessions, this is a unique call id for use whenever a session is not needed.
|
11
|
+
@@genericCallID = nil
|
12
|
+
|
13
|
+
# @return [Integer] Auto Increments ad returns the genericCallID as required by the YourMembership.com API
|
14
|
+
def self.new_call_id
|
15
|
+
if @@genericCallID.nil?
|
16
|
+
# We start with a very high number to avoid conflicts when initiating a new session.
|
17
|
+
@@genericCallID = 10_000
|
18
|
+
else
|
19
|
+
@@genericCallID += 1
|
20
|
+
end
|
21
|
+
@@genericCallID
|
22
|
+
end
|
23
|
+
# rubocop:enable Style/ClassVars
|
24
|
+
|
25
|
+
# A Guard Method that returns true if the response from the API can be processed and raises an exception if not.
|
26
|
+
# @param [Hash] response
|
27
|
+
# @return [Boolean] true if no errors found.
|
28
|
+
# @raise [HTTParty::ResponseError] if a communication error is found.
|
29
|
+
def self.response_valid?(response)
|
30
|
+
if response.success?
|
31
|
+
!response_ym_error?(response)
|
32
|
+
else
|
33
|
+
raise HTTParty::ResponseError.new(response), 'Connection to YourMembership API failed.'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks for error codes in the API response and raises an exception if an error is found.
|
38
|
+
# @param [Hash] response
|
39
|
+
# @return [Boolean] false if no error is found
|
40
|
+
# @raise [YourMembership::Error] if an error is found
|
41
|
+
def self.response_ym_error?(response)
|
42
|
+
if response['YourMembership_Response']['ErrCode'] != '0'
|
43
|
+
raise YourMembership::Error.new(
|
44
|
+
response['YourMembership_Response']['ErrCode'],
|
45
|
+
response['YourMembership_Response']['ErrDesc']
|
46
|
+
)
|
47
|
+
else
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Creates an XML string to send to the API
|
53
|
+
# @todo THIS SHOULD BE MARKED PRIVATE and refactored to DRY up the calls.
|
54
|
+
def self.build_XML_request(callMethod, session = nil, params = {}) # rubocop:disable Style/MethodLength, Style/MethodName
|
55
|
+
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
56
|
+
# Root Node Is always <YourMembership>
|
57
|
+
xml.YourMembership do
|
58
|
+
# API Version
|
59
|
+
xml.Version_ YourMembership.config[:version]
|
60
|
+
|
61
|
+
# API Key: For System Administrative tasks it is the private key and
|
62
|
+
# passcode, for all others it is the public key
|
63
|
+
if callMethod.downcase.start_with?('sa.')
|
64
|
+
xml.ApiKey_ YourMembership.config[:privateKey]
|
65
|
+
xml.SaPasscode_ YourMembership.config[:saPasscode]
|
66
|
+
else
|
67
|
+
xml.ApiKey_ YourMembership.config[:publicKey]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Pass Session ID and Session Call ID unless there is no session, then
|
71
|
+
# send call id from class
|
72
|
+
if session
|
73
|
+
if session.is_a? YourMembership::Session
|
74
|
+
xml.SessionID_ session.session_id
|
75
|
+
else
|
76
|
+
xml.SessionID_ session
|
77
|
+
end
|
78
|
+
xml.CallID_ session.call_id
|
79
|
+
else
|
80
|
+
xml.CallID_ new_call_id
|
81
|
+
end
|
82
|
+
|
83
|
+
xml.Call(:Method => callMethod) do
|
84
|
+
params.each do |key, value|
|
85
|
+
xml_process(key, value, xml)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
builder.to_xml
|
92
|
+
end
|
93
|
+
|
94
|
+
# This is a helper method to always return an array (potentially empty) of responses (Hashes) for methods that can
|
95
|
+
# have multiple results. The default behavior of the API is to return a nil if no records are found, a hash if one
|
96
|
+
# record is found and an array if multiple records are found.
|
97
|
+
#
|
98
|
+
# @param [Hash] response_body This is the nodeset that returns nil if an empty data set is returned.
|
99
|
+
# @param [Array] keys This is a list of keys, in order that are nested inside the response body, these keys will be
|
100
|
+
# traversed in order before retrieving the data associated with the last key in the array
|
101
|
+
# @return [Array] A single dimension array of hashes.
|
102
|
+
def self.response_to_array_of_hashes(response_body, keys = [])
|
103
|
+
return_array = []
|
104
|
+
if response_body
|
105
|
+
# http://stackoverflow.com/questions/13259181/what-is-the-most-ruby-ish-way-of-accessing-nested-hash-values-at-arbitrary-depth
|
106
|
+
response_body_items = keys.reduce(response_body) { |h, key| h[key] }
|
107
|
+
if response_body_items.class == Array
|
108
|
+
response_body_items.each do |response_item|
|
109
|
+
return_array.push response_item
|
110
|
+
end
|
111
|
+
else
|
112
|
+
return_array.push response_body_items
|
113
|
+
end
|
114
|
+
end
|
115
|
+
return_array
|
116
|
+
end
|
117
|
+
|
118
|
+
# Converts the desired portion of the XML response to a single dimension array.
|
119
|
+
# This is useful when you don't have a need for key, value pairs and want a clean array of values to work with.
|
120
|
+
#
|
121
|
+
# @param [Hash] response_body This is the nodeset that returns nil if an empty data set is returned.
|
122
|
+
# @param [Array] keys This is a list of keys, in order that are nested inside the response body, these keys will be
|
123
|
+
# traversed in order before retrieving the data associated with the key_for_array
|
124
|
+
# @param [String] key_for_array this is the key that represents the list of items you want to turn into an array.
|
125
|
+
# @return [Array] A single dimension array of values.
|
126
|
+
def self.response_to_array(response_body, keys = [], key_for_array)
|
127
|
+
return_array = []
|
128
|
+
response_hash_array = response_to_array_of_hashes(response_body, keys)
|
129
|
+
response_hash_array.each do |item|
|
130
|
+
return_array.push item[key_for_array]
|
131
|
+
end
|
132
|
+
return_array
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# Convenience method to convert Ruby DateTime objects into ODBC canonical strings as are expected by the API
|
138
|
+
# @param [DateTime] dateTime
|
139
|
+
# @return [String] An ODBC canonical string representation of a date as is expected by the API
|
140
|
+
def self.format_date_string(dateTime)
|
141
|
+
dateTime.strftime('%Y-%m-%d %H:%M:%S')
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.xml_process(key, value, xml)
|
145
|
+
case value
|
146
|
+
when Array
|
147
|
+
xml_process_array(key, value, xml)
|
148
|
+
when Hash
|
149
|
+
xml_process_hash(key, value, xml)
|
150
|
+
when YourMembership::Profile
|
151
|
+
xml_process_profile(value, xml)
|
152
|
+
when DateTime
|
153
|
+
xml.send(key, format_date_string(value))
|
154
|
+
else
|
155
|
+
xml.send(key, value)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.xml_process_profile(profile, xml)
|
160
|
+
profile.data.each do |k, v|
|
161
|
+
xml_process(k, v, xml)
|
162
|
+
end
|
163
|
+
xml.send('CustomFieldResponses') do
|
164
|
+
profile.custom_data.each do |k, v|
|
165
|
+
xml_process_custom_field_responses(k, v, xml)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.xml_process_hash(key, value, xml)
|
171
|
+
xml.send(key) do
|
172
|
+
value.each { |k, v| xml_process(k, v, xml) }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.xml_process_array(key, value, xml)
|
177
|
+
xml.send(key) do
|
178
|
+
value.each { |tag| xml.send(tag[0], tag[1]) }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.xml_process_custom_field_responses(key, value, xml)
|
183
|
+
xml.send('CustomFieldResponse', :FieldCode => key) do
|
184
|
+
xml.send('Values') do
|
185
|
+
case value
|
186
|
+
when Array
|
187
|
+
value.each do | item |
|
188
|
+
xml.send('Value', item)
|
189
|
+
end
|
190
|
+
else
|
191
|
+
xml.send('Value', value)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module YourMembership
|
2
|
+
# The Commerce Namespace does not currently have any methods in the
|
3
|
+
# YourMembership.com API. This is used for some helper methods.
|
4
|
+
class Commerce
|
5
|
+
# Converts some convenient Symbols to the Integer that the YourMembership API expects
|
6
|
+
# @param [Symbol] status Accepts :cancelled, :open, :processed, :shipped
|
7
|
+
# @return [Integer] Returns the integer that the API expects
|
8
|
+
def self.convert_order_status(status)
|
9
|
+
status_code = 0
|
10
|
+
case status
|
11
|
+
when :cancelled, :Cancelled
|
12
|
+
status_code = -1
|
13
|
+
when :open, :Open
|
14
|
+
status_code = 0
|
15
|
+
when :processed, :Processed
|
16
|
+
status_code = 1
|
17
|
+
when :shipped, :Shipped, :closed, :Closed
|
18
|
+
status_code = 2
|
19
|
+
else
|
20
|
+
status_code = status
|
21
|
+
end
|
22
|
+
status_code
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module YourMembership
|
3
|
+
# Configuration defaults
|
4
|
+
@config = {
|
5
|
+
:publicKey => '',
|
6
|
+
:privateKey => '',
|
7
|
+
:saPasscode => '',
|
8
|
+
:baseUri => 'https://api.yourmembership.com',
|
9
|
+
:version => '2.00'
|
10
|
+
}
|
11
|
+
|
12
|
+
@valid_config_keys = @config.keys
|
13
|
+
|
14
|
+
# Configure through hash
|
15
|
+
# @example
|
16
|
+
# YourMembership.configure(:publicKey => 45G2E6DC-98NA-45W7-8493-D97C4E2C156A,
|
17
|
+
# :privateKey => D74H44B2-2348-4ACT-B531-45W385TGB966, :saPasscode => WPIkriJtqS4m)
|
18
|
+
# @note The baseUri and version are both defaulted to the current API for the release version.
|
19
|
+
def self.configure(opts = {})
|
20
|
+
opts.each { |k, v| @config[k.to_sym] = v if @valid_config_keys.include? k.to_sym }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Configure through yaml file
|
24
|
+
# @example
|
25
|
+
# ---
|
26
|
+
# publicKey: 45G2E6DC-98NA-45W7-8493-D97C4E2C156A
|
27
|
+
# privateKey: D74H44B2-2348-4ACT-B531-45W385TGB966
|
28
|
+
# saPasscode: WPIkriJtqS4m
|
29
|
+
# baseUri: 'https://api.yourmembership.com'
|
30
|
+
# version: '2.00'
|
31
|
+
# @note The baseUri and version are both defaulted to the current API for the release version.
|
32
|
+
def self.configure_with(path_to_yaml_file)
|
33
|
+
config = YAML.load(IO.read(path_to_yaml_file))
|
34
|
+
configure(config)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Access configuration variables by calling YourMembership.config[ :attribute ]
|
38
|
+
def self.config # rubocop:disable Style/TrivialAccessors
|
39
|
+
@config
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module YourMembership
|
2
|
+
# YourMembership Convert Namespace
|
3
|
+
class Convert < YourMembership::Base
|
4
|
+
# Converts the given local time to current Eastern Time, factoring in adjustments for Daylight Savings Time.
|
5
|
+
# @see https://api.yourmembership.com/reference/2_00/Convert_ToEasternTime.htm
|
6
|
+
#
|
7
|
+
# @param [YourMembership::Session] session
|
8
|
+
# @param [DateTime] localTime A DateTime that you want to convert to US/Eastern Time
|
9
|
+
# @param [Integer] localGMTBias The Number of hours the local time zone is away from GMT
|
10
|
+
# @return [Hash] Returns an Hash with 'Converted' and 'ServerGmtBias'
|
11
|
+
# @note This is probably not necessary as Ruby will happily convert dates.
|
12
|
+
def self.ToEasternTime(session, localTime, localGMTBias) # rubocop:disable Style/MethodName
|
13
|
+
options = {}
|
14
|
+
options[:LocalTime] = format_date_string localTime
|
15
|
+
options[:LocalGmtBias] = localGMTBias
|
16
|
+
|
17
|
+
response = post('/', :body => build_XML_request('Convert.ToEasternTime', session, options))
|
18
|
+
|
19
|
+
if response_valid? response
|
20
|
+
response['YourMembership_Response']['Convert.ToEasternTime']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module YourMembership
|
2
|
+
# Custom exception for YourMembership error codes.
|
3
|
+
# @attr [Integer] error_code The Error Code
|
4
|
+
# @attr [String] error_description A Description of what the error means.
|
5
|
+
class Error < StandardError
|
6
|
+
attr_accessor :error_code, :error_description
|
7
|
+
|
8
|
+
# @param [Integer] error_code The Error Code
|
9
|
+
# @param [String] error_description A Description of what the error means.
|
10
|
+
# @see https://api.yourmembership.com/reference/2_00/Error_Codes.htm
|
11
|
+
def initialize(error_code, error_description)
|
12
|
+
self.error_code = error_code
|
13
|
+
self.error_description = error_description
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String] A highly readable error message.
|
17
|
+
def to_s
|
18
|
+
"Your Membership Returned An Error Code: #{error_code} With Message: #{error_description}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|