aws_agcod_2 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 57228cfced3a4ce27d86778e5458cb28c6dd00490880ae22db9ef8c2b1ca456e
4
+ data.tar.gz: f06da4395c979dba879ef0aaced9bbdd6ad79958b3514340d6ccbed0bfec74f0
5
+ SHA512:
6
+ metadata.gz: 7384eddccb0d643c91eb8e6c00ea515333b7a9a602b46200f2bda20d1d5464e49c02dbc36b3d650de43ffc27aa9a5fc6bc35349a6ae6b1007f92400545e3b12a
7
+ data.tar.gz: 7971db886d28511ba4818d283b8c3245ac0cf14f850d14ea58539e8b574293ae4d7b75454d0661574556bd6b7348a83dfe8d11bec623071e7efa0c187aef5a79
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Xenor Chang
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,115 @@
1
+ # AGCOD
2
+
3
+ [![Build Status](https://travis-ci.org/compwron/aws_agcod.svg?branch=master)](https://travis-ci.org/compwron/aws_agcod)
4
+ [![Gem Version](https://badge.fury.io/rb/aws_agcod.svg)](http://badge.fury.io/rb/aws_agcod)
5
+
6
+ Amazon Gift Code On Demand (AGCOD) API v2 implementation for distributing Amazon gift cards (gift codes) instantly in any denomination.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'aws_agcod'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install aws_agcod
23
+
24
+ ## Usage
25
+
26
+ #### Configure
27
+
28
+ ```ruby
29
+ require "aws_agcod"
30
+
31
+ AGCOD.configure do |config|
32
+ config.access_key = "YOUR ACCESS KEY"
33
+ config.secret_key = "YOUR SECRET KEY"
34
+ config.partner_id = "PARTNER ID"
35
+
36
+ # The `production` config is important as it determines which endpoint
37
+ # you're hitting.
38
+ config.production = true # This defaults to false.
39
+
40
+ # Optionally, you can customize the URI completely.
41
+ config.uri = "https://my-custom-agcod-endpoint.com"
42
+
43
+ config.region = "us-east-1" # default
44
+ end
45
+ ```
46
+
47
+ #### Create Gift Code/Card
48
+
49
+ ```ruby
50
+ request_id = "test"
51
+ amount = 10
52
+ currency = "USD" # default to USD, available types are: USD, EUR, JPY, CNY, CAD
53
+ httpable = HTTP # or HTTParty- whatever library you're using that has .post
54
+ request = AGCOD::CreateGiftCard.new(httpable, request_id, amount, currency)
55
+
56
+ # When succeed
57
+ if request.success?
58
+ request.claim_code # => code for the gift card
59
+ request.gc_id # => gift card id
60
+ request.request_id # => your request id
61
+ else
62
+ # When failed
63
+ request.error_message # => Error response from AGCOD service
64
+ end
65
+ ```
66
+
67
+ #### Cancel Gift Code/Card
68
+
69
+ ```ruby
70
+ request_id = "test"
71
+ gc_id = "test_gc_id"
72
+ httpable = HTTP # or HTTParty- whatever library you're using that has .post
73
+ request = AGCOD::CancelGiftCard.new(httpable, request_id, gc_id)
74
+
75
+ # When failed
76
+ unless request.success?
77
+ request.error_message # => Error response from AGCOD service
78
+ end
79
+ ```
80
+
81
+ #### Get Gift Code/Card activities
82
+
83
+ ```ruby
84
+ request_id = "test"
85
+ start_time = Time.now - 86400
86
+ end_time = Time.now
87
+ page = 1
88
+ per_page = 100
89
+ show_no_ops = false # Whether or not to show activities with no operation
90
+ httpable = HTTP # or HTTParty- whatever library you're using that has .post
91
+ request = AGCOD::GiftCardActivityList.new(httpable, request_id, start_time, end_time, page, per_page, show_no_ops)
92
+
93
+ if request.success?
94
+ request.results.each do |activity|
95
+ activity.status # => SUCCESS, FAILURE, RESEND
96
+ activity.created_at
97
+ activity.type
98
+ activity.card_number
99
+ activity.amount
100
+ activity.error_code
101
+ activity.gc_id
102
+ activity.partner_id
103
+ activity.request_id
104
+ end
105
+ else
106
+ request.error_message # => Error response from AGCOD service
107
+ end
108
+ ```
109
+ ## Contributing
110
+
111
+ 1. Fork it ( https://github.com/[my-github-username]/aws_agcod/fork )
112
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
113
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
114
+ 4. Push to the branch (`git push origin my-new-feature`)
115
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ task default: :spec
5
+
6
+ RSpec::Core::RakeTask.new
7
+
data/lib/aws_agcod.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "aws_agcod/version"
2
+ require "aws_agcod/config"
3
+ require "aws_agcod/create_gift_card"
4
+ require "aws_agcod/cancel_gift_card"
5
+ require "aws_agcod/gift_card_activity_list"
6
+
7
+ module AGCOD
8
+ def self.configure(&block)
9
+ @config = Config.new
10
+
11
+ yield @config
12
+
13
+ nil
14
+ end
15
+
16
+ def self.config
17
+ @config
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ require 'aws_agcod/request'
2
+
3
+ module AGCOD
4
+ class AvailableFunds
5
+ attr_reader :currency_code, :amount
6
+
7
+ def initialize(payload)
8
+ @currency_code = payload['currencyCode']
9
+ @amount = payload['amount']
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,16 @@
1
+ require "aws_agcod/request"
2
+
3
+ module AGCOD
4
+ class CancelGiftCard
5
+ extend Forwardable
6
+
7
+ def_delegators :@response, :status, :success?, :error_message
8
+
9
+ def initialize(httpable, request_id, gc_id)
10
+ @response = Request.new(httpable,"CancelGiftCard",
11
+ "creationRequestId" => request_id,
12
+ "gcId" => gc_id
13
+ ).response
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ module AGCOD
2
+ class Config
3
+ attr_writer :uri
4
+ attr_accessor :access_key,
5
+ :secret_key,
6
+ :partner_id,
7
+ :region,
8
+ :production
9
+
10
+ URI = {
11
+ sandbox: "https://agcod-v2-gamma.amazon.com",
12
+ production: "https://agcod-v2.amazon.com"
13
+ }
14
+
15
+ def initialize
16
+ # API defaults
17
+ @production = false
18
+ @region = "us-east-1"
19
+ end
20
+
21
+ def uri
22
+ return @uri if @uri
23
+
24
+ production ? URI[:production] : URI[:sandbox]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ require "aws_agcod/request"
2
+
3
+ module AGCOD
4
+ class CreateGiftCardError < StandardError; end
5
+
6
+ class CreateGiftCard
7
+ extend Forwardable
8
+
9
+ CURRENCIES = %w(USD EUR JPY CNY CAD)
10
+
11
+ def_delegators :@response, :status, :success?, :error_message
12
+
13
+ def initialize(httpable, request_id, amount, currency = "USD")
14
+ unless CURRENCIES.include?(currency.to_s)
15
+ raise CreateGiftCardError, "Currency #{currency} not supported, available types are #{CURRENCIES.join(", ")}"
16
+ end
17
+
18
+ @response = Request.new(httpable, "CreateGiftCard",
19
+ "creationRequestId" => request_id,
20
+ "value" => {
21
+ "currencyCode" => currency,
22
+ "amount" => amount
23
+ }
24
+ ).response
25
+ end
26
+
27
+ def claim_code
28
+ @response.payload["gcClaimCode"]
29
+ end
30
+
31
+ def expiration_date
32
+ @expiration_date ||= Time.parse @response.payload["gcExpirationDate"]
33
+ end
34
+
35
+ def gc_id
36
+ @response.payload["gcId"]
37
+ end
38
+
39
+ def request_id
40
+ @response.payload["creationRequestId"]
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ require 'aws_agcod/request'
2
+ require 'aws_agcod/available_funds'
3
+
4
+ module AGCOD
5
+ class GetAvailableFunds
6
+ extend Forwardable
7
+
8
+ def initialize(httpable, partner_id)
9
+ @response = Request.new(httpable, 'GetAvailableFunds',
10
+ 'partnerId' => partner_id,
11
+ ).response
12
+ end
13
+
14
+ def available_funds
15
+ @response.payload['availableFunds'].map { |payload| AGCOD::AvailableFunds.new(payload) }
16
+ end
17
+
18
+ def timestamp
19
+ Time.parse(@response.payload['timestamp'])
20
+ end
21
+
22
+ def status
23
+ @response.payload['status']
24
+ end
25
+
26
+ def success?
27
+ @response.payload['status'] == 'SUCCESS'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,54 @@
1
+ require "aws_agcod/request"
2
+
3
+ module AGCOD
4
+ class GiftCardActivityListError < StandardError; end
5
+
6
+ class GiftCardActivity
7
+ attr_reader :status, :created_at, :type, :card_number, :amount, :error_code,
8
+ :gc_id, :partner_id, :request_id
9
+
10
+ def initialize(payload)
11
+ @payload = payload
12
+ @status = payload["activityStatus"]
13
+ @created_at = payload["activityTime"]
14
+ @type = payload["activityType"]
15
+ @card_number = payload["cardNumber"]
16
+ @amount = payload["cardValue"]["amount"] if payload["cardValue"]
17
+ @error_code = payload["failureCode"]
18
+ @gc_id = payload["gcId"]
19
+ @partner_id = payload["partnerId"]
20
+ @request_id = payload["requestId"]
21
+ end
22
+
23
+ def is_real?
24
+ @payload["isRealOp"] == "true"
25
+ end
26
+ end
27
+
28
+ class GiftCardActivityList
29
+ extend Forwardable
30
+
31
+ LIMIT = 1000 # limit per request
32
+ TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
33
+
34
+ def_delegators :@response, :success?, :error_message
35
+
36
+ def initialize(httpable, request_id, start_time, end_time, page = 1, per_page = 100, show_no_ops = false)
37
+ raise GiftCardActivityListError, "Only #{LIMIT} records allowed per request." if per_page > LIMIT
38
+
39
+ @response = Request.new(httpable,"GetGiftCardActivityPage",
40
+ "requestId" => request_id,
41
+ "utcStartDate" => start_time.strftime(TIME_FORMAT),
42
+ "utcEndDate" => end_time.strftime(TIME_FORMAT),
43
+ "pageIndex" => (page - 1) * per_page,
44
+ "pageSize" => per_page,
45
+ "showNoOps" => show_no_ops
46
+ ).response
47
+ end
48
+
49
+ def results
50
+ @response.payload["cardActivityList"].map { |payload| GiftCardActivity.new(payload) }
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,61 @@
1
+ require "aws_agcod/signature"
2
+ require "aws_agcod/response"
3
+ require "http"
4
+ require "yaml"
5
+
6
+ module AGCOD
7
+ class Request
8
+ TIME_FORMAT = "%Y%m%dT%H%M%SZ"
9
+ MOCK_REQUEST_IDS = %w(F0000 F2005)
10
+
11
+ attr_reader :response
12
+
13
+ def initialize(httpable, action, params) # httpable is anything that can have post called upon it; this allows passing in a proxy
14
+ @action = action
15
+ @params = sanitized_params(params)
16
+
17
+ @response = Response.new(httpable.post(uri, body: body, headers: signed_headers).body)
18
+ end
19
+
20
+ private
21
+
22
+ def signed_headers
23
+ time = Time.now.utc
24
+
25
+ headers = {
26
+ "content-type" => "application/json",
27
+ "x-amz-date" => time.strftime(TIME_FORMAT),
28
+ "accept" => "application/json",
29
+ "host" => uri.host,
30
+ "x-amz-target" => "com.amazonaws.agcod.AGCODService.#{@action}",
31
+ "date" => time.to_s
32
+ }
33
+
34
+ Signature.new(AGCOD.config).sign(uri, headers, body)
35
+ end
36
+
37
+ def uri
38
+ @uri ||= URI("#{AGCOD.config.uri}/#{@action}")
39
+ end
40
+
41
+ def body
42
+ @body ||= @params.merge(
43
+ "partnerId" => AGCOD.config.partner_id
44
+ ).to_json
45
+ end
46
+
47
+ def sanitized_params(params)
48
+ # Prefix partner_id when it's not given as part of request_id for creationRequestId and it's not a mock request_id
49
+ if params["creationRequestId"] && !(params["creationRequestId"] =~ /#{AGCOD.config.partner_id}/) && !(MOCK_REQUEST_IDS.member?(params["creationRequestId"]))
50
+ params["creationRequestId"] = "#{AGCOD.config.partner_id}#{params["creationRequestId"]}"
51
+ end
52
+
53
+ # Remove partner_id when it's prefixed in requestId
54
+ if params["requestId"] && !!(params["requestId"] =~ /^#{AGCOD.config.partner_id}/)
55
+ params["requestId"].sub!(/^#{AGCOD.config.partner_id}/, "")
56
+ end
57
+
58
+ params
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,31 @@
1
+ require "json"
2
+
3
+ module AGCOD
4
+ class Response
5
+ attr_reader :status, :payload
6
+
7
+ def initialize(raw_json)
8
+ @payload = JSON.parse(raw_json)
9
+
10
+ # All status:
11
+ # SUCCESS -- Operation succeeded
12
+ # FAILURE -- Operation failed
13
+ # RESEND -- A temporary/recoverable system failure that can be resolved by the partner retrying the request
14
+ @status = if payload["status"]
15
+ payload["status"]
16
+ elsif payload["agcodResponse"]
17
+ payload["agcodResponse"]["status"]
18
+ else
19
+ "FAILURE"
20
+ end
21
+ end
22
+
23
+ def success?
24
+ status == "SUCCESS"
25
+ end
26
+
27
+ def error_message
28
+ "#{payload["errorCode"]} #{payload["errorType"]} - #{payload["message"]}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,95 @@
1
+ # Currently AGCOD v2 uses v4 Signature for it's authentication,
2
+ # this class generates signed headers for making proper request to AGCOD service.
3
+ #
4
+ # Based on https://github.com/ifeelgoods/aws4/blob/master/lib/aws4/signer.rb
5
+ require "openssl"
6
+ require "uri"
7
+ require "pathname"
8
+
9
+ module AGCOD
10
+ class Signature
11
+ SERVICE = "AGCODService"
12
+
13
+ def initialize(credentials)
14
+ @access_key = credentials.access_key
15
+ @secret_key = credentials.secret_key
16
+ @region = credentials.region || DEFAULT_REGION
17
+ end
18
+
19
+ def sign(uri, headers, body = "")
20
+ @uri = uri
21
+ @headers = headers
22
+ @body = body
23
+ @date = headers["x-amz-date"]
24
+
25
+ signed_headers = headers.dup
26
+ signed_headers["Authorization"] = authorization
27
+
28
+ signed_headers
29
+ end
30
+
31
+ private
32
+
33
+ def authorization
34
+ [
35
+ "AWS4-HMAC-SHA256 Credential=#{@access_key}/#{credential_string}",
36
+ "SignedHeaders=#{@headers.keys.map(&:downcase).sort.join(";")}",
37
+ "Signature=#{signature}"
38
+ ].join(", ")
39
+ end
40
+
41
+ # Reference http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
42
+ def signature
43
+ k_date = hmac("AWS4" + @secret_key, @date[0, 8])
44
+ k_region = hmac(k_date, @region)
45
+ k_service = hmac(k_region, SERVICE)
46
+ k_credentials = hmac(k_service, "aws4_request")
47
+ hexhmac(k_credentials, string_to_sign)
48
+ end
49
+
50
+ # Reference http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
51
+ def string_to_sign
52
+ @string_to_sign ||= [
53
+ "AWS4-HMAC-SHA256", # Algorithm
54
+ @date, # RequestDate
55
+ credential_string, # CredentialScope
56
+ hexdigest(canonical_request) # HashedCanonicalRequest
57
+ ].join("\n")
58
+ end
59
+
60
+ def credential_string
61
+ @credential_string ||= [@date[0, 8], @region, SERVICE, "aws4_request"].join("/")
62
+ end
63
+
64
+ # Reference http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
65
+ def canonical_request
66
+ @canonical_request ||= [
67
+ "POST", # HTTPRequestMethod
68
+ Pathname.new(@uri.path).cleanpath.to_s, # CanonicalURI
69
+ @uri.query, # CanonicalQueryString
70
+ @headers.sort.map { |k, v| [k.downcase, v.strip].join(":") }.join("\n") + "\n", # CanonicalHeaders
71
+ @headers.sort.map { |k, v| k.downcase }.join(";"), # SignedHeaders
72
+ hexdigest(@body) # HexEncode(Hash(RequestPayload))
73
+ ].join("\n")
74
+ end
75
+
76
+ # Hexdigest simply produces an ascii safe way
77
+ # to view the bytes produced from the hash algorithm.
78
+ # It takes the hex representation of each byte
79
+ # and concatenates them together to produce a string
80
+ def hexdigest(value)
81
+ Digest::SHA256.new.update(value).hexdigest
82
+ end
83
+
84
+ # Hash-based message authentication code (HMAC)
85
+ # is a mechanism for calculating a message authentication code
86
+ # involving a hash function in combination with a secret key
87
+ def hmac(key, value)
88
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), key, value)
89
+ end
90
+
91
+ def hexhmac(key, value)
92
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), key, value)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,3 @@
1
+ module AGCOD
2
+ VERSION = "1.1.0"
3
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+ require "aws_agcod/cancel_gift_card"
3
+
4
+ describe AGCOD::CancelGiftCard do
5
+ let(:partner_id) { "Testa" }
6
+ let(:response) { spy }
7
+ let(:httpable) { HTTP }
8
+
9
+ before do
10
+ AGCOD.configure do |config|
11
+ config.partner_id = partner_id
12
+ end
13
+ end
14
+
15
+ context ".new" do
16
+ let(:request_id) { "test1" }
17
+ let(:gc_id) { "FOO" }
18
+
19
+ it "makes cancel request" do
20
+ expect(AGCOD::Request).to receive(:new) do |_, action, params|
21
+ expect(action).to eq("CancelGiftCard")
22
+ expect(params["creationRequestId"]).to eq(request_id)
23
+ expect(params["gcId"]).to eq(gc_id)
24
+ end.and_return(response)
25
+
26
+ AGCOD::CancelGiftCard.new(httpable, request_id, gc_id)
27
+ end
28
+ end
29
+
30
+ shared_context "request with response" do
31
+ let(:gc_id) { "BAR" }
32
+ let(:creation_request_id) { "BAZ" }
33
+ let(:status) { "SUCCESS" }
34
+ let(:request) { AGCOD::CancelGiftCard.new(httpable, creation_request_id, gc_id) }
35
+
36
+ before do
37
+ allow(AGCOD::Request).to receive(:new) { double(response: response) }
38
+ allow(response).to receive(:status) { status }
39
+ end
40
+ end
41
+
42
+ context "#status" do
43
+ include_context "request with response"
44
+
45
+ it "returns the response status" do
46
+ expect(request.status).to eq(status)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+ require "aws_agcod/config"
3
+
4
+ describe AGCOD::Config do
5
+ let(:config) { AGCOD::Config.new }
6
+
7
+ context ".new" do
8
+ it "sets default uri and region" do
9
+ expect(config.uri).not_to be_nil
10
+ expect(config.region).not_to be_nil
11
+ expect(config.production).to eq(false)
12
+ end
13
+ end
14
+
15
+ context "#uri" do
16
+ context "when uri is set" do
17
+ before do
18
+ config.uri = "https://custom-uri.example.com"
19
+ end
20
+
21
+ it "returns the custom uri" do
22
+ expect(config.uri).to eq("https://custom-uri.example.com")
23
+ end
24
+ end
25
+
26
+ context "when uri is not set" do
27
+ context "when production is enabled" do
28
+ before do
29
+ config.production = true
30
+ end
31
+
32
+ it "returns the production uri" do
33
+ expect(config.uri).to eq(AGCOD::Config::URI[:production])
34
+ end
35
+ end
36
+
37
+ context "when production is disabled" do
38
+ it "returns the sandbox uri" do
39
+ expect(config.uri).to eq(AGCOD::Config::URI[:sandbox])
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,104 @@
1
+ require "spec_helper"
2
+ require "aws_agcod/create_gift_card"
3
+
4
+ describe AGCOD::CreateGiftCard do
5
+ let(:partner_id) { "Testa" }
6
+ let(:request_id) { "test1" }
7
+ let(:amount) { 10 }
8
+ let(:currency) { AGCOD::CreateGiftCard::CURRENCIES.first }
9
+ let(:response) { spy }
10
+ let(:httpable) { HTTP }
11
+
12
+ before do
13
+ AGCOD.configure do |config|
14
+ config.partner_id = partner_id
15
+ end
16
+ end
17
+
18
+ context ".new" do
19
+ context "when currency available" do
20
+ it "makes create request" do
21
+ expect(AGCOD::Request).to receive(:new) do |httpable, action, params|
22
+ expect(httpable).to eq(HTTP)
23
+ expect(action).to eq("CreateGiftCard")
24
+ expect(params["creationRequestId"]).to eq(request_id)
25
+ expect(params["value"]).to eq(
26
+ "currencyCode" => currency,
27
+ "amount" => amount
28
+ )
29
+ end.and_return(response)
30
+
31
+ AGCOD::CreateGiftCard.new(httpable, request_id, amount, currency)
32
+ end
33
+ end
34
+
35
+ context "when currency not available" do
36
+ let(:currency) { "NOTEXIST" }
37
+
38
+ it "raises error" do
39
+ expect {
40
+ AGCOD::CreateGiftCard.new(httpable, request_id, amount, currency)
41
+ }.to raise_error(
42
+ AGCOD::CreateGiftCardError,
43
+ "Currency #{currency} not supported, available types are #{AGCOD::CreateGiftCard::CURRENCIES.join(", ")}"
44
+ )
45
+ end
46
+ end
47
+ end
48
+
49
+ shared_context "request with response" do
50
+ let(:claim_code) { "FOO" }
51
+ let(:expiration_date) { "Wed Mar 12 22:59:59 UTC 2025" }
52
+ let(:gc_id) { "BAR" }
53
+ let(:creation_request_id) { "BAZ" }
54
+ let(:status) { "SUCCESS" }
55
+ let(:payload) { {"gcClaimCode" => claim_code, "gcId" => gc_id, "creationRequestId" => creation_request_id, "gcExpirationDate" => expiration_date} }
56
+ let(:request) { AGCOD::CreateGiftCard.new(httpable, request_id, amount, currency) }
57
+
58
+ before do
59
+ allow(AGCOD::Request).to receive(:new) { double(response: response) }
60
+ allow(response).to receive(:payload) { payload }
61
+ allow(response).to receive(:status) { status }
62
+ end
63
+ end
64
+
65
+ context "#claim_code" do
66
+ include_context "request with response"
67
+
68
+ it "returns claim_code" do
69
+ expect(request.claim_code).to eq(claim_code)
70
+ end
71
+ end
72
+
73
+ context "#expiration_date" do
74
+ include_context "request with response"
75
+
76
+ it "returns expiration_date" do
77
+ expect(request.expiration_date).to eq(Time.parse expiration_date)
78
+ end
79
+ end
80
+
81
+ context "#gc_id" do
82
+ include_context "request with response"
83
+
84
+ it "returns gc_id" do
85
+ expect(request.gc_id).to eq(gc_id)
86
+ end
87
+ end
88
+
89
+ context "#request_id" do
90
+ include_context "request with response"
91
+
92
+ it "returns creation request_id" do
93
+ expect(request.request_id).to eq(creation_request_id)
94
+ end
95
+ end
96
+
97
+ context "#status" do
98
+ include_context "request with response"
99
+
100
+ it "returns the response status" do
101
+ expect(request.status).to eq(status)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+ require 'aws_agcod/get_available_funds'
3
+ require 'ostruct'
4
+
5
+ describe AGCOD::GetAvailableFunds do
6
+ let(:partner_id) { 'Testa' }
7
+ let(:currency) { AGCOD::CreateGiftCard::CURRENCIES.first }
8
+ let(:httpable) { HTTP }
9
+ let(:timestamp) { '20180323T233815Z' }
10
+ let(:status) { 'SUCCESS' }
11
+ let(:currency_code) { 'USD' }
12
+ let(:amount) { 0.0 }
13
+ OpenStruct.new(
14
+ first_name: OpenStruct.new({
15
+ primary: OpenStruct.new({
16
+ first_name: 'Albus',
17
+ }),
18
+ }),
19
+ )
20
+ let(:payload) { { "availableFunds" => { "amount" => amount, "currencyCode" => currency_code }, "status" => status, "timestamp" => timestamp } }
21
+
22
+ let(:request_response) {
23
+ OpenStruct.new(response:
24
+ OpenStruct.new(payload:
25
+ {
26
+ "availableFunds" => [
27
+ {
28
+ "amount" => amount,
29
+ "currencyCode" => currency_code
30
+ }
31
+ ],
32
+ "status" => status,
33
+ "timestamp" => timestamp
34
+ }
35
+ )
36
+ )
37
+ }
38
+
39
+ subject { described_class.new(httpable, partner_id) }
40
+
41
+ before do
42
+ AGCOD.configure do |config|
43
+ config.partner_id = partner_id
44
+ end
45
+ end
46
+
47
+ context '.new' do
48
+ it 'makes request' do
49
+ expect(AGCOD::Request).to receive(:new) do |httpable, action, params|
50
+ expect(httpable).to eq(HTTP)
51
+ expect(action).to eq('GetAvailableFunds')
52
+ expect(params['partnerId']).to eq(partner_id)
53
+ end.and_return(request_response)
54
+
55
+ expect(subject.available_funds.first.currency_code).to eq currency
56
+ expect(subject.available_funds.first.amount).to eq amount
57
+ expect(subject.timestamp).to eq Time.parse(timestamp)
58
+ expect(subject.status).to eq status
59
+ expect(subject.success?).to eq true
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,75 @@
1
+ require "spec_helper"
2
+ require "aws_agcod/gift_card_activity_list"
3
+
4
+ describe AGCOD::GiftCardActivityList do
5
+ let(:partner_id) { "Testa" }
6
+ let(:request_id) { "test1" }
7
+ let(:start_time) { double("start_time") }
8
+ let(:end_time) { double("end_time") }
9
+ let(:page) { 1 }
10
+ let(:per_page) { AGCOD::GiftCardActivityList::LIMIT }
11
+ let(:show_no_ops) { true }
12
+ let(:response) { spy }
13
+ let(:httpable) { HTTP }
14
+
15
+ before do
16
+ AGCOD.configure do |config|
17
+ config.partner_id = partner_id
18
+ end
19
+ end
20
+
21
+ context ".new" do
22
+ it "makes request" do
23
+ expect(start_time).to receive(:strftime).with(
24
+ AGCOD::GiftCardActivityList::TIME_FORMAT
25
+ ).and_return(start_time)
26
+
27
+ expect(end_time).to receive(:strftime).with(
28
+ AGCOD::GiftCardActivityList::TIME_FORMAT
29
+ ).and_return(end_time)
30
+
31
+ expect(AGCOD::Request).to receive(:new) do |_, action, params|
32
+ expect(action).to eq("GetGiftCardActivityPage")
33
+ expect(params["requestId"]).to eq(request_id)
34
+ expect(params["utcStartDate"]).to eq(start_time)
35
+ expect(params["utcEndDate"]).to eq(end_time)
36
+ expect(params["pageIndex"]).to eq((page - 1) * per_page)
37
+ expect(params["pageSize"]).to eq(per_page)
38
+ expect(params["showNoOps"]).to eq(show_no_ops)
39
+ end.and_return(response)
40
+
41
+ AGCOD::GiftCardActivityList.new(httpable, request_id, start_time, end_time, page, per_page, show_no_ops)
42
+ end
43
+
44
+ context "when request per_page reaches limit" do
45
+ let(:per_page) { AGCOD::GiftCardActivityList::LIMIT + 1 }
46
+
47
+ it "raises error" do
48
+ expect {
49
+ AGCOD::GiftCardActivityList.new(httpable, request_id, start_time, end_time, page, per_page, show_no_ops)
50
+ }.to raise_error(
51
+ AGCOD::GiftCardActivityListError,
52
+ "Only #{AGCOD::GiftCardActivityList::LIMIT} records allowed per request."
53
+ )
54
+ end
55
+ end
56
+ end
57
+
58
+ context "#results" do
59
+ let(:payload) { { "cardActivityList" => [spy] } }
60
+ let(:request) { AGCOD::GiftCardActivityList.new(httpable, request_id, start_time, end_time, page, per_page, show_no_ops) }
61
+
62
+ before do
63
+ allow(start_time).to receive(:strftime)
64
+ allow(end_time).to receive(:strftime)
65
+ allow(AGCOD::Request).to receive(:new) { double(response: response) }
66
+ allow(response).to receive(:payload) { payload }
67
+ end
68
+
69
+ it "returns GiftCardActivity instances" do
70
+ request.results.each do |item|
71
+ expect(item).to be_a(AGCOD::GiftCardActivity)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+ require "aws_agcod/request"
3
+
4
+ describe AGCOD::Request do
5
+ let(:action) { "Foo" }
6
+ let(:params) { {} }
7
+ let(:signature) { double("Signature") }
8
+ let(:signed_headers) { double("signed_headers") }
9
+ let(:base_uri) { "https://example.com" }
10
+ let(:partner_id) { "BAR" }
11
+ let(:config) { double(uri: base_uri, partner_id: partner_id) }
12
+ let(:httpable) { HTTP }
13
+
14
+ context "#new" do
15
+ before do
16
+ allow(AGCOD).to receive(:config) { config }
17
+ allow(AGCOD::Signature).to receive(:new).with(config) { signature }
18
+ end
19
+
20
+ context "with creationRequestId as special testing value" do
21
+ let(:params) { { creationRequestId: creationRequestId } }
22
+
23
+ subject { AGCOD::Request.new(httpable, action, params) }
24
+
25
+ before do
26
+ allow(signature).to receive(:sign).and_return(signed_headers)
27
+ end
28
+
29
+ context "with special creationRequestId F0000" do
30
+ let(:creationRequestId) { "F0000" }
31
+
32
+ it "does not add partnerId to creationRequestId" do
33
+ expect(HTTP).to receive(:post) do |_, options|
34
+ expect(JSON.parse(options[:body])["creationRequestId"]).to eq("F0000")
35
+ end.and_return(double(body: params.to_json))
36
+ subject
37
+ end
38
+ end
39
+
40
+ context "with special creationRequestId F2005" do
41
+ let(:creationRequestId) { "F2005" }
42
+
43
+ it "does not add partnerId to creationRequestId" do
44
+ expect(HTTP).to receive(:post) do |_, options|
45
+ expect(JSON.parse(options[:body])["creationRequestId"]).to eq("F2005")
46
+ end.and_return(double(body: params.to_json))
47
+ subject
48
+ end
49
+ end
50
+ end
51
+
52
+ it "sends post request to endpoint uri" do
53
+ expect(signature).to receive(:sign) do |uri, headers, body|
54
+ expect(uri).to eq(URI("#{base_uri}/#{action}"))
55
+ expect(headers.keys).to match_array(%w(content-type x-amz-date accept host x-amz-target date))
56
+ expect(headers["content-type"]).to eq("application/json")
57
+ expect(headers["x-amz-target"]).to eq("com.amazonaws.agcod.AGCODService.#{action}")
58
+ expect(JSON.parse(body)["partnerId"]).to eq(partner_id)
59
+ end.and_return(signed_headers)
60
+
61
+ expect(HTTP).to receive(:post) do |uri, options|
62
+ expect(uri).to eq(URI("#{base_uri}/#{action}"))
63
+ expect(JSON.parse(options[:body])["partnerId"]).to eq(partner_id)
64
+ expect(options[:headers]).to eq(signed_headers)
65
+ end.and_return(double(body: params.to_json))
66
+
67
+ AGCOD::Request.new(httpable, action, params)
68
+ end
69
+
70
+ it "sets response" do
71
+ expect(signature).to receive(:sign) { signed_headers }
72
+ expect(HTTP).to receive(:post) { (double(body: params.to_json)) }
73
+
74
+ response = AGCOD::Request.new(httpable, action, params).response
75
+
76
+ expect(response).to be_a(AGCOD::Response)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,66 @@
1
+ require "spec_helper"
2
+ require "aws_agcod/response"
3
+
4
+ describe AGCOD::Response do
5
+ let(:payload) { { foo: "bar" }.to_json }
6
+
7
+ context "#new" do
8
+ it "sets payload and status" do
9
+ response = AGCOD::Response.new(payload)
10
+
11
+ expect(response.payload).not_to be_nil
12
+ expect(response.status).not_to be_nil
13
+ end
14
+
15
+ context "when no status in payload" do
16
+ it "sets status to failure" do
17
+ expect(AGCOD::Response.new(payload).status).to eq("FAILURE")
18
+ end
19
+ end
20
+
21
+ context "when has status in payload" do
22
+ let(:status) { "foo" }
23
+ let(:payload) { { status: status }.to_json }
24
+
25
+ it "sets status as payload's status" do
26
+ expect(AGCOD::Response.new(payload).status).to eq(status)
27
+ end
28
+ end
29
+
30
+ context "when has agcodResponse in payload" do
31
+ let(:status) { "foo" }
32
+ let(:payload) { { agcodResponse: { status: status } }.to_json }
33
+
34
+ it "sets status as agcodResponse's status" do
35
+ expect(AGCOD::Response.new(payload).status).to eq(status)
36
+ end
37
+ end
38
+ end
39
+
40
+ context "success?" do
41
+ context "when status is SUCCESS" do
42
+ let(:payload) { { status: "SUCCESS" }.to_json }
43
+
44
+ it "returns true" do
45
+ expect(AGCOD::Response.new(payload).success?).to be_truthy
46
+ end
47
+ end
48
+
49
+ context "when status is not SUCCESS" do
50
+ it "returns false" do
51
+ expect(AGCOD::Response.new(payload).success?).to be_falsey
52
+ end
53
+ end
54
+ end
55
+
56
+ context "error_message" do
57
+ let(:error_code) { "foo" }
58
+ let(:error_type) { "bar" }
59
+ let(:error_message) { "baz" }
60
+ let(:payload) { { errorCode: error_code, errorType: error_type, message: error_message }.to_json }
61
+
62
+ it "composes error message by error code, type, and message from payload" do
63
+ expect(AGCOD::Response.new(payload).error_message).to eq("#{error_code} #{error_type} - #{error_message}")
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+ require "aws_agcod"
3
+
4
+ describe AGCOD do
5
+ context ".configure" do
6
+ it "yields config" do
7
+ AGCOD.configure do |config|
8
+ expect(config).to be_an_instance_of(AGCOD::Config)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ require "pathname"
2
+ require "http"
3
+
4
+ ROOT_PATH = Pathname.new(__FILE__).join("../..").expand_path
5
+ $LOAD_PATH.unshift(ROOT_PATH.join("lib"))
6
+
7
+ RSpec.configure do |config|
8
+ # Run specs in random order to surface order dependencies. If you find an
9
+ # order dependency and want to debug it, you can fix the order by providing
10
+ # the seed, which is printed after each run.
11
+ # --seed 1234
12
+ config.order = "random"
13
+
14
+ # Disable the should syntax compeletely; we use the expect syntax only.
15
+ config.expect_with :rspec do |c|
16
+ c.syntax = :expect
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws_agcod_2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Xenor Chang
8
+ - compwron
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-03-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: http
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '12'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '12'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3'
70
+ description: |-
71
+ Amazon Gift Code On Demand (AGCOD) API v2 implementation for
72
+ distribute Amazon gift cards (gift codes) instantly in any denomination
73
+ email:
74
+ - xenor@listia.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/aws_agcod.rb
83
+ - lib/aws_agcod/available_funds.rb
84
+ - lib/aws_agcod/cancel_gift_card.rb
85
+ - lib/aws_agcod/config.rb
86
+ - lib/aws_agcod/create_gift_card.rb
87
+ - lib/aws_agcod/get_available_funds.rb
88
+ - lib/aws_agcod/gift_card_activity_list.rb
89
+ - lib/aws_agcod/request.rb
90
+ - lib/aws_agcod/response.rb
91
+ - lib/aws_agcod/signature.rb
92
+ - lib/aws_agcod/version.rb
93
+ - spec/aws_agcod/cancel_gift_card_spec.rb
94
+ - spec/aws_agcod/config_spec.rb
95
+ - spec/aws_agcod/create_gift_card_spec.rb
96
+ - spec/aws_agcod/get_available_funds_spec.rb
97
+ - spec/aws_agcod/gift_card_activity_list_spec.rb
98
+ - spec/aws_agcod/request_spec.rb
99
+ - spec/aws_agcod/response_spec.rb
100
+ - spec/aws_agcod_spec.rb
101
+ - spec/spec_helper.rb
102
+ homepage: https://github.com/compwron/aws_agcod
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.7.6
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: AWS AGCOD API v2 endpoints implementation
126
+ test_files:
127
+ - spec/aws_agcod/cancel_gift_card_spec.rb
128
+ - spec/aws_agcod/config_spec.rb
129
+ - spec/aws_agcod/create_gift_card_spec.rb
130
+ - spec/aws_agcod/get_available_funds_spec.rb
131
+ - spec/aws_agcod/gift_card_activity_list_spec.rb
132
+ - spec/aws_agcod/request_spec.rb
133
+ - spec/aws_agcod/response_spec.rb
134
+ - spec/aws_agcod_spec.rb
135
+ - spec/spec_helper.rb