From 8500c324fffb52149a37187184fa1ba6bda79944 Mon Sep 17 00:00:00 2001 From: Nick Schmidt Date: Fri, 11 Dec 2020 18:11:45 +0100 Subject: [PATCH 1/3] Add service for acknowledging subscriptions --- .../acknowledgement.rb | 45 +++++++++++++++++++ .../subscription_acknowledgements/response.rb | 24 ++++++++++ 2 files changed, 69 insertions(+) create mode 100644 lib/candy_check/play_store/subscription_acknowledgements/acknowledgement.rb create mode 100644 lib/candy_check/play_store/subscription_acknowledgements/response.rb diff --git a/lib/candy_check/play_store/subscription_acknowledgements/acknowledgement.rb b/lib/candy_check/play_store/subscription_acknowledgements/acknowledgement.rb new file mode 100644 index 0000000..1ca6adc --- /dev/null +++ b/lib/candy_check/play_store/subscription_acknowledgements/acknowledgement.rb @@ -0,0 +1,45 @@ +module CandyCheck + module PlayStore + module SubscriptionAcknowledgements + # Acknowledges a subscription through the API + + class Acknowledgement + # @return [String] the package_name which will be queried + attr_reader :package_name + # @return [String] the item id which will be queried + attr_reader :subscription_id + # @return [String] the token for authentication + attr_reader :token + + # Initializes a new call to the API + # @param package_name [String] + # @param subscription_id [String] + # @param token [String] + def initialize(package_name:, subscription_id:, token:, authorization:) + @package_name = package_name + @subscription_id = subscription_id + @token = token + @authorization = authorization + end + + def call! + acknowledge! + + CandyCheck::PlayStore::SubscriptionAcknowledgements::Response.new( + result: @response[:result], error_data: @response[:error_data]) + end + + private + + def acknowledge! + service = CandyCheck::PlayStore::AndroidPublisherService.new + + service.authorization = @authorization + service.acknowledge_subscription_purchase(package_name, subscription_id, token) do |result, error_data| + @response = { result: result, error_data: error_data } + end + end + end + end + end +end diff --git a/lib/candy_check/play_store/subscription_acknowledgements/response.rb b/lib/candy_check/play_store/subscription_acknowledgements/response.rb new file mode 100644 index 0000000..0e535d9 --- /dev/null +++ b/lib/candy_check/play_store/subscription_acknowledgements/response.rb @@ -0,0 +1,24 @@ +module CandyCheck + module PlayStore + module SubscriptionAcknowledgements + class Response + def initialize(result:, error_data:) + @result = result + @error_data = error_data + end + + def acknowledged? + !!result + end + + def error + return unless error_data + + { status_code: error_data.status_code, body: error_data.body } + end + + attr_reader :result, :error_data + end + end + end +end From 89fa629296d10c2eeef12f503ec968505dab96c5 Mon Sep 17 00:00:00 2001 From: Nick Schmidt Date: Fri, 11 Dec 2020 18:15:39 +0100 Subject: [PATCH 2/3] Add `acknowledge_product_purchase` method to `Acknowledger` --- lib/candy_check/play_store.rb | 2 ++ lib/candy_check/play_store/acknowledger.rb | 9 +++++++++ .../subscription_acknowledgements/acknowledgement.rb | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/candy_check/play_store.rb b/lib/candy_check/play_store.rb index 7cdcbff..9e5ddbc 100644 --- a/lib/candy_check/play_store.rb +++ b/lib/candy_check/play_store.rb @@ -7,6 +7,8 @@ require "candy_check/play_store/product_acknowledgements/acknowledgement" require "candy_check/play_store/product_acknowledgements/response" require "candy_check/play_store/subscription_purchases/subscription_verification" +require "candy_check/play_store/subscription_acknowledgements/acknowledgement" +require "candy_check/play_store/subscription_acknowledgements/response" require "candy_check/play_store/verification_failure" require "candy_check/play_store/verifier" require "candy_check/play_store/acknowledger" diff --git a/lib/candy_check/play_store/acknowledger.rb b/lib/candy_check/play_store/acknowledger.rb index 900aea9..44a5886 100644 --- a/lib/candy_check/play_store/acknowledger.rb +++ b/lib/candy_check/play_store/acknowledger.rb @@ -14,6 +14,15 @@ def acknowledge_product_purchase(package_name:, product_id:, token:) ) acknowledger.call! end + + def acknowledge_subscription_purchase(package_name:, subscription_id:, token:) + CandyCheck::PlayStore::SubscriptionAcknowledgements::Acknowledgement.new( + package_name: package_name, + subscription_id: subscription_id, + token: token, + authorization: @authorization, + ).call! + end end end end diff --git a/lib/candy_check/play_store/subscription_acknowledgements/acknowledgement.rb b/lib/candy_check/play_store/subscription_acknowledgements/acknowledgement.rb index 1ca6adc..0a159ce 100644 --- a/lib/candy_check/play_store/subscription_acknowledgements/acknowledgement.rb +++ b/lib/candy_check/play_store/subscription_acknowledgements/acknowledgement.rb @@ -35,7 +35,7 @@ def acknowledge! service = CandyCheck::PlayStore::AndroidPublisherService.new service.authorization = @authorization - service.acknowledge_subscription_purchase(package_name, subscription_id, token) do |result, error_data| + service.acknowledge_purchase_subscription(package_name, subscription_id, token) do |result, error_data| @response = { result: result, error_data: error_data } end end From 9b18f4ab0af74d36c0ec5e13aebc834b1fda9199 Mon Sep 17 00:00:00 2001 From: Nick Schmidt Date: Fri, 11 Dec 2020 18:44:33 +0100 Subject: [PATCH 3/3] Add tests to pull request --- .../acknowledged.yml | 105 +++++++++++++++ .../already_acknowledged.yml | 124 ++++++++++++++++++ .../refunded.yml | 122 +++++++++++++++++ .../acknowledgement_spec.rb | 54 ++++++++ .../response_spec.rb | 66 ++++++++++ 5 files changed, 471 insertions(+) create mode 100644 spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/acknowledged.yml create mode 100644 spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/already_acknowledged.yml create mode 100644 spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/refunded.yml create mode 100644 spec/play_store/subscription_acknowledgements/acknowledgement_spec.rb create mode 100644 spec/play_store/subscription_acknowledgements/response_spec.rb diff --git a/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/acknowledged.yml b/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/acknowledged.yml new file mode 100644 index 0000000..3c97f9a --- /dev/null +++ b/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/acknowledged.yml @@ -0,0 +1,105 @@ +--- +http_interactions: +- request: + method: post + uri: https://www.googleapis.com/oauth2/v4/token + body: + encoding: ASCII-8BIT + string: params + headers: + User-Agent: + - Faraday v1.0.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Mon, 22 Jun 2020 11:38:56 GMT + Server: + - scaffolding on HTTPServer2 + Cache-Control: + - private + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3-28=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; + ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; + ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; + ma=2592000; v="46,43" + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: '{"access_token":"access_token","expires_in":3599,"token_type":"Bearer"}' + recorded_at: Mon, 22 Jun 2020 11:38:56 GMT +- request: + method: post + uri: https://www.googleapis.com/androidpublisher/v3/applications/fake_package_name/purchases/subscriptions/fake_subscription_id/tokens/fake_token:acknowledge + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - unknown/0.0.0 google-api-ruby-client/0.34.1 Mac OS X/10.14.6 (gzip) + Accept: + - "*/*" + Accept-Encoding: + - gzip,deflate + Date: + - Mon, 22 Jun 2020 11:38:56 GMT + X-Goog-Api-Client: + - gl-ruby/2.5.1 gdcl/0.34.1 + Authorization: + - Bearer some_token + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 204 + message: No Content + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Mon, 22 Jun 2020 11:38:57 GMT + Server: + - ESF + Content-Length: + - '0' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3-28=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; + ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; + ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; + ma=2592000; v="46,43" + body: + encoding: UTF-8 + string: '' + recorded_at: Mon, 22 Jun 2020 11:38:57 GMT +recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/already_acknowledged.yml b/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/already_acknowledged.yml new file mode 100644 index 0000000..fca2ab9 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/already_acknowledged.yml @@ -0,0 +1,124 @@ +--- +http_interactions: +- request: + method: post + uri: https://www.googleapis.com/oauth2/v4/token + body: + encoding: ASCII-8BIT + string: params + headers: + User-Agent: + - Faraday v1.0.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Mon, 22 Jun 2020 13:11:45 GMT + Server: + - scaffolding on HTTPServer2 + Cache-Control: + - private + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3-28=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; + ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; + ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; + ma=2592000; v="46,43" + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: '{"access_token":"access_token","expires_in":3599,"token_type":"Bearer"}' + recorded_at: Mon, 22 Jun 2020 13:11:45 GMT +- request: + method: post + uri: https://www.googleapis.com/androidpublisher/v3/applications/fake_package_name/purchases/subscriptions/fake_subscription_id/tokens/fake_token:acknowledge + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - unknown/0.0.0 google-api-ruby-client/0.34.1 Mac OS X/10.14.6 (gzip) + Accept: + - "*/*" + Accept-Encoding: + - gzip,deflate + Date: + - Mon, 22 Jun 2020 13:11:45 GMT + X-Goog-Api-Client: + - gl-ruby/2.5.1 gdcl/0.34.1 + Authorization: + - Bearer some_token + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 400 + message: Bad Request + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Content-Encoding: + - gzip + Date: + - Mon, 22 Jun 2020 13:11:46 GMT + Server: + - ESF + Cache-Control: + - private + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3-28=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; + ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; + ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; + ma=2592000; v="46,43" + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: | + { + "error": { + "code": 400, + "message": "The purchase is not in a valid state to perform the desired operation.", + "errors": [ + { + "message": "The purchase is not in a valid state to perform the desired operation.", + "domain": "androidpublisher", + "reason": "invalidPurchaseState", + "location": "token", + "locationType": "parameter" + } + ] + } + } + recorded_at: Mon, 22 Jun 2020 13:11:46 GMT +recorded_with: VCR 6.0.0 diff --git a/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/refunded.yml b/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/refunded.yml new file mode 100644 index 0000000..24d4036 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/play_store/subscription_acknowledgements/refunded.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://www.googleapis.com/oauth2/v4/token + body: + encoding: ASCII-8BIT + string: params + headers: + User-Agent: + - Faraday v1.0.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Mon, 22 Jun 2020 14:37:46 GMT + Server: + - scaffolding on HTTPServer2 + Cache-Control: + - private + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3-28=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; + ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; + ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; + ma=2592000; v="46,43" + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: '{"access_token":"access_token","expires_in":3599,"token_type":"Bearer"}' + recorded_at: Mon, 22 Jun 2020 14:37:46 GMT +- request: + method: post + uri: https://www.googleapis.com/androidpublisher/v3/applications/fake_package_name/purchases/subscriptions/fake_subscription_id/tokens/fake_token:acknowledge + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - unknown/0.0.0 google-api-ruby-client/0.34.1 Mac OS X/10.14.6 (gzip) + Accept: + - "*/*" + Accept-Encoding: + - gzip,deflate + Date: + - Mon, 22 Jun 2020 14:37:46 GMT + X-Goog-Api-Client: + - gl-ruby/2.5.1 gdcl/0.34.1 + Authorization: + - Bearer some_token + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 400 + message: Bad Request + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Content-Encoding: + - gzip + Date: + - Mon, 22 Jun 2020 14:37:47 GMT + Server: + - ESF + Cache-Control: + - private + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3-28=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; + ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; + ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; + ma=2592000; v="46,43" + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: | + { + "error": { + "code": 400, + "message": "The product purchase is not owned by the user.", + "errors": [ + { + "message": "The product purchase is not owned by the user.", + "domain": "androidpublisher", + "reason": "productNotOwnedByUser" + } + ] + } + } + recorded_at: Mon, 22 Jun 2020 14:37:47 GMT +recorded_with: VCR 6.0.0 diff --git a/spec/play_store/subscription_acknowledgements/acknowledgement_spec.rb b/spec/play_store/subscription_acknowledgements/acknowledgement_spec.rb new file mode 100644 index 0000000..4353ee2 --- /dev/null +++ b/spec/play_store/subscription_acknowledgements/acknowledgement_spec.rb @@ -0,0 +1,54 @@ +require "spec_helper" + +describe CandyCheck::PlayStore::SubscriptionAcknowledgements::Acknowledgement do + subject do + CandyCheck::PlayStore::SubscriptionAcknowledgements::Acknowledgement.new( + package_name: package_name, + subscription_id: subscription_id, + token: token, + authorization: authorization, + ) + end + + let(:package_name) { "fake_package_name" } + let(:subscription_id) { "fake_subscription_id" } + let(:token) { "fake_token" } + let(:json_key_file) { File.expand_path("../../fixtures/play_store/random_dummy_key.json", __dir__) } + let(:authorization) { CandyCheck::PlayStore.authorization(json_key_file) } + + describe "#call!" do + it "when acknowledgement succeeds" do + VCR.use_cassette("play_store/subscription_acknowledgements/acknowledged") do + result = subject.call! + + result.must_be_instance_of CandyCheck::PlayStore::SubscriptionAcknowledgements::Response + result.acknowledged?.must_be_true + result.error.must_be_nil + end + end + it "when already acknowledged" do + error_body = "{\n \"error\": {\n \"code\": 400,\n \"message\": \"The purchase is not in a valid state to perform the desired operation.\",\n \"errors\": [\n {\n \"message\": \"The purchase is not in a valid state to perform the desired operation.\",\n \"domain\": \"androidpublisher\",\n \"reason\": \"invalidPurchaseState\",\n \"location\": \"token\",\n \"locationType\": \"parameter\"\n }\n ]\n }\n}\n" + + VCR.use_cassette("play_store/subscription_acknowledgements/already_acknowledged") do + result = subject.call! + + result.must_be_instance_of CandyCheck::PlayStore::SubscriptionAcknowledgements::Response + result.acknowledged?.must_be_false + result.error[:body].must_equal(error_body) + result.error[:status_code].must_equal(400) + end + end + it "when it has been refunded" do + error_body = "{\n \"error\": {\n \"code\": 400,\n \"message\": \"The product purchase is not owned by the user.\",\n \"errors\": [\n {\n \"message\": \"The product purchase is not owned by the user.\",\n \"domain\": \"androidpublisher\",\n \"reason\": \"productNotOwnedByUser\"\n }\n ]\n }\n}\n" + + VCR.use_cassette("play_store/subscription_acknowledgements/refunded") do + result = subject.call! + + result.must_be_instance_of CandyCheck::PlayStore::SubscriptionAcknowledgements::Response + result.acknowledged?.must_be_false + result.error[:body].must_equal(error_body) + result.error[:status_code].must_equal(400) + end + end + end +end diff --git a/spec/play_store/subscription_acknowledgements/response_spec.rb b/spec/play_store/subscription_acknowledgements/response_spec.rb new file mode 100644 index 0000000..8e763d0 --- /dev/null +++ b/spec/play_store/subscription_acknowledgements/response_spec.rb @@ -0,0 +1,66 @@ +require "spec_helper" + +describe CandyCheck::PlayStore::ProductAcknowledgements::Response do + subject do + CandyCheck::PlayStore::ProductAcknowledgements::Response.new(result: result, error_data: error_data) + end + + describe '#acknowledged?' do + context 'when result present' do + let(:result) { '' } + let(:error_data) { nil } + + it 'returns true' do + result = subject.acknowledged? + + result.must_be_true + end + end + + context 'when result is not present' do + let(:result) { nil } + let(:error_data) { nil } + + it 'returns false' do + result = subject.acknowledged? + + result.must_be_false + end + end + end + + describe '#error' do + context 'when error present' do + let(:result) { nil } + let(:error_data) do + Module.new do + def status_code + 400 + end + def body + 'A String describing the issue' + end + module_function :status_code, :body + end + end + + it 'returns the expected data' do + result = subject.error + + result[:status_code].must_equal(400) + result[:body].must_equal('A String describing the issue') + end + end + + context 'when error is not present' do + let(:result) { '' } + let(:error_data) { nil } + + it 'returns false' do + result = subject.error + + result.must_be_nil + end + end + end +end