pay_with_amazon 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +245 -2
- data/lib/pay_with_amazon.rb +3 -0
- data/lib/pay_with_amazon/client.rb +131 -62
- data/lib/pay_with_amazon/client_helper.rb +193 -0
- data/lib/pay_with_amazon/ipn_handler.rb +2 -5
- data/lib/pay_with_amazon/login.rb +75 -0
- data/lib/pay_with_amazon/request.rb +112 -0
- data/lib/pay_with_amazon/response.rb +0 -1
- data/lib/pay_with_amazon/version.rb +1 -1
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 540fd0fdf13854727e8cf3ee539ea88010295308
|
4
|
+
data.tar.gz: 46c573769d9f978f9c597af943d60d96cccb39c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc1788370ce2e5a3c9733ce0dec4cdead496af0617cb9fc1766f5944a49522743ae6d04236ce13dae47ffc0cccbc997ab1aa697b7af4104f3017b2bba6ebec4b
|
7
|
+
data.tar.gz: 4ca87d9bc5a23f623d349203c9dcbb01e1e9b2c45f6ef36172401a14176d4fa60656175fa451d6f4bac5da891416f12275c04921fc877c6b7cc293530329a074
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ or add the following in your Gemfile:
|
|
12
12
|
gem 'pay_with_amazon'
|
13
13
|
```
|
14
14
|
```
|
15
|
-
|
15
|
+
bundle install
|
16
16
|
```
|
17
17
|
|
18
18
|
## Requirements
|
@@ -149,7 +149,7 @@ response.success
|
|
149
149
|
### Instant Payment Notification Verification and Parsing
|
150
150
|
|
151
151
|
```ruby
|
152
|
-
# This can be placed in your controller for a method
|
152
|
+
# This can be placed in your rails controller for a method
|
153
153
|
# that is configured to receive a "POST" IPN from Amazon.
|
154
154
|
headers = request.headers
|
155
155
|
body = request.body.read
|
@@ -179,3 +179,246 @@ ipn.notification_data
|
|
179
179
|
ipn.message_timestamp
|
180
180
|
|
181
181
|
```
|
182
|
+
|
183
|
+
### One Time Transaction API Flow
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
require 'pay_with_amazon'
|
187
|
+
|
188
|
+
# Your Login and Pay with Amazon keys are
|
189
|
+
# available in your Seller Central account
|
190
|
+
merchant_id = 'YOUR_MERCHANT_ID'
|
191
|
+
access_key = 'YOUR_ACCESS_KEY'
|
192
|
+
secret_key = 'YOUR_SECRET_KEY'
|
193
|
+
|
194
|
+
client = PayWithAmazon::Client.new(
|
195
|
+
merchant_id,
|
196
|
+
access_key,
|
197
|
+
secret_key,
|
198
|
+
sandbox: true
|
199
|
+
)
|
200
|
+
|
201
|
+
# These values are grabbed from the Login and Pay
|
202
|
+
# with Amazon Address and Wallet widgets
|
203
|
+
amazon_order_reference_id = 'AMAZON_ORDER_REFERENCE_ID'
|
204
|
+
address_consent_token = 'ADDRESS_CONSENT_TOKEN'
|
205
|
+
|
206
|
+
# To get the buyers full address if shipping/tax
|
207
|
+
# calculations are needed you can use the following
|
208
|
+
# API call to obtain the order reference details.
|
209
|
+
client.get_order_reference_details(
|
210
|
+
amazon_order_reference_id,
|
211
|
+
address_consent_token: address_consent_token
|
212
|
+
)
|
213
|
+
|
214
|
+
# Set the amount for the transaction.
|
215
|
+
amount = '10.00'
|
216
|
+
|
217
|
+
# Make the SetOrderReferenceDetails API call to
|
218
|
+
# configure the Amazon Order Reference Id.
|
219
|
+
# There are additional optional parameters that
|
220
|
+
# are not used below.
|
221
|
+
client.set_order_reference_details(
|
222
|
+
amazon_order_reference_id,
|
223
|
+
amount,
|
224
|
+
currency_code: 'USD', # Default: USD
|
225
|
+
seller_note: 'Your Seller Note',
|
226
|
+
seller_order_id: 'Your Seller Order Id',
|
227
|
+
store_name: 'Your Store Name'
|
228
|
+
)
|
229
|
+
|
230
|
+
# Make the ConfirmOrderReference API call to
|
231
|
+
# confirm the details set in the API call
|
232
|
+
# above.
|
233
|
+
client.confirm_order_reference(amazon_order_reference_id)
|
234
|
+
|
235
|
+
# Set a unique id for your current authorization
|
236
|
+
# of this payment.
|
237
|
+
authorization_reference_id = 'Your Unique Id'
|
238
|
+
|
239
|
+
# Make the Authorize API call to authorize the
|
240
|
+
# transaction. You can also capture the amount
|
241
|
+
# in this API call or make the Capture API call
|
242
|
+
# separately. There are additional optional
|
243
|
+
# parameters not used below.
|
244
|
+
response = client.authorize(
|
245
|
+
amazon_order_reference_id,
|
246
|
+
authorization_reference_id,
|
247
|
+
amount,
|
248
|
+
currency_code: 'USD', # Default: USD
|
249
|
+
seller_authorization_note: 'Your Authorization Note',
|
250
|
+
transaction_timeout: 0, # Set to 0 for synchronous mode
|
251
|
+
capture_now: true # Set this to true if you want to capture the amount in the same API call
|
252
|
+
)
|
253
|
+
|
254
|
+
# You will need the Amazon Authorization Id from the
|
255
|
+
# Authorize API response if you decide to make the
|
256
|
+
# Capture API call separately.
|
257
|
+
amazon_authorization_id = response.get_element('AuthorizeResponse/AuthorizeResult/AuthorizationDetails','AmazonAuthorizationId')
|
258
|
+
|
259
|
+
# Set a unique id for your current capture of
|
260
|
+
# this payment.
|
261
|
+
capture_reference_id = 'Your Unique Id'
|
262
|
+
|
263
|
+
# Make the Capture API call if you did not set the
|
264
|
+
# 'capture_now' parameter to 'true'. There are
|
265
|
+
# additional optional parameters that are not used
|
266
|
+
# below.
|
267
|
+
client.capture(
|
268
|
+
amazon_authorization_id,
|
269
|
+
capture_reference_id,
|
270
|
+
amount,
|
271
|
+
currency_code: 'USD', # Default: USD
|
272
|
+
seller_capture_note: 'Your Capture Note'
|
273
|
+
)
|
274
|
+
|
275
|
+
# Close the order reference once your one time
|
276
|
+
# transaction is complete.
|
277
|
+
client.close_order_reference(amazon_order_reference_id)
|
278
|
+
|
279
|
+
```
|
280
|
+
|
281
|
+
### Subscriptions API Flow
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
require 'pay_with_amazon'
|
285
|
+
|
286
|
+
# Your Login and Pay with Amazon keys are
|
287
|
+
# available in your Seller Central account
|
288
|
+
merchant_id = 'YOUR_MERCHANT_ID'
|
289
|
+
access_key = 'YOUR_ACCESS_KEY'
|
290
|
+
secret_key = 'YOUR_SECRET_KEY'
|
291
|
+
|
292
|
+
client = PayWithAmazon::Client.new(
|
293
|
+
merchant_id,
|
294
|
+
access_key,
|
295
|
+
secret_key,
|
296
|
+
sandbox: true
|
297
|
+
)
|
298
|
+
|
299
|
+
# These values are grabbed from the Login and Pay
|
300
|
+
# with Amazon Address and Wallet widgets
|
301
|
+
amazon_billing_agreement_id = 'AMAZON_BILLING_AGREEMENT_ID'
|
302
|
+
address_consent_token = 'ADDRESS_CONSENT_TOKEN'
|
303
|
+
|
304
|
+
# To get the buyers full address, if shipping/tax
|
305
|
+
# calculations are needed, you can use the following
|
306
|
+
# API call to obtain the billing agreement details.
|
307
|
+
client.get_billing_agreement_details(
|
308
|
+
amazon_billing_agreement_id,
|
309
|
+
address_consent_token: address_consent_token
|
310
|
+
)
|
311
|
+
|
312
|
+
# Next you will need to set the various details
|
313
|
+
# for this subscription with the following API call.
|
314
|
+
# There are additional optional parameters that
|
315
|
+
# are not used below.
|
316
|
+
client.set_billing_agreement_details(
|
317
|
+
amazon_billing_agreement_id,
|
318
|
+
seller_note: 'Your Seller Note',
|
319
|
+
seller_billing_agreement_id: 'Your Transaction Id',
|
320
|
+
store_name: 'Your Store Name',
|
321
|
+
custom_information: 'Additional Information'
|
322
|
+
)
|
323
|
+
|
324
|
+
# Make the ConfirmBillingAgreement API call to confirm
|
325
|
+
# the Amazon Billing Agreement Id with the details set above.
|
326
|
+
# Be sure that everything is set correctly above before
|
327
|
+
# confirming.
|
328
|
+
client.confirm_billing_agreement(amazon_billing_agreement_id)
|
329
|
+
|
330
|
+
# The following API call is not needed at this point, but
|
331
|
+
# can be used in the future when you need to validate that
|
332
|
+
# the payment method is still valid with the associated billing
|
333
|
+
# agreement id.
|
334
|
+
client.validate_billing_agreement(amazon_billing_agreement_id)
|
335
|
+
|
336
|
+
# Set the amount for your first authorization.
|
337
|
+
amount = '10.00'
|
338
|
+
|
339
|
+
# Set a unique authorization reference id for your
|
340
|
+
# first transaction on the billing agreement.
|
341
|
+
authorization_reference_id = 'Your Unique Id'
|
342
|
+
|
343
|
+
# Now you can authorize your first transaction on the
|
344
|
+
# billing agreement id. Every month you can make the
|
345
|
+
# same API call to continue charging your buyer
|
346
|
+
# with the 'capture_now' parameter set to true. You can
|
347
|
+
# also make the Capture API call separately. There are
|
348
|
+
# additional optional parameters that are not used
|
349
|
+
# below.
|
350
|
+
response = client.authorize_on_billing_agreement(
|
351
|
+
amazon_billing_agreement_id,
|
352
|
+
authorization_reference_id,
|
353
|
+
amount,
|
354
|
+
currency_code: 'USD', # Default: USD
|
355
|
+
seller_authorization_note: 'Your Authorization Note',
|
356
|
+
transaction_timeout: 0, # Set to 0 for synchronous mode
|
357
|
+
capture_now: true, # Set this to true if you want to capture the amount in the same API call
|
358
|
+
seller_note: 'Your Seller Note',
|
359
|
+
seller_order_id: 'Your Order Id',
|
360
|
+
store_name: 'Your Store Name',
|
361
|
+
custom_information: 'Additional Information'
|
362
|
+
)
|
363
|
+
|
364
|
+
# You will need the Amazon Authorization Id from the
|
365
|
+
# AuthorizeOnBillingAgreement API response if you decide
|
366
|
+
# to make the Capture API call separately.
|
367
|
+
amazon_authorization_id = response.get_element('AuthorizeOnBillingAgreementResponse/AuthorizeOnBillingAgreementResult/AuthorizationDetails','AmazonAuthorizationId')
|
368
|
+
|
369
|
+
# Set a unique id for your current capture of
|
370
|
+
# this transaction.
|
371
|
+
capture_reference_id = 'Your Unique Id'
|
372
|
+
|
373
|
+
# Make the Capture API call if you did not set the
|
374
|
+
# 'capture_now' parameter to 'true'. There are
|
375
|
+
# additional optional parameters that are not used
|
376
|
+
# below.
|
377
|
+
client.capture(
|
378
|
+
amazon_authorization_id,
|
379
|
+
capture_reference_id,
|
380
|
+
amount,
|
381
|
+
currency_code: 'USD', # Default: USD
|
382
|
+
seller_capture_note: 'Your Capture Note'
|
383
|
+
)
|
384
|
+
|
385
|
+
# The following API call should not be made until you
|
386
|
+
# are ready to terminate the billing agreement.
|
387
|
+
client.close_billing_agreement(
|
388
|
+
amazon_billing_agreement_id,
|
389
|
+
closure_reason: 'Reason For Closing'
|
390
|
+
)
|
391
|
+
|
392
|
+
```
|
393
|
+
|
394
|
+
### Get Login Profile API
|
395
|
+
|
396
|
+
This API call allows you to obtain user profile information
|
397
|
+
once a user has logged into your application using
|
398
|
+
their Amazon credentials.
|
399
|
+
|
400
|
+
```ruby
|
401
|
+
require 'pay_with_amazon'
|
402
|
+
|
403
|
+
# Your client id is located in your Seller
|
404
|
+
# Central account.
|
405
|
+
client_id = 'Your Client Id'
|
406
|
+
|
407
|
+
login = PayWithAmazon::Login.new(
|
408
|
+
client_id,
|
409
|
+
region: :na, # Default: :na
|
410
|
+
sandbox: true # Default: false
|
411
|
+
)
|
412
|
+
|
413
|
+
# The access token is available in the return URL
|
414
|
+
# parameters after a user has logged in.
|
415
|
+
access_token = 'User Access Token'
|
416
|
+
|
417
|
+
# Make the 'get_login_profile' api call.
|
418
|
+
profile = login.get_login_profile(access_token)
|
419
|
+
|
420
|
+
name = profile['name']
|
421
|
+
email = profile['email']
|
422
|
+
user_id = profile['user_id']
|
423
|
+
|
424
|
+
```
|
data/lib/pay_with_amazon.rb
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
require 'uri'
|
2
|
-
require 'net/http'
|
3
|
-
require 'net/https'
|
4
|
-
require 'base64'
|
5
|
-
require 'openssl'
|
6
1
|
require 'time'
|
7
2
|
|
8
3
|
module PayWithAmazon
|
@@ -14,8 +9,6 @@ module PayWithAmazon
|
|
14
9
|
# uses the standard Ruby library and is not dependant on Rails.
|
15
10
|
class Client
|
16
11
|
|
17
|
-
MAX_RETRIES = 4
|
18
|
-
|
19
12
|
attr_reader(
|
20
13
|
:merchant_id,
|
21
14
|
:access_key,
|
@@ -36,7 +29,7 @@ module PayWithAmazon
|
|
36
29
|
:proxy_pass)
|
37
30
|
|
38
31
|
# API keys are located at:
|
39
|
-
# @see
|
32
|
+
# @see https://sellercentral.amazon.com
|
40
33
|
# @param merchant_id [String]
|
41
34
|
# @param access_key [String]
|
42
35
|
# @param secret_key [String]
|
@@ -44,7 +37,7 @@ module PayWithAmazon
|
|
44
37
|
# @optional currency_code [Symbol] Default: :usd
|
45
38
|
# @optional region [Symbol] Default: :na
|
46
39
|
# @optional platform_id [String] Default: nil
|
47
|
-
# @optional throttle [Boolean]
|
40
|
+
# @optional throttle [Boolean] Default: true
|
48
41
|
# @optional application_name [String]
|
49
42
|
# @optional application_version [String]
|
50
43
|
# @optional proxy_addr [String]
|
@@ -95,8 +88,7 @@ module PayWithAmazon
|
|
95
88
|
@default_hash['PlatformId'] = @platform_id if @platform_id
|
96
89
|
end
|
97
90
|
|
98
|
-
#
|
99
|
-
# The GetServiceStatus operation returns the operational status of the Off-Amazon Payments API
|
91
|
+
# The GetServiceStatus operation returns the operational status of the Amazon Payments API
|
100
92
|
# section of Amazon Marketplace Web Service (Amazon MWS). Status values are GREEN, GREEN_I, YELLOW, and RED.
|
101
93
|
# @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetServiceStatus.html
|
102
94
|
def get_service_status
|
@@ -306,7 +298,7 @@ module PayWithAmazon
|
|
306
298
|
custom_information: nil,
|
307
299
|
seller_order_id: nil,
|
308
300
|
store_name: nil,
|
309
|
-
|
301
|
+
inherit_shipping_address: nil,
|
310
302
|
merchant_id: @merchant_id,
|
311
303
|
mws_auth_token: nil)
|
312
304
|
|
@@ -329,7 +321,7 @@ module PayWithAmazon
|
|
329
321
|
'SellerOrderAttributes.CustomInformation' => custom_information,
|
330
322
|
'SellerOrderAttributes.SellerOrderId' => seller_order_id,
|
331
323
|
'SellerOrderAttributes.StoreName' => store_name,
|
332
|
-
'InheritShippingAddress' =>
|
324
|
+
'InheritShippingAddress' => inherit_shipping_address,
|
333
325
|
'MWSAuthToken' => mws_auth_token
|
334
326
|
}
|
335
327
|
|
@@ -496,6 +488,7 @@ module PayWithAmazon
|
|
496
488
|
# @optional transaction_timeout [Integer]
|
497
489
|
# @optional capture_now [Boolean]
|
498
490
|
# @optional soft_descriptor [String]
|
491
|
+
# @optional provider_credit_details [Array of Hash]
|
499
492
|
# @optional merchant_id [String]
|
500
493
|
# @optional mws_auth_token [String]
|
501
494
|
def authorize(
|
@@ -507,6 +500,7 @@ module PayWithAmazon
|
|
507
500
|
transaction_timeout: nil,
|
508
501
|
capture_now: nil,
|
509
502
|
soft_descriptor: nil,
|
503
|
+
provider_credit_details: nil,
|
510
504
|
merchant_id: @merchant_id,
|
511
505
|
mws_auth_token: nil)
|
512
506
|
|
@@ -527,6 +521,8 @@ module PayWithAmazon
|
|
527
521
|
'MWSAuthToken' => mws_auth_token
|
528
522
|
}
|
529
523
|
|
524
|
+
optional.merge!(set_provider_credit_details(provider_credit_details)) if provider_credit_details
|
525
|
+
|
530
526
|
operation(parameters, optional)
|
531
527
|
end
|
532
528
|
|
@@ -562,6 +558,7 @@ module PayWithAmazon
|
|
562
558
|
# @optional currency_code [String]
|
563
559
|
# @optional seller_capture_note [String]
|
564
560
|
# @optional soft_descriptor [String]
|
561
|
+
# @optional provider_credit_details [Array of Hash]
|
565
562
|
# @optional merchant_id [String]
|
566
563
|
# @optional mws_auth_token [String]
|
567
564
|
def capture(
|
@@ -571,6 +568,7 @@ module PayWithAmazon
|
|
571
568
|
currency_code: @currency_code,
|
572
569
|
seller_capture_note: nil,
|
573
570
|
soft_descriptor: nil,
|
571
|
+
provider_credit_details: nil,
|
574
572
|
merchant_id: @merchant_id,
|
575
573
|
mws_auth_token: nil)
|
576
574
|
|
@@ -589,6 +587,8 @@ module PayWithAmazon
|
|
589
587
|
'MWSAuthToken' => mws_auth_token
|
590
588
|
}
|
591
589
|
|
590
|
+
optional.merge!(set_provider_credit_details(provider_credit_details)) if provider_credit_details
|
591
|
+
|
592
592
|
operation(parameters, optional)
|
593
593
|
end
|
594
594
|
|
@@ -624,6 +624,7 @@ module PayWithAmazon
|
|
624
624
|
# @optional currency_code [String]
|
625
625
|
# @optional seller_refund_note [String]
|
626
626
|
# @optional soft_descriptor [String]
|
627
|
+
# @optional provider_credit_reversal_details [Array of Hash]
|
627
628
|
# @optional merchant_id [String]
|
628
629
|
# @optional mws_auth_token [String]
|
629
630
|
def refund(
|
@@ -633,6 +634,7 @@ module PayWithAmazon
|
|
633
634
|
currency_code: @currency_code,
|
634
635
|
seller_refund_note: nil,
|
635
636
|
soft_descriptor: nil,
|
637
|
+
provider_credit_reversal_details: nil,
|
636
638
|
merchant_id: @merchant_id,
|
637
639
|
mws_auth_token: nil)
|
638
640
|
|
@@ -651,6 +653,8 @@ module PayWithAmazon
|
|
651
653
|
'MWSAuthToken' => mws_auth_token
|
652
654
|
}
|
653
655
|
|
656
|
+
optional.merge!(set_provider_credit_reversal_details(provider_credit_reversal_details)) if provider_credit_reversal_details
|
657
|
+
|
654
658
|
operation(parameters, optional)
|
655
659
|
end
|
656
660
|
|
@@ -731,6 +735,81 @@ module PayWithAmazon
|
|
731
735
|
operation(parameters, optional)
|
732
736
|
end
|
733
737
|
|
738
|
+
# @param amazon_provider_credit_id [String]
|
739
|
+
# @optional merchant_id [String]
|
740
|
+
# @optional mws_auth_token [String]
|
741
|
+
def get_provider_credit_details(
|
742
|
+
amazon_provider_credit_id,
|
743
|
+
merchant_id: @merchant_id,
|
744
|
+
mws_auth_token: nil)
|
745
|
+
|
746
|
+
parameters = {
|
747
|
+
'Action' => 'GetProviderCreditDetails',
|
748
|
+
'SellerId' => merchant_id,
|
749
|
+
'AmazonProviderCreditId' => amazon_provider_credit_id
|
750
|
+
}
|
751
|
+
|
752
|
+
optional = {
|
753
|
+
'MWSAuthToken' => mws_auth_token
|
754
|
+
}
|
755
|
+
|
756
|
+
operation(parameters, optional)
|
757
|
+
end
|
758
|
+
|
759
|
+
# @param amazon_provider_credit_reversal_id [String]
|
760
|
+
# @optional merchant_id [String]
|
761
|
+
# @optional mws_auth_token [String]
|
762
|
+
def get_provider_credit_reversal_details(
|
763
|
+
amazon_provider_credit_reversal_id,
|
764
|
+
merchant_id: @merchant_id,
|
765
|
+
mws_auth_token: nil)
|
766
|
+
|
767
|
+
parameters = {
|
768
|
+
'Action' => 'GetProviderCreditReversalDetails',
|
769
|
+
'SellerId' => merchant_id,
|
770
|
+
'AmazonProviderCreditReversalId' => amazon_provider_credit_reversal_id
|
771
|
+
}
|
772
|
+
|
773
|
+
optional = {
|
774
|
+
'MWSAuthToken' => mws_auth_token
|
775
|
+
}
|
776
|
+
|
777
|
+
operation(parameters, optional)
|
778
|
+
end
|
779
|
+
|
780
|
+
# @param amazon_provider_credit_id [String]
|
781
|
+
# @param credit_reversal_reference_id [String]
|
782
|
+
# @param amount [String]
|
783
|
+
# @optional currency_code [String]
|
784
|
+
# @optional credit_reversal_note [String]
|
785
|
+
# @optional merchant_id [String]
|
786
|
+
# @optional mws_auth_token [String]
|
787
|
+
def reverse_provider_credit(
|
788
|
+
amazon_provider_credit_id,
|
789
|
+
credit_reversal_reference_id,
|
790
|
+
amount,
|
791
|
+
currency_code: @currency_code,
|
792
|
+
credit_reversal_note: nil,
|
793
|
+
merchant_id: @merchant_id,
|
794
|
+
mws_auth_token: nil)
|
795
|
+
|
796
|
+
parameters = {
|
797
|
+
'Action' => 'ReverseProviderCredit',
|
798
|
+
'SellerId' => merchant_id,
|
799
|
+
'AmazonProviderCreditId' => amazon_provider_credit_id,
|
800
|
+
'CreditReversalReferenceId' => credit_reversal_reference_id,
|
801
|
+
'CreditReversalAmount.Amount' => amount,
|
802
|
+
'CreditReversalAmount.CurrencyCode' => currency_code
|
803
|
+
}
|
804
|
+
|
805
|
+
optional = {
|
806
|
+
'CreditReversalNote' => credit_reversal_note,
|
807
|
+
'MWSAuthToken' => mws_auth_token
|
808
|
+
}
|
809
|
+
|
810
|
+
operation(parameters, optional)
|
811
|
+
end
|
812
|
+
|
734
813
|
private
|
735
814
|
|
736
815
|
def region_hash
|
@@ -744,61 +823,51 @@ module PayWithAmazon
|
|
744
823
|
}
|
745
824
|
end
|
746
825
|
|
747
|
-
# This method
|
748
|
-
#
|
749
|
-
#
|
750
|
-
def
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
826
|
+
# This method builds the provider credit details hash
|
827
|
+
# that will be combined with either the authorize or capture
|
828
|
+
# API call.
|
829
|
+
def set_provider_credit_details(provider_credit_details)
|
830
|
+
member_details = {}
|
831
|
+
provider_credit_details.each_with_index { |val, index|
|
832
|
+
member = index + 1
|
833
|
+
member_details["ProviderCreditList.member.#{member}.ProviderId"] = val[:provider_id]
|
834
|
+
member_details["ProviderCreditList.member.#{member}.CreditAmount.Amount"] = val[:amount]
|
835
|
+
member_details["ProviderCreditList.member.#{member}.CreditAmount.CurrencyCode"] = val[:currency_code]
|
836
|
+
}
|
758
837
|
|
759
|
-
|
760
|
-
# using the secret key provided.
|
761
|
-
def sign(post_body)
|
762
|
-
custom_escape(Base64.strict_encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @secret_key, post_body)))
|
838
|
+
return member_details
|
763
839
|
end
|
764
840
|
|
765
|
-
# This method
|
766
|
-
#
|
767
|
-
#
|
768
|
-
def
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
response = https.post(uri.path, post_url, user_agent)
|
777
|
-
if @throttle.eql?(true)
|
778
|
-
if response.code.eql?('500')
|
779
|
-
raise 'InternalServerError'
|
780
|
-
elsif response.code.eql?('503')
|
781
|
-
raise 'ServiceUnavailable or RequestThrottled'
|
782
|
-
end
|
783
|
-
end
|
784
|
-
PayWithAmazon::Response.new(response)
|
785
|
-
rescue => error
|
786
|
-
tries += 1
|
787
|
-
sleep(get_seconds_for_try_count(tries))
|
788
|
-
retry if tries < MAX_RETRIES
|
789
|
-
raise error.message
|
790
|
-
end
|
791
|
-
end
|
841
|
+
# This method builds the provider credit reversal
|
842
|
+
# details hash that will be combined with the refund
|
843
|
+
# API call.
|
844
|
+
def set_provider_credit_reversal_details(provider_credit_reversal_details)
|
845
|
+
member_details = {}
|
846
|
+
provider_credit_reversal_details.each_with_index { |val, index|
|
847
|
+
member = index + 1
|
848
|
+
member_details["ProviderCreditReversalList.member.#{member}.ProviderId"] = val[:provider_id]
|
849
|
+
member_details["ProviderCreditReversalList.member.#{member}.CreditReversalAmount.Amount"] = val[:amount]
|
850
|
+
member_details["ProviderCreditReversalList.member.#{member}.CreditReversalAmount.CurrencyCode"] = val[:currency_code]
|
851
|
+
}
|
792
852
|
|
793
|
-
|
794
|
-
seconds = { 1=>1, 2=>4, 3=>10, 4=>0 }
|
795
|
-
seconds[try_count]
|
853
|
+
return member_details
|
796
854
|
end
|
797
855
|
|
798
|
-
def
|
799
|
-
|
800
|
-
|
801
|
-
|
856
|
+
def operation(parameters, optional)
|
857
|
+
PayWithAmazon::Request.new(
|
858
|
+
parameters,
|
859
|
+
optional,
|
860
|
+
@default_hash,
|
861
|
+
@mws_endpoint,
|
862
|
+
@sandbox_str,
|
863
|
+
@secret_key,
|
864
|
+
@proxy_addr,
|
865
|
+
@proxy_port,
|
866
|
+
@proxy_user,
|
867
|
+
@proxy_pass,
|
868
|
+
@throttle,
|
869
|
+
@application_name,
|
870
|
+
@application_version).send_post
|
802
871
|
end
|
803
872
|
|
804
873
|
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module PayWithAmazon
|
2
|
+
|
3
|
+
# This will extend the client class to add additional
|
4
|
+
# helper methods that combine core API calls.
|
5
|
+
class Client
|
6
|
+
|
7
|
+
# This method combines multiple API calls to perform
|
8
|
+
# a complete transaction with minimum requirements.
|
9
|
+
# @param amazon_reference_id [String]
|
10
|
+
# @param authorization_reference_id [String]
|
11
|
+
# @param charge_amount [String]
|
12
|
+
# @optional charge_currency_code [String]
|
13
|
+
# @optional charge_note [String]
|
14
|
+
# @optional charge_order [String]
|
15
|
+
# @optional store_name [String]
|
16
|
+
# @optional custom_information [String]
|
17
|
+
# @optional soft_descriptor [String]
|
18
|
+
# @optional platform_id [String]
|
19
|
+
# @optional merchant_id [String]
|
20
|
+
# @optional mws_auth_token [String]
|
21
|
+
def charge(
|
22
|
+
amazon_reference_id,
|
23
|
+
authorization_reference_id,
|
24
|
+
charge_amount,
|
25
|
+
charge_currency_code: @currency_code,
|
26
|
+
charge_note: nil,
|
27
|
+
charge_order_id: nil,
|
28
|
+
store_name: nil,
|
29
|
+
custom_information: nil,
|
30
|
+
soft_descriptor: nil,
|
31
|
+
platform_id: nil,
|
32
|
+
merchant_id: @merchant_id,
|
33
|
+
mws_auth_token: nil)
|
34
|
+
|
35
|
+
if is_order_reference?(amazon_reference_id)
|
36
|
+
response = call_order_reference_api(
|
37
|
+
amazon_reference_id,
|
38
|
+
authorization_reference_id,
|
39
|
+
charge_amount,
|
40
|
+
charge_currency_code,
|
41
|
+
charge_note,
|
42
|
+
charge_order_id,
|
43
|
+
store_name,
|
44
|
+
custom_information,
|
45
|
+
soft_descriptor,
|
46
|
+
platform_id,
|
47
|
+
merchant_id,
|
48
|
+
mws_auth_token)
|
49
|
+
return response
|
50
|
+
end
|
51
|
+
|
52
|
+
if is_billing_agreement?(amazon_reference_id)
|
53
|
+
response = call_billing_agreement_api(
|
54
|
+
amazon_reference_id,
|
55
|
+
authorization_reference_id,
|
56
|
+
charge_amount,
|
57
|
+
charge_currency_code,
|
58
|
+
charge_note,
|
59
|
+
charge_order_id,
|
60
|
+
store_name,
|
61
|
+
custom_information,
|
62
|
+
soft_descriptor,
|
63
|
+
platform_id,
|
64
|
+
merchant_id,
|
65
|
+
mws_auth_token)
|
66
|
+
return response
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def call_order_reference_api(
|
73
|
+
amazon_reference_id,
|
74
|
+
authorization_reference_id,
|
75
|
+
charge_amount,
|
76
|
+
charge_currency_code,
|
77
|
+
charge_note,
|
78
|
+
charge_order_id,
|
79
|
+
store_name,
|
80
|
+
custom_information,
|
81
|
+
soft_descriptor,
|
82
|
+
platform_id,
|
83
|
+
merchant_id,
|
84
|
+
mws_auth_token)
|
85
|
+
|
86
|
+
response = set_order_reference_details(
|
87
|
+
amazon_reference_id,
|
88
|
+
charge_amount,
|
89
|
+
currency_code: charge_currency_code,
|
90
|
+
platform_id: platform_id,
|
91
|
+
seller_note: charge_note,
|
92
|
+
seller_order_id: charge_order_id,
|
93
|
+
store_name: store_name,
|
94
|
+
custom_information: custom_information,
|
95
|
+
merchant_id: merchant_id,
|
96
|
+
mws_auth_token: mws_auth_token)
|
97
|
+
if response.success
|
98
|
+
response = confirm_order_reference(
|
99
|
+
amazon_reference_id,
|
100
|
+
merchant_id: merchant_id,
|
101
|
+
mws_auth_token: mws_auth_token)
|
102
|
+
if response.success
|
103
|
+
response = authorize(
|
104
|
+
amazon_reference_id,
|
105
|
+
authorization_reference_id,
|
106
|
+
charge_amount,
|
107
|
+
currency_code: charge_currency_code,
|
108
|
+
seller_authorization_note: charge_note,
|
109
|
+
transaction_timeout: 0,
|
110
|
+
capture_now: true,
|
111
|
+
soft_descriptor: soft_descriptor,
|
112
|
+
merchant_id: merchant_id,
|
113
|
+
mws_auth_token: mws_auth_token)
|
114
|
+
return response
|
115
|
+
else
|
116
|
+
return response
|
117
|
+
end
|
118
|
+
else
|
119
|
+
return response
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def call_billing_agreement_api(
|
124
|
+
amazon_reference_id,
|
125
|
+
authorization_reference_id,
|
126
|
+
charge_amount,
|
127
|
+
charge_currency_code,
|
128
|
+
charge_note,
|
129
|
+
charge_order_id,
|
130
|
+
store_name,
|
131
|
+
custom_information,
|
132
|
+
soft_descriptor,
|
133
|
+
platform_id,
|
134
|
+
merchant_id,
|
135
|
+
mws_auth_token)
|
136
|
+
|
137
|
+
response = get_billing_agreement_details(
|
138
|
+
amazon_reference_id,
|
139
|
+
merchant_id: merchant_id,
|
140
|
+
mws_auth_token: mws_auth_token)
|
141
|
+
if response.get_element('GetBillingAgreementDetailsResponse/GetBillingAgreementDetailsResult/BillingAgreementDetails/BillingAgreementStatus','State').eql?('Draft')
|
142
|
+
response = set_billing_agreement_details(
|
143
|
+
amazon_reference_id,
|
144
|
+
platform_id: platform_id,
|
145
|
+
seller_note: charge_note,
|
146
|
+
seller_billing_agreement_id: charge_order_id,
|
147
|
+
store_name: store_name,
|
148
|
+
custom_information: custom_information,
|
149
|
+
merchant_id: merchant_id,
|
150
|
+
mws_auth_token: mws_auth_token)
|
151
|
+
if response.success
|
152
|
+
response = confirm_billing_agreement(
|
153
|
+
amazon_reference_id,
|
154
|
+
merchant_id: merchant_id,
|
155
|
+
mws_auth_token: mws_auth_token)
|
156
|
+
if response.success.eql?(false)
|
157
|
+
return response
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
response = authorize_on_billing_agreement(
|
163
|
+
amazon_reference_id,
|
164
|
+
authorization_reference_id,
|
165
|
+
charge_amount,
|
166
|
+
currency_code: charge_currency_code,
|
167
|
+
seller_authorization_note: charge_note,
|
168
|
+
transaction_timeout: 0,
|
169
|
+
capture_now: true,
|
170
|
+
soft_descriptor: soft_descriptor,
|
171
|
+
seller_note: charge_note,
|
172
|
+
platform_id: platform_id,
|
173
|
+
seller_order_id: charge_order_id,
|
174
|
+
store_name: store_name,
|
175
|
+
custom_information: custom_information,
|
176
|
+
inherit_shipping_address: true,
|
177
|
+
merchant_id: merchant_id,
|
178
|
+
mws_auth_token: mws_auth_token)
|
179
|
+
return response
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def is_order_reference?(amazon_reference_id)
|
184
|
+
amazon_reference_id.start_with?('S','P')
|
185
|
+
end
|
186
|
+
|
187
|
+
def is_billing_agreement?(amazon_reference_id)
|
188
|
+
amazon_reference_id.start_with?('C','B')
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
@@ -31,10 +31,7 @@ module PayWithAmazon
|
|
31
31
|
attr_reader(:headers, :body)
|
32
32
|
attr_accessor(:proxy_addr, :proxy_port, :proxy_user, :proxy_pass)
|
33
33
|
|
34
|
-
#
|
35
|
-
# pass in the request header and body. The body can be
|
36
|
-
# passed in parsed by json or without.
|
37
|
-
# @param header [request.headers]
|
34
|
+
# @param headers [request.headers]
|
38
35
|
# @param body [request.body.read]
|
39
36
|
# @optional proxy_addr [String]
|
40
37
|
# @optional proxy_port [String]
|
@@ -58,7 +55,7 @@ module PayWithAmazon
|
|
58
55
|
end
|
59
56
|
|
60
57
|
# This method will authenticate the ipn message sent from Amazon.
|
61
|
-
# It will return true if everything is verified. It will
|
58
|
+
# It will return true if everything is verified. It will raise an
|
62
59
|
# error message if verification fails.
|
63
60
|
def authentic?
|
64
61
|
begin
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'json'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
module PayWithAmazon
|
8
|
+
|
9
|
+
# Login with Amazon API
|
10
|
+
#
|
11
|
+
# This class allows you to obtain user profile
|
12
|
+
# information once a user has logged into your
|
13
|
+
# application using their Amazon credentials.
|
14
|
+
class Login
|
15
|
+
|
16
|
+
attr_reader(:region)
|
17
|
+
|
18
|
+
attr_accessor(:client_id, :sandbox)
|
19
|
+
|
20
|
+
# @param client_id [String]
|
21
|
+
# @optional region [Symbol] Default: :na
|
22
|
+
# @optional sandbox [Boolean] Default: false
|
23
|
+
def initialize(client_id, region: :na, sandbox: false)
|
24
|
+
@client_id = client_id
|
25
|
+
@region = region
|
26
|
+
@endpoint = region_hash[@region]
|
27
|
+
@sandbox = sandbox
|
28
|
+
@sandbox_str = @sandbox ? "api.sandbox" : "api"
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method will validate the access token and
|
32
|
+
# return the user's profile information.
|
33
|
+
# @param access_token [String]
|
34
|
+
def get_login_profile(access_token)
|
35
|
+
decoded_access_token = URI.decode(access_token)
|
36
|
+
encoded_access_token = URI.encode(decoded_access_token)
|
37
|
+
uri = URI("https://#{@sandbox_str}.#{@endpoint}/auth/o2/tokeninfo?access_token=#{encoded_access_token}")
|
38
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
39
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
40
|
+
http.use_ssl = true
|
41
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
42
|
+
response = http.request(req)
|
43
|
+
decode = JSON.parse(response.body)
|
44
|
+
|
45
|
+
if decode['aud'] != @client_id
|
46
|
+
raise "Invalid Access Token"
|
47
|
+
end
|
48
|
+
|
49
|
+
uri = URI.parse("https://#{@sandbox_str}.#{@endpoint}/user/profile")
|
50
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
51
|
+
req['Authorization'] = "bearer " + decoded_access_token
|
52
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
53
|
+
http.use_ssl = true
|
54
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
55
|
+
response = http.request(req)
|
56
|
+
decoded_login_profile = JSON.parse(response.body)
|
57
|
+
return decoded_login_profile
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def region_hash
|
63
|
+
{
|
64
|
+
:jp => 'amazon.co.jp',
|
65
|
+
:uk => 'amazon.co.uk',
|
66
|
+
:de => 'amazon.de',
|
67
|
+
:eu => 'amazon.co.uk',
|
68
|
+
:us => 'amazon.com',
|
69
|
+
:na => 'amazon.com'
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'base64'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
module PayWithAmazon
|
8
|
+
|
9
|
+
# This class creates the request to send to the
|
10
|
+
# specified MWS endpoint.
|
11
|
+
class Request
|
12
|
+
|
13
|
+
MAX_RETRIES = 3
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
parameters,
|
17
|
+
optional,
|
18
|
+
default_hash,
|
19
|
+
mws_endpoint,
|
20
|
+
sandbox_str,
|
21
|
+
secret_key,
|
22
|
+
proxy_addr,
|
23
|
+
proxy_port,
|
24
|
+
proxy_user,
|
25
|
+
proxy_pass,
|
26
|
+
throttle,
|
27
|
+
application_name,
|
28
|
+
application_version)
|
29
|
+
|
30
|
+
@parameters = parameters
|
31
|
+
@optional = optional
|
32
|
+
@default_hash = default_hash
|
33
|
+
@mws_endpoint = mws_endpoint
|
34
|
+
@sandbox_str = sandbox_str
|
35
|
+
@secret_key = secret_key
|
36
|
+
@proxy_addr = proxy_addr
|
37
|
+
@proxy_port = proxy_port
|
38
|
+
@proxy_user = proxy_user
|
39
|
+
@proxy_pass = proxy_pass
|
40
|
+
@throttle = throttle
|
41
|
+
@application_name = application_name
|
42
|
+
@application_version = application_version
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method sends the post request.
|
46
|
+
def send_post
|
47
|
+
post_url = build_post_url
|
48
|
+
post(@mws_endpoint, @sandbox_str, post_url)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# This method combines the required and optional
|
54
|
+
# parameters to sign the post body and generate
|
55
|
+
# the post url.
|
56
|
+
def build_post_url
|
57
|
+
@optional.map { |k, v| @parameters[k] = v unless v.nil? }
|
58
|
+
@parameters = @default_hash.merge(@parameters)
|
59
|
+
post_url = @parameters.sort.map { |k, v| "#{k}=#{ custom_escape(v) }" }.join("&")
|
60
|
+
post_body = ["POST", "#{@mws_endpoint}", "/#{@sandbox_str}/#{PayWithAmazon::API_VERSION}", post_url].join("\n")
|
61
|
+
post_url += "&Signature=" + sign(post_body)
|
62
|
+
return post_url
|
63
|
+
end
|
64
|
+
|
65
|
+
# This method signs the post body that is being sent
|
66
|
+
# using the secret key provided.
|
67
|
+
def sign(post_body)
|
68
|
+
custom_escape(Base64.strict_encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @secret_key, post_body)))
|
69
|
+
end
|
70
|
+
|
71
|
+
# This method performs the post to the MWS endpoint.
|
72
|
+
# It will retry three times after the initial post if
|
73
|
+
# the status code comes back as either 500 or 503.
|
74
|
+
def post(mws_endpoint, sandbox_str, post_url)
|
75
|
+
uri = URI("https://#{mws_endpoint}/#{sandbox_str}/#{PayWithAmazon::API_VERSION}")
|
76
|
+
https = Net::HTTP.new(uri.host, uri.port, @proxy_addr, @proxy_port, @proxy_user, @proxy_pass)
|
77
|
+
https.use_ssl = true
|
78
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
79
|
+
user_agent = {"User-Agent" => "Language=Ruby; ApplicationLibraryVersion=#{PayWithAmazon::VERSION}; Platform=#{RUBY_PLATFORM}; MWSClientVersion=#{PayWithAmazon::API_VERSION}; ApplicationName=#{@application_name}; ApplicationVersion=#{@application_version}"}
|
80
|
+
tries = 0
|
81
|
+
begin
|
82
|
+
response = https.post(uri.path, post_url, user_agent)
|
83
|
+
if @throttle.eql?(true)
|
84
|
+
if response.code.eql?('500')
|
85
|
+
raise 'InternalServerError'
|
86
|
+
elsif response.code.eql?('503')
|
87
|
+
raise 'ServiceUnavailable or RequestThrottled'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
PayWithAmazon::Response.new(response)
|
91
|
+
rescue => error
|
92
|
+
tries += 1
|
93
|
+
sleep(get_seconds_for_try_count(tries))
|
94
|
+
retry if tries <= MAX_RETRIES
|
95
|
+
raise error.message
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_seconds_for_try_count(try_count)
|
100
|
+
seconds = { 1=>1, 2=>4, 3=>10, 4=>0 }
|
101
|
+
seconds[try_count]
|
102
|
+
end
|
103
|
+
|
104
|
+
def custom_escape(val)
|
105
|
+
val.to_s.gsub(/([^\w.~-]+)/) do
|
106
|
+
"%" + $1.unpack("H2" * $1.bytesize).join("%").upcase
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pay_with_amazon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amazon Payments
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: Login and Pay with Amazon Ruby SDK
|
14
|
-
email:
|
13
|
+
description: Amazon Payments - Login and Pay with Amazon Ruby SDK
|
14
|
+
email: pay-with-amazon-sdk@amazon.com
|
15
15
|
executables: []
|
16
16
|
extensions: []
|
17
17
|
extra_rdoc_files: []
|
@@ -21,7 +21,10 @@ files:
|
|
21
21
|
- README.md
|
22
22
|
- lib/pay_with_amazon.rb
|
23
23
|
- lib/pay_with_amazon/client.rb
|
24
|
+
- lib/pay_with_amazon/client_helper.rb
|
24
25
|
- lib/pay_with_amazon/ipn_handler.rb
|
26
|
+
- lib/pay_with_amazon/login.rb
|
27
|
+
- lib/pay_with_amazon/request.rb
|
25
28
|
- lib/pay_with_amazon/response.rb
|
26
29
|
- lib/pay_with_amazon/version.rb
|
27
30
|
homepage: https://github.com/amzn/login-and-pay-with-amazon-sdk-ruby
|
@@ -47,6 +50,6 @@ rubyforge_project:
|
|
47
50
|
rubygems_version: 2.4.5
|
48
51
|
signing_key:
|
49
52
|
specification_version: 4
|
50
|
-
summary: Login and Pay with Amazon Ruby SDK
|
53
|
+
summary: Amazon Payments - Login and Pay with Amazon Ruby SDK
|
51
54
|
test_files: []
|
52
55
|
has_rdoc:
|