├── .gitignore ├── .rspec ├── .ruby-gemset ├── .ruby-version ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── aws_agcod.gemspec ├── lib ├── aws_agcod.rb └── aws_agcod │ ├── cancel_gift_card.rb │ ├── config.rb │ ├── create_gift_card.rb │ ├── gift_card_activity_list.rb │ ├── request.rb │ ├── response.rb │ ├── signature.rb │ └── version.rb └── spec ├── aws_agcod ├── cancel_gift_card_spec.rb ├── config_spec.rb ├── create_gift_card_spec.rb ├── gift_card_activity_list_spec.rb ├── request_spec.rb └── response_spec.rb ├── aws_agcod_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | .idea/* 15 | mkmf.log 16 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | aws_agcod 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.1.2 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | before_install: gem install bundler 4 | rvm: 5 | - 2.2.0 6 | - 2.1.0 7 | - 2.0.0 8 | - 1.9.3 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in aws_agcod.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AGCOD 2 | 3 | [![Build Status](https://travis-ci.org/listia/aws_agcod.svg?branch=master)](https://travis-ci.org/listia/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 distribute 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 | config.timeout = 30 # default 45 | end 46 | ``` 47 | 48 | #### Create Gift Code/Card 49 | 50 | ```ruby 51 | request_id = "test" 52 | amount = 10 53 | currency = "USD" # default to USD, available types are: USD, EUR, JPY, CNY, CAD 54 | 55 | request = AGCOD::CreateGiftCard.new(request_id, amount, currency) 56 | 57 | # When succeed 58 | if request.success? 59 | request.claim_code # => code for the gift card 60 | request.gc_id # => gift card id 61 | request.request_id # => your request id 62 | else 63 | # When failed 64 | request.error_message # => Error response from AGCOD service 65 | end 66 | ``` 67 | 68 | #### Cancel Gift Code/Card 69 | 70 | ```ruby 71 | request_id = "test" 72 | gc_id = "test_gc_id" 73 | 74 | request = AGCOD::CancelGiftCard.new(request_id, gc_id) 75 | 76 | # When failed 77 | unless request.success? 78 | request.error_message # => Error response from AGCOD service 79 | end 80 | ``` 81 | 82 | #### Get Gift Code/Card activities 83 | 84 | ```ruby 85 | request_id = "test" 86 | start_time = Time.now - 86400 87 | end_time = Time.now 88 | page = 1 89 | per_page = 100 90 | show_no_ops = false # Whether or not to show activities with no operation 91 | 92 | request = AGCOD::GiftCardActivityList.new(request_id, start_time, end_time, page, per_page, show_no_ops) 93 | 94 | if request.success? 95 | request.results.each do |activity| 96 | activity.status # => SUCCESS, FAILURE, RESEND 97 | activity.created_at 98 | activity.type 99 | activity.card_number 100 | activity.amount 101 | activity.error_code 102 | activity.gc_id 103 | activity.partner_id 104 | activity.request_id 105 | end 106 | else 107 | request.error_message # => Error response from AGCOD service 108 | end 109 | ``` 110 | ## Contributing 111 | 112 | 1. Fork it ( https://github.com/[my-github-username]/aws_agcod/fork ) 113 | 2. Create your feature branch (`git checkout -b my-new-feature`) 114 | 3. Commit your changes (`git commit -am 'Add some feature'`) 115 | 4. Push to the branch (`git push origin my-new-feature`) 116 | 5. Create a new Pull Request 117 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | task default: :spec 5 | 6 | RSpec::Core::RakeTask.new 7 | 8 | -------------------------------------------------------------------------------- /aws_agcod.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'aws_agcod/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "aws_agcod" 8 | spec.version = AGCOD::VERSION 9 | spec.authors = ["Xenor Chang"] 10 | spec.email = ["xenor@listia.com"] 11 | spec.summary = %q{AWS AGCOD API v2 endpoints implementation} 12 | spec.description = %q{Amazon Gift Code On Demand (AGCOD) API v2 implementation for 13 | distribute Amazon gift cards (gift codes) instantly in any denomination} 14 | spec.homepage = "https://github.com/listia/aws_agcod" 15 | spec.license = "MIT" 16 | 17 | spec.files = Dir["{lib,spec}/**/*"].select { |f| File.file?(f) } + 18 | %w(LICENSE.txt Rakefile README.md) 19 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 20 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 21 | spec.require_paths = ["lib"] 22 | 23 | spec.add_dependency "httparty", "~> 0.13" 24 | 25 | spec.add_development_dependency "bundler", "~> 1.7" 26 | spec.add_development_dependency "rake", "~> 10.0" 27 | spec.add_development_dependency "rspec", '~> 3.1', '>= 3.1.0' 28 | end 29 | -------------------------------------------------------------------------------- /lib/aws_agcod.rb: -------------------------------------------------------------------------------- 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 20 | -------------------------------------------------------------------------------- /lib/aws_agcod/cancel_gift_card.rb: -------------------------------------------------------------------------------- 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(request_id, gc_id) 10 | @response = Request.new("CancelGiftCard", 11 | "creationRequestId" => request_id, 12 | "gcId" => gc_id 13 | ).response 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/aws_agcod/config.rb: -------------------------------------------------------------------------------- 1 | module AGCOD 2 | class Config 3 | attr_writer :uri 4 | attr_accessor :access_key, 5 | :secret_key, 6 | :partner_id, 7 | :region, 8 | :timeout, 9 | :production 10 | 11 | URI = { 12 | sandbox: "https://agcod-v2-gamma.amazon.com", 13 | production: "https://agcod-v2.amazon.com" 14 | } 15 | 16 | def initialize 17 | # API defaults 18 | @production = false 19 | @region = "us-east-1" 20 | @timeout = 30 21 | end 22 | 23 | def uri 24 | return @uri if @uri 25 | 26 | production ? URI[:production] : URI[:sandbox] 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/aws_agcod/create_gift_card.rb: -------------------------------------------------------------------------------- 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 GBP).freeze 10 | 11 | def_delegators :@response, :status, :success?, :error_message 12 | 13 | def initialize(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("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 44 | -------------------------------------------------------------------------------- /lib/aws_agcod/gift_card_activity_list.rb: -------------------------------------------------------------------------------- 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(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("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 | 55 | -------------------------------------------------------------------------------- /lib/aws_agcod/request.rb: -------------------------------------------------------------------------------- 1 | require "aws_agcod/signature" 2 | require "aws_agcod/response" 3 | require "httparty" 4 | require "yaml" 5 | 6 | module AGCOD 7 | class Request 8 | TIME_FORMAT = "%Y%m%dT%H%M%SZ".freeze 9 | MOCK_REQUEST_IDS = %w(F0000 F2005).freeze 10 | 11 | attr_reader :response 12 | 13 | def initialize(action, params) 14 | @action = action 15 | @params = sanitized_params(params) 16 | 17 | @response = Response.new(HTTParty.post(uri, body: body, headers: signed_headers, timeout: AGCOD.config.timeout).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" => partner_id 44 | ).to_json 45 | end 46 | 47 | def sanitized_params(params) 48 | # Prefix partner_id in creationRequestId when it's not given as part of request_id, and it's not a mocked request_id. 49 | if params["creationRequestId"] && 50 | !(params["creationRequestId"] =~ /#{partner_id}/) && 51 | !(MOCK_REQUEST_IDS.member?(params["creationRequestId"])) 52 | 53 | params["creationRequestId"] = "#{partner_id}#{params["creationRequestId"]}" 54 | end 55 | 56 | # Remove partner_id when it's prefixed in requestId 57 | if params["requestId"] && !!(params["requestId"] =~ /^#{partner_id}/) 58 | params["requestId"].sub!(/^#{partner_id}/, "") 59 | end 60 | 61 | params 62 | end 63 | 64 | def partner_id 65 | @partner_id ||= AGCOD.config.partner_id 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/aws_agcod/response.rb: -------------------------------------------------------------------------------- 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 32 | -------------------------------------------------------------------------------- /lib/aws_agcod/signature.rb: -------------------------------------------------------------------------------- 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 96 | -------------------------------------------------------------------------------- /lib/aws_agcod/version.rb: -------------------------------------------------------------------------------- 1 | module AGCOD 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /spec/aws_agcod/cancel_gift_card_spec.rb: -------------------------------------------------------------------------------- 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 | 8 | before do 9 | AGCOD.configure do |config| 10 | config.partner_id = partner_id 11 | end 12 | end 13 | 14 | context ".new" do 15 | let(:request_id) { "test1" } 16 | let(:gc_id) { "FOO" } 17 | 18 | it "makes cancel request" do 19 | expect(AGCOD::Request).to receive(:new) do |action, params| 20 | expect(action).to eq("CancelGiftCard") 21 | expect(params["creationRequestId"]).to eq(request_id) 22 | expect(params["gcId"]).to eq(gc_id) 23 | end.and_return(response) 24 | 25 | AGCOD::CancelGiftCard.new(request_id, gc_id) 26 | end 27 | end 28 | 29 | shared_context "request with response" do 30 | let(:gc_id) { "BAR" } 31 | let(:creation_request_id) { "BAZ" } 32 | let(:status) { "SUCCESS" } 33 | let(:request) { AGCOD::CancelGiftCard.new(creation_request_id, gc_id) } 34 | 35 | before do 36 | allow(AGCOD::Request).to receive(:new) { double(response: response) } 37 | allow(response).to receive(:status) { status } 38 | end 39 | end 40 | 41 | context "#status" do 42 | include_context "request with response" 43 | 44 | it "returns the response status" do 45 | expect(request.status).to eq(status) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/aws_agcod/config_spec.rb: -------------------------------------------------------------------------------- 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.timeout).not_to be_nil 12 | expect(config.production).to eq(false) 13 | end 14 | end 15 | 16 | context "#uri" do 17 | context "when uri is set" do 18 | before do 19 | config.uri = "https://custom-uri.example.com" 20 | end 21 | 22 | it "returns the custom uri" do 23 | expect(config.uri).to eq("https://custom-uri.example.com") 24 | end 25 | end 26 | 27 | context "when uri is not set" do 28 | context "when production is enabled" do 29 | before do 30 | config.production = true 31 | end 32 | 33 | it "returns the production uri" do 34 | expect(config.uri).to eq(AGCOD::Config::URI[:production]) 35 | end 36 | end 37 | 38 | context "when production is disabled" do 39 | it "returns the sandbox uri" do 40 | expect(config.uri).to eq(AGCOD::Config::URI[:sandbox]) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/aws_agcod/create_gift_card_spec.rb: -------------------------------------------------------------------------------- 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 | 11 | before do 12 | AGCOD.configure do |config| 13 | config.partner_id = partner_id 14 | end 15 | end 16 | 17 | context ".new" do 18 | context "when currency available" do 19 | it "makes create request" do 20 | expect(AGCOD::Request).to receive(:new) do |action, params| 21 | expect(action).to eq("CreateGiftCard") 22 | expect(params["creationRequestId"]).to eq(request_id) 23 | expect(params["value"]).to eq( 24 | "currencyCode" => currency, 25 | "amount" => amount 26 | ) 27 | end.and_return(response) 28 | 29 | AGCOD::CreateGiftCard.new(request_id, amount, currency) 30 | end 31 | end 32 | 33 | context "when currency not available" do 34 | let(:currency) { "NOTEXIST" } 35 | 36 | it "raises error" do 37 | expect { 38 | AGCOD::CreateGiftCard.new(request_id, amount, currency) 39 | }.to raise_error( 40 | AGCOD::CreateGiftCardError, 41 | "Currency #{currency} not supported, available types are #{AGCOD::CreateGiftCard::CURRENCIES.join(", ")}" 42 | ) 43 | end 44 | end 45 | end 46 | 47 | shared_context "request with response" do 48 | let(:claim_code) { "FOO" } 49 | let(:expiration_date) { "Wed Mar 12 22:59:59 UTC 2025" } 50 | let(:gc_id) { "BAR" } 51 | let(:creation_request_id) { "BAZ" } 52 | let(:status) { "SUCCESS" } 53 | let(:payload) { {"gcClaimCode" => claim_code, "gcId" => gc_id, "creationRequestId" => creation_request_id, "gcExpirationDate" => expiration_date} } 54 | let(:request) { AGCOD::CreateGiftCard.new(request_id, amount, currency) } 55 | 56 | before do 57 | allow(AGCOD::Request).to receive(:new) { double(response: response) } 58 | allow(response).to receive(:payload) { payload } 59 | allow(response).to receive(:status) { status } 60 | end 61 | end 62 | 63 | context "#claim_code" do 64 | include_context "request with response" 65 | 66 | it "returns claim_code" do 67 | expect(request.claim_code).to eq(claim_code) 68 | end 69 | end 70 | 71 | context "#expiration_date" do 72 | include_context "request with response" 73 | 74 | it "returns expiration_date" do 75 | expect(request.expiration_date).to eq(Time.parse expiration_date) 76 | end 77 | end 78 | 79 | context "#gc_id" do 80 | include_context "request with response" 81 | 82 | it "returns gc_id" do 83 | expect(request.gc_id).to eq(gc_id) 84 | end 85 | end 86 | 87 | context "#request_id" do 88 | include_context "request with response" 89 | 90 | it "returns creation request_id" do 91 | expect(request.request_id).to eq(creation_request_id) 92 | end 93 | end 94 | 95 | context "#status" do 96 | include_context "request with response" 97 | 98 | it "returns the response status" do 99 | expect(request.status).to eq(status) 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /spec/aws_agcod/gift_card_activity_list_spec.rb: -------------------------------------------------------------------------------- 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 | 14 | before do 15 | AGCOD.configure do |config| 16 | config.partner_id = partner_id 17 | end 18 | end 19 | 20 | context ".new" do 21 | it "makes request" do 22 | expect(start_time).to receive(:strftime).with( 23 | AGCOD::GiftCardActivityList::TIME_FORMAT 24 | ).and_return(start_time) 25 | 26 | expect(end_time).to receive(:strftime).with( 27 | AGCOD::GiftCardActivityList::TIME_FORMAT 28 | ).and_return(end_time) 29 | 30 | expect(AGCOD::Request).to receive(:new) do |action, params| 31 | expect(action).to eq("GetGiftCardActivityPage") 32 | expect(params["requestId"]).to eq(request_id) 33 | expect(params["utcStartDate"]).to eq(start_time) 34 | expect(params["utcEndDate"]).to eq(end_time) 35 | expect(params["pageIndex"]).to eq((page - 1) * per_page) 36 | expect(params["pageSize"]).to eq(per_page) 37 | expect(params["showNoOps"]).to eq(show_no_ops) 38 | end.and_return(response) 39 | 40 | AGCOD::GiftCardActivityList.new(request_id, start_time, end_time, page, per_page, show_no_ops) 41 | end 42 | 43 | context "when request per_page reaches limit" do 44 | let(:per_page) { AGCOD::GiftCardActivityList::LIMIT + 1 } 45 | 46 | it "raises error" do 47 | expect { 48 | AGCOD::GiftCardActivityList.new(request_id, start_time, end_time, page, per_page, show_no_ops) 49 | }.to raise_error( 50 | AGCOD::GiftCardActivityListError, 51 | "Only #{AGCOD::GiftCardActivityList::LIMIT} records allowed per request." 52 | ) 53 | end 54 | end 55 | end 56 | 57 | context "#results" do 58 | let(:payload) { { "cardActivityList" => [spy] } } 59 | let(:request) { AGCOD::GiftCardActivityList.new(request_id, start_time, end_time, page, per_page, show_no_ops) } 60 | 61 | before do 62 | allow(start_time).to receive(:strftime) 63 | allow(end_time).to receive(:strftime) 64 | allow(AGCOD::Request).to receive(:new) { double(response: response) } 65 | allow(response).to receive(:payload) { payload } 66 | end 67 | 68 | it "returns GiftCardActivity instances" do 69 | request.results.each do |item| 70 | expect(item).to be_a(AGCOD::GiftCardActivity) 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/aws_agcod/request_spec.rb: -------------------------------------------------------------------------------- 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(:timeout) { 10 } 12 | let(:config) { double(uri: base_uri, partner_id: partner_id, timeout: timeout) } 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 | it "sends post request to endpoint uri" do 21 | expect(signature).to receive(:sign) do |uri, headers, body| 22 | expect(uri).to eq(URI("#{base_uri}/#{action}")) 23 | expect(headers.keys).to match_array(%w(content-type x-amz-date accept host x-amz-target date)) 24 | expect(headers["content-type"]).to eq("application/json") 25 | expect(headers["x-amz-target"]).to eq("com.amazonaws.agcod.AGCODService.#{action}") 26 | expect(JSON.parse(body)["partnerId"]).to eq(partner_id) 27 | end.and_return(signed_headers) 28 | 29 | expect(HTTParty).to receive(:post) do |uri, options| 30 | expect(uri).to eq(URI("#{base_uri}/#{action}")) 31 | expect(JSON.parse(options[:body])["partnerId"]).to eq(partner_id) 32 | expect(options[:headers]).to eq(signed_headers) 33 | expect(options[:timeout]).to eq(timeout) 34 | end.and_return(double(body: params.to_json)) 35 | 36 | AGCOD::Request.new(action, params) 37 | end 38 | 39 | it "sets response" do 40 | expect(signature).to receive(:sign) { signed_headers } 41 | expect(HTTParty).to receive(:post) { (double(body: params.to_json)) } 42 | 43 | response = AGCOD::Request.new(action, params).response 44 | 45 | expect(response).to be_a(AGCOD::Response) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/aws_agcod/response_spec.rb: -------------------------------------------------------------------------------- 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 67 | -------------------------------------------------------------------------------- /spec/aws_agcod_spec.rb: -------------------------------------------------------------------------------- 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 13 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | ROOT_PATH = Pathname.new(__FILE__).join("../..").expand_path 4 | $LOAD_PATH.unshift(ROOT_PATH.join("lib")) 5 | 6 | RSpec.configure do |config| 7 | # Run specs in random order to surface order dependencies. If you find an 8 | # order dependency and want to debug it, you can fix the order by providing 9 | # the seed, which is printed after each run. 10 | # --seed 1234 11 | config.order = "random" 12 | 13 | # Disable the should syntax compeletely; we use the expect syntax only. 14 | config.expect_with :rspec do |c| 15 | c.syntax = :expect 16 | end 17 | end 18 | --------------------------------------------------------------------------------