app_store_server_api_client 0.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: e0d6c54805549e9e61a26f3dc874d091722c548ecc57ed45b9086c295f9d02db
4
+ data.tar.gz: 0f545da8ddc8a22a83d1e9fa382968f45e90a82d9059835822e30d02c2debf28
5
+ SHA512:
6
+ metadata.gz: d478dae3696e4f0e8eda24c12f83a9ccdb4f7ffc2e3f5e95c8b0ee62ef664278384fbeba0f728044eab5f1adb8f1eb6ac71a79ae86710d66b59894c06c179f46
7
+ data.tar.gz: e2106ce7f0313203f26c807f1ee90aa998cce02f4cb7dce765695f8853cdfeb8d82f9d6e3a473f6cd8bcb4cac30709713450cb4dbd04b801b10213dab1cc2c34
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.3
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: single_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: single_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-02-14
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 watanabe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # App Store Server API Client
2
+
3
+ A Ruby client for
4
+ the [App Store Server API](https://developer.apple.com/documentation/appstoreserverapi).
5
+
6
+ ## Support API Endpoints
7
+
8
+ * [Get Transaction Info](https://developer.apple.com/documentation/appstoreserverapi/get-v1-transactions-_transactionid_)
9
+ * [Request a Test Notification](https://developer.apple.com/documentation/appstoreserverapi/post-v1-notifications-test)
10
+ * [Get Test Notification Status](https://developer.apple.com/documentation/appstoreserverapi/get-v1-notifications-test-_testnotificationtoken_)
11
+ * [Get Transaction History](https://developer.apple.com/documentation/appstoreserverapi/get-v2-history-_transactionid_)
12
+
13
+ ## Requirements
14
+
15
+ Ruby 3.3.0 or later.
16
+
17
+ ## Installation
18
+
19
+ add this line to your application's Gemfile:
20
+
21
+ ```Gemfile
22
+ gem 'app_store_server_api_client'
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Prerequisites
28
+
29
+ To use this, please obtain an API Key.
30
+ https://developer.apple.com/documentation/appstoreserverapi/creating-api-keys-to-authorize-api-requests
31
+
32
+ ### Configure
33
+
34
+ **In your Rails application, create a client configure**
35
+
36
+ ```yaml
37
+ # my_app/config/app_store_server.yml
38
+ default: &default
39
+ private_key: |
40
+ -----BEGIN PRIVATE KEY-----
41
+ ...
42
+ -----END PRIVATE KEY-----
43
+ key_id: Z1BT391B21
44
+ issuer_id: ef02153z-1290-3519-875e-237a15237e3c
45
+ bundle_id: com.myapp.app
46
+ environment: sandbox
47
+
48
+ development:
49
+ <<: *default
50
+
51
+ test:
52
+ <<: *default
53
+
54
+ production:
55
+ <<: *default
56
+ ```
57
+
58
+ ### load the configuration
59
+
60
+ ```ruby
61
+ config = Rails.application.config_for(:app_store_server)
62
+ client = AppStoreServerApi::Client.new(**config)
63
+ ```
64
+
65
+ ## API
66
+
67
+ ### Get Transaction Info
68
+
69
+ [Get Transaction Info](
70
+ https://developer.apple.com/documentation/appstoreserverapi/get-v1-transactions-_transactionid_)
71
+
72
+ Get information about a single transaction for your app.
73
+
74
+ ```ruby
75
+ transaction_id = '2000000847061981'
76
+ client.get_transaction_info(transaction_id)
77
+ =>
78
+ {
79
+ "transactionId" => "2000000847061981",
80
+ "originalTransactionId" => "2000000847061981",
81
+ "bundleId" => "com.myapp.app",
82
+ "productId" => "com.myapp.app.product",
83
+ "type" => "Consumable",
84
+ "purchaseDate" => 1738645560000,
85
+ "originalPurchaseDate" => 1738645560000,
86
+ "quantity" => 1,
87
+ ...
88
+ }
89
+ ```
90
+
91
+ ### Request a Test Notification
92
+
93
+ [Request a Test Notification](https://developer.apple.com/documentation/appstoreserverapi/post-v1-notifications-test)
94
+
95
+ Ask App Store Server Notifications to send a test notification to your server.
96
+
97
+ ```ruby
98
+ result = client.request_test_notification
99
+ #=> {"testNotificationToken"=>"9f90efb9-2f75-4dbe-990c-5d1fc89f4546_1739179413123"}
100
+ ```
101
+
102
+ ### Get Test Notification Status
103
+
104
+ [Get Test Notification Status](https://developer.apple.com/documentation/appstoreserverapi/get-v1-notifications-test-_testnotificationtoken_)
105
+
106
+ Check the status of the test App Store server notification sent to your server.
107
+
108
+ ```ruby
109
+ test_notification_token = client.request_test_notification['testNotificationToken']
110
+ result = client.get_test_notification_status(test_notification_token)
111
+ #=> {
112
+ # "signedPayload"=> "eyJhbGciOiJFUzI1NiIsIng1YyI6...",
113
+ # "firstSendAttemptResult"=>"SUCCESS",
114
+ # "sendAttempts"=>[{"attemptDate"=>1739179888814, "sendAttemptResult"=>"SUCCESS"}]
115
+ #}
116
+
117
+ signed_payload = AppStoreServerApi::Utils::Decoder.decode_jws!(result['signedPayload'])
118
+ # => {
119
+ # "notificationType"=>"TEST",
120
+ # "notificationUUID"=>"3838df56-31ab-4e2e-9535-e6e9377c4c77",
121
+ # "data"=>{"bundleId"=>"com.myapp.app", "environment"=>"Sandbox"},
122
+ # "version"=>"2.0",
123
+ # "signedDate"=>1739180480080
124
+ # }
125
+ ```
126
+
127
+ ### Get Transaction History
128
+
129
+ [Get Transaction History](https://developer.apple.com/documentation/appstoreserverapi/get-v2-history-_transactionid_)
130
+
131
+ Get a customer’s in-app purchase transaction history for your app.
132
+
133
+ ```ruby
134
+ data = client.get_transaction_history(transaction_id,
135
+ params: {
136
+ sort: "DESCENDING"
137
+ })
138
+
139
+ transactions = AppStoreServerApi::Utils::Decoder.decode_transactions(signed_transactions:
140
+ data["signedTransactions"])
141
+ ```
142
+
143
+ ## Error Handling
144
+
145
+ ```ruby
146
+
147
+ begin
148
+ # response success
149
+ transaction_info = client.get_transaction_info('invalid_transaction_id')
150
+ rescue AppStoreServerApi::Error => e
151
+ # response failure
152
+ # case of error:
153
+ # - http status 40x, 50x
154
+ # - json parse error
155
+ puts e.code # => Integer
156
+ puts e.message # => String
157
+ end
158
+ ```
159
+
160
+ ## License
161
+
162
+ The gem is available as open source under the terms of
163
+ the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+ require 'jwt'
3
+ require 'faraday'
4
+ require 'uri'
5
+ require 'json'
6
+ require 'openssl'
7
+
8
+ module AppStoreServerApi
9
+ class Client
10
+ attr_reader :environment, :issuer_id, :key_id, :private_key, :bundle_id
11
+
12
+ PAYLOAD_AUD = 'appstoreconnect-v1'
13
+ TOKEN_TYPE = 'JWT'
14
+ ENCODE_ALGORITHM = 'ES256'
15
+ ENVIRONMENTS = [:production, :sandbox].freeze
16
+ API_BASE_URLS = {
17
+ :production => 'https://api.storekit.itunes.apple.com',
18
+ :sandbox => 'https://api.storekit-sandbox.itunes.apple.com'
19
+ }.freeze
20
+
21
+ # initialize client
22
+ # @param private_key [String] p8 key
23
+ # @param key_id [String] Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
24
+ # @param issuer_id [String] Your issuer ID from the Keys page in App Store Connect
25
+ # @param bundle_id [String] Your app’s bundle ID (Ex: “com.example.testbundleid”)
26
+ # @param environment [Symbol] :production or :sandbox
27
+ def initialize(private_key:, key_id:, issuer_id:, bundle_id:, environment: :production)
28
+ self.environment = environment.to_sym
29
+ @issuer_id = issuer_id
30
+ @key_id = key_id
31
+ @private_key = private_key
32
+ @bundle_id = bundle_id
33
+ @http_client = Utils::HttpClient.new
34
+ end
35
+
36
+ # set environment
37
+ # @param env [Symbol] :production or :sandbox
38
+ # @raise [ArgumentError] if env is not :production or :sandbox
39
+ def environment=(env)
40
+ unless ENVIRONMENTS.include?(env)
41
+ raise ArgumentError, 'environment must be :production or :sandbox'
42
+ end
43
+
44
+ @environment = env
45
+ end
46
+
47
+ # get information about a single transaction
48
+ # @see https://developer.apple.com/documentation/appstoreserverapi/get-v1-transactions-_transactionid_
49
+ # @param [String] transaction_id The identifier of a transaction
50
+ # @return [Hash] transaction info
51
+ def get_transaction_info(transaction_id)
52
+ path = "/inApps/v1/transactions/#{transaction_id}"
53
+ response = do_request(path)
54
+ json = JSON.parse(response.body)
55
+ payload, = Utils::Decoder.decode_jws!(json['signedTransactionInfo'])
56
+ payload
57
+ end
58
+
59
+ # Request a Test Notification
60
+ # @see https://developer.apple.com/documentation/appstoreserverapi/post-v1-notifications-test
61
+ # @return [Hash] test notification token info
62
+ def request_test_notification
63
+ path = '/inApps/v1/notifications/test'
64
+ response = do_request(path, method: :post, params: {}.to_json)
65
+ JSON.parse(response.body)
66
+ end
67
+
68
+ # Get Test Notification Status
69
+ # @see https://developer.apple.com/documentation/appstoreserverapi/get-v1-notifications-test-_testnotificationtoken_
70
+ def get_test_notification_status(test_notification_token)
71
+ path = "/inApps/v1/notifications/test/#{test_notification_token}"
72
+ response = do_request(path)
73
+ JSON.parse(response.body)
74
+ end
75
+
76
+ # Get Transaction History
77
+ # @see https://developer.apple.com/documentation/appstoreserverapi/get-v2-history-_transactionid_
78
+ # @param [String] transaction_id The identifier of a transaction
79
+ # @param [Hash] params request params
80
+ # @return [Hash] transaction history
81
+ def get_transaction_history(transaction_id, params: {})
82
+ path = "/inApps/v2/history/#{transaction_id}"
83
+ response = do_request(path, params: params)
84
+ JSON.parse(response.body)
85
+ end
86
+
87
+ # generate bearer token
88
+ # @param issued_at [Time] issued at
89
+ # @param expired_in [Integer] expired in seconds (max 3600)
90
+ # @return [String] bearer token
91
+ def generate_bearer_token(issued_at: Time.now, expired_in: 3600)
92
+ # expirations longer than 60 minutes will be rejected
93
+ if expired_in > 3600
94
+ raise ArgumentError, 'expired_in must be less than or equal to 3600'
95
+ end
96
+
97
+ headers = {
98
+ alg: ENCODE_ALGORITHM,
99
+ kid: key_id,
100
+ typ: TOKEN_TYPE,
101
+ }
102
+
103
+ payload = {
104
+ iss: issuer_id,
105
+ iat: issued_at.to_i,
106
+ exp: (issued_at + expired_in).to_i,
107
+ aud: PAYLOAD_AUD,
108
+ bid: bundle_id
109
+ }
110
+
111
+ JWT.encode(payload, OpenSSL::PKey::EC.new(private_key), ENCODE_ALGORITHM, headers)
112
+ end
113
+
114
+ def api_base_url
115
+ API_BASE_URLS[environment]
116
+ end
117
+
118
+ def base_request_headers(bearer_token)
119
+ {
120
+ 'Content-Type' => 'application/json',
121
+ 'Authorization' => "Bearer #{bearer_token}"
122
+ }
123
+ end
124
+
125
+ # send get request to App Store Server API
126
+ # @param [String] path request path
127
+ # @param [Symbol] method request method
128
+ # @param [Hash,String,nil] params request params
129
+ # @param [Hash] headers additional headers
130
+ # @return [Faraday::Response] response
131
+ #
132
+ # @raise [Error::UnauthorizedError] if unauthorized error
133
+ # @raise [Error::ServerError] if server error
134
+ # @raise [Error] if other error
135
+ def do_request(path, method: :get, params: {}, headers: {}, open_timeout: 10, read_timeout: 30)
136
+ request_url = api_base_url + path
137
+ bearer_token = generate_bearer_token
138
+ request_headers = base_request_headers(bearer_token).merge(headers)
139
+
140
+ response = @http_client.request_with_retry(
141
+ url: request_url,
142
+ method: method,
143
+ params: params,
144
+ headers: request_headers)
145
+
146
+ if response.success?
147
+ return response
148
+ end
149
+
150
+ Error.handle_error(response)
151
+ end
152
+
153
+ end
154
+
155
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+ module AppStoreServerApi
3
+
4
+ class Error < StandardError
5
+ attr_reader :code, :response
6
+
7
+ # initialize error
8
+ # @param [Integer] code error code
9
+ # @param [String] message error message
10
+ # @param [Faraday::Response] response error response
11
+ def initialize(code:, message:, response:)
12
+ super(message)
13
+ @code = code
14
+ @response = response
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ code: code,
20
+ message: message,
21
+ response: response
22
+ }
23
+ end
24
+
25
+ def inspect
26
+ "#<#{self.class.name}: #{to_h.to_json}>"
27
+ end
28
+
29
+ # The JSON Web Token (JWT) in the authorization header is invalid.
30
+ # For more information, see Generating JSON Web Tokens for API requests.
31
+ # @see https://developer.apple.com/documentation/appstoreserverapi/generating-json-web-tokens-for-api-requests
32
+ # other:
33
+ # - wrong environment (sandbox/production)
34
+ class UnauthorizedError < Error
35
+ def initialize(code: 4010000, message: 'unauthorized error', response:)
36
+ super(code: code, message: message, response: response)
37
+ end
38
+ end
39
+
40
+ class ServerError < Error
41
+ def initialize(code: 5000000, message: 'Internal Server Error', response:)
42
+ super(code: code, message: message, response: response)
43
+ end
44
+ end
45
+
46
+ # error response body is invalid
47
+ # must have errorCode and errorMessage.
48
+ # valid example response body:
49
+ # {
50
+ # "errorCode": 4000006,
51
+ # "errorMessage": "Invalid transaction id."
52
+ # }
53
+ class InvalidResponseError < Error
54
+ def initialize(code: 5000002, message: 'response body is invalid', response:)
55
+ super(code: code, message: message, response: response)
56
+ end
57
+ end
58
+
59
+ class TransactionIdNotFoundError < Error; end
60
+
61
+ class InvalidTransactionIdError < Error; end
62
+
63
+ class RateLimitExceededError < Error; end
64
+
65
+ class ServerNotificationURLNotFoundError < Error; end
66
+
67
+ class InvalidTestNotificationTokenError < Error; end
68
+
69
+ class TestNotificationNotFoundError < Error; end
70
+
71
+ # map error code to error class
72
+ ERROR_CODE_MAP = {
73
+ 4040010 => Error::TransactionIdNotFoundError,
74
+ 4000020 => Error::InvalidTestNotificationTokenError,
75
+ 4000006 => Error::InvalidTransactionIdError,
76
+ 4290000 => Error::RateLimitExceededError,
77
+ 4040007 => Error::ServerNotificationURLNotFoundError,
78
+ 4040008 => Error::TestNotificationNotFoundError,
79
+ }.freeze
80
+
81
+ # raise error from response
82
+ # @param [Faraday::Response] response error response
83
+ def self.handle_error(response)
84
+ case response.status
85
+ when 401
86
+ # Unauthorized error
87
+ # reasons:
88
+ # - JWT in the authorization header is invalid.
89
+ raise Error::UnauthorizedError.new(response: response)
90
+ when 500
91
+ raise Error::ServerError.new(response: response)
92
+ else
93
+ data = JSON.parse(response.body)
94
+
95
+ # error object must be {errorCode: Integer, errorMessage: String}
96
+ unless data.has_key?('errorCode') && data.has_key?('errorMessage')
97
+ raise Error::InvalidResponseError.new(message: 'response body is invalid', response: response)
98
+ end
99
+
100
+ error_code = data['errorCode']
101
+ error_class = ERROR_CODE_MAP[error_code] || Error
102
+ raise error_class.new(code: error_code, message: data['errorMessage'], response: response)
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ require 'openssl'
3
+ require 'jwt'
4
+
5
+ module AppStoreServerApi
6
+ module Utils
7
+ module Decoder
8
+ module_function
9
+
10
+ # Decode a signed JWT
11
+ # @param [String] jws The signed JWT to decode
12
+ # @return [Hash] The decoded payload
13
+ def decode_jws!(jws)
14
+ apple_cert_store = make_apple_cert_store
15
+
16
+ payload, = JWT.decode(jws, nil, true, {algorithm: 'ES256'}) do |headers|
17
+ # verify the certificate included in the header x5c
18
+ cert_target, *cert_chain = headers['x5c'].map {|cert| OpenSSL::X509::Certificate.new(Base64.decode64(cert))}
19
+ apple_cert_store.verify(cert_target, cert_chain)
20
+ cert_target.public_key
21
+ end
22
+
23
+ payload
24
+ end
25
+
26
+ def decode_transaction(signed_transaction:)
27
+ decode_jws! signed_transaction
28
+ end
29
+
30
+ def decode_transactions(signed_transactions:)
31
+ signed_transactions.map do |signed_transaction|
32
+ decode_transaction signed_transaction: signed_transaction
33
+ end
34
+ end
35
+
36
+ def apple_root_certs
37
+ Dir.glob(File.join(__dir__, 'certs', '*.cer')).map do |filename|
38
+ OpenSSL::X509::Certificate.new File.read(filename)
39
+ end
40
+ end
41
+
42
+ def make_apple_cert_store
43
+ apple_cert_store = OpenSSL::X509::Store.new
44
+ apple_root_certs.each do |cert|
45
+ apple_cert_store.add_cert cert
46
+ end
47
+
48
+ apple_cert_store
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ require 'faraday'
3
+ require 'retriable'
4
+
5
+ module AppStoreServerApi
6
+
7
+ module Utils
8
+
9
+ class HttpClient
10
+ DEFAULT_OPEN_TIMEOUT = 10
11
+ DEFAULT_READ_TIMEOUT = 30
12
+ RETRY_ERRORS = [Faraday::TimeoutError, Faraday::ConnectionFailed, Faraday::ServerError]
13
+
14
+ # initialize client
15
+ # @param open_timeout [Integer] open timeout
16
+ # @param read_timeout [Integer] read timeout
17
+ def initialize(open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT)
18
+ @open_timeout = open_timeout
19
+ @read_timeout = read_timeout
20
+ end
21
+
22
+ def build_connection
23
+ Faraday.new do |f|
24
+ f.adapter :net_http do |http|
25
+ http.open_timeout = @open_timeout
26
+ http.read_timeout = @read_timeout
27
+ end
28
+ end
29
+ end
30
+
31
+ def connection
32
+ @connection ||= build_connection
33
+ end
34
+
35
+ # send request
36
+ # @param url [String] request url
37
+ # @param method [Symbol] request method(:get, :post, :put, :patch, :delete)
38
+ # @param params [Hash,String,nil] request params
39
+ # @param headers [Hash] request headers
40
+ # @return [Faraday::Response]
41
+ def request(url:, method: :get, params: {}, headers: {})
42
+ method = method.to_sym
43
+
44
+ case method
45
+ when :get, :delete
46
+ connection.run_request(method, url, nil, headers) do |req|
47
+ req.params.update(params) if params.is_a?(Hash)
48
+ end
49
+ when :post, :put, :patch
50
+ connection.run_request(method, url, params, headers)
51
+ else
52
+ raise ArgumentError, "Unsupported HTTP method: #{method}"
53
+ end
54
+ end
55
+
56
+ # send request with retry
57
+ # @param url [String] request url
58
+ # @param method [Symbol] request method
59
+ # @param params [Hash,String,nil] request params
60
+ # @param headers [Hash] request headers
61
+ # @param retries [Integer] retry count
62
+ # @param base_interval [Float] base interval
63
+ # @param multiplier [Float] multiplier
64
+ # @param max_interval [Float] max interval
65
+ # @return [Faraday::Response]
66
+ def request_with_retry(url:, method: :get, params: {}, headers: {}, retries: 3,
67
+ base_interval: 0.5, multiplier: 1.0, max_interval: 30)
68
+
69
+ Retriable.retriable tries: retries,
70
+ base_interval: base_interval,
71
+ max_interval: max_interval,
72
+ multiplier: multiplier,
73
+ on: RETRY_ERRORS do
74
+ request(url: url, method: method, params: params, headers: headers)
75
+ end
76
+ end
77
+
78
+ end
79
+
80
+ end
81
+
82
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module AppStoreServerApi
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'app_store_server_api/version'
4
+ require_relative 'app_store_server_api/utils/decoder'
5
+ require_relative 'app_store_server_api/utils/http_client'
6
+ require_relative 'app_store_server_api/error'
7
+ require_relative 'app_store_server_api/client'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'app_store_server_api/version'
4
+ require_relative 'app_store_server_api/utils/decoder'
5
+ require_relative 'app_store_server_api/utils/http_client'
6
+ require_relative 'app_store_server_api/error'
7
+ require_relative 'app_store_server_api/client'
@@ -0,0 +1,4 @@
1
+ module AppStoreServerApiClient
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: app_store_server_api_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - mingos
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-02-14 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: jwt
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.8'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.8'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.12'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.12'
40
+ - !ruby/object:Gem::Dependency
41
+ name: retriable
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.1'
54
+ description: Manage your customers' App Store transactions from your server.
55
+ email:
56
+ - mingos@pumb.jp
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - ".rspec"
62
+ - ".rubocop.yml"
63
+ - CHANGELOG.md
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/app_store_server_api/client.rb
68
+ - lib/app_store_server_api/error.rb
69
+ - lib/app_store_server_api/utils/certs/AppleRootCA-G3.cer
70
+ - lib/app_store_server_api/utils/decoder.rb
71
+ - lib/app_store_server_api/utils/http_client.rb
72
+ - lib/app_store_server_api/version.rb
73
+ - lib/app_store_server_api_client.rb
74
+ - lib/app_store_server_api_client.rb~
75
+ - sig/app_store_server_api_client.rbs
76
+ homepage: https://github.com/mingos/app-store-server-api-client
77
+ licenses:
78
+ - MIT
79
+ metadata:
80
+ homepage_uri: https://github.com/mingos/app-store-server-api-client
81
+ source_code_uri: https://github.com/mingos/app-store-server-api-client
82
+ changelog_uri: https://github.com/mingos/app-store-server-api-client/CHANGELOG.md
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 3.3.0
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.6.2
98
+ specification_version: 4
99
+ summary: A Ruby client for App Store Server API
100
+ test_files: []