diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e127a..4de9043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ Changelog for Razorpay-Ruby SDK. ## Unreleased +## [3.2.2] - 2024-04-16 + +feat: Added oauth APIs and support for access token based authentication mechanism +* Added oauth APIs (getAuthURL, getAccessToken, refreshToken, revokeToken) +* Added support for access token based authentication mechanism +* Added support for onboarding signature generation + +## [3.2.1] - 2023-12-19 + +Rollback: Generic access point due to some performance concern + ## [3.2.0] - 2023-12-11 feat: Added generic access point diff --git a/README.md b/README.md index 7083803..519a0a9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,11 @@ Ruby 2.6.8 or later Remember to `require 'razorpay'` before anything else. -Next, you need to setup your key and secret using the following: +Next, you need to setup your auth details. This setup can be done via two ways: + +### Using Private Auth + +you need to setup your key and secret using the following: ```rb Razorpay.setup('key_id', 'key_secret') @@ -38,6 +42,16 @@ You can set customer headers for your requests using the following: Razorpay.headers = {"CUSTOM_APP_HEADER" => "CUSTOM_VALUE"} ``` +### Using Access Token +you need to setup your access token using the following +```rb +Razorpay.setup_with_oauth('access_token') +``` +You can set customer headers for your requests using the following: +```rb +Razorpay.headers = {"CUSTOM_APP_HEADER" => "CUSTOM_VALUE"} +``` + You can find your API keys at . If you are using rails, the right place to do this might be `config/initializers/razorpay.rb`. @@ -70,7 +84,7 @@ If you are using rails, the right place to do this might be `config/initializers - [Register NACH and Charge First Payment Together](documents/registerNach.md) - [Payment Verification](documents/paymentVerification.md) - [Webhook](documents/webhook.md) -- [Generic](documents/generic.md) +- [OAuthToken](documents/oauth_token.md) ## Development diff --git a/documents/oauth_token.md b/documents/oauth_token.md new file mode 100644 index 0000000..30a9bd0 --- /dev/null +++ b/documents/oauth_token.md @@ -0,0 +1,142 @@ +### OAuthToken + +```rb +require "razorpay" +``` + +### Generate Authorize Url +```rb +body = { + submerchant_id: '', + timestamp: Time.now.to_i +} +onboarding_signature = Razorpay::Utility.generate_onboarding_signature(body, '') + +options = { + 'client_id' => '', + 'redirect_uri' => 'https://example.com/razorpay_callback', + 'scopes' => ["read_write"], + 'state' => 'NOBYtv8r6c75ex6WZ', + 'onboarding_signature' => onboarding_signature +} +authorize_url = Razorpay::OAuthToken.get_auth_url(options) +``` + +**Parameters:** + +| Name | Type | Description | +|----------------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| client_id* | string | Unique client identifier. | +| redirect_uri* | string | Callback URL used by Razorpay to redirect after the user approves or denies the authorisation request. The client should whitelist the 'redirect_uri'. | +| scopes* | array | Defines what access your application is requesting from the user. You can request one or multiple scopes by adding them to an array as indicated above. | +| state* | string | A random string generated by your service. This parameter helps prevent cross-site request forgery (CSRF) attacks. | +| onboarding_signature | string | A cryptographic string generated by your service using generateOnboardingSignature method in Utils class. Only applicable for accounts created with pre-fill KYC | + +**Response:** +``` +"https://auth.razorpay.com/authorize?response_type=code&client_id=&redirect_uri=https:%2F%2Fexample.com%2Frazorpay_callback&scope[]=read_only&scope[]=rx_read_write&state=NOBYtv8r6c75ex6WZ&onboarding_signature=" +``` + +------------------------------------------------------------------------------------------------------- +### Get Access token +```rb +options = { + 'client_id' => '', + 'client_secret' => '', + 'redirect_uri' => 'https://example.com/razorpay_callback', + 'grant_type' => 'authorization_code', + 'code' => '', + 'mode' => 'test' +} +oauth_token = Razorpay::OAuthToken.get_access_token(options) +``` + +**Parameters:** + +| Name | Type | Description | +|----------------|--------|------------------------------------------------------------------------------------------------------------------------------| +| client_id* | string | Unique client identifier. | +| client_secret* | string | Client secret string. | +| redirect_uri* | string | Specifies the same redirect_uri used in the authorisation request. | +| grant_type* | string | Defines the grant type for the request. Possible value are:
  • authorization_code
  • client_credentials
| +| code* | string | Decoded authorisation code received in the last step. Note: Pass this parameter only when grant_type is 'authorization_code' | +| mode | string | The type of mode. Possible values:
  • test
  • live (default)
| + +**Response:** +```json +{ + "public_token": "rzp_test_oauth_9xu1rkZqoXlClS", + "token_type": "Bearer", + "expires_in": 7862400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IkY1Z0NQYkhhRzRjcUpnIn0.eyJhdWQiOiJGNFNNeEgxanMxbkpPZiIsImp0aSI6IkY1Z0NQYkhhRzRjcUpnIiwiaWF0IjoxNTkyODMxMDExLCJuYmYiOjE1OTI4MzEwMTEsInN1YiI6IiIsImV4cCI6MTYwMDc3OTgxMSwidXNlcl9pZCI6IkYycVBpejJEdzRPRVFwIiwibWVyY2hhbnRfaWQiOiJGMnFQaVZ3N0lNV01GSyIsInNjb3BlcyI6WyJyZWFkX29ubHkiXX0.Wwqt5czhoWpVzP5_aoiymKXoGj-ydo-4A_X2jf_7rrSvk4pXdqzbA5BMrHxPdPbeFQWV6vsnsgbf99Q3g-W4kalHyH67LfAzc3qnJ-mkYDkFY93tkeG-MCco6GJW-Jm8xhaV9EPUak7z9J9jcdluu9rNXYMtd5qxD8auyRYhEgs", + "refresh_token": "def50200f42e07aded65a323f6c53181d802cc797b62cc5e78dd8038d6dff253e5877da9ad32f463a4da0ad895e3de298cbce40e162202170e763754122a6cb97910a1f58e2378ee3492dc295e1525009cccc45635308cce8575bdf373606c453ebb5eb2bec062ca197ac23810cf9d6cf31fbb9fcf5b7d4de9bf524c89a4aa90599b0151c9e4e2fa08acb6d2fe17f30a6cfecdfd671f090787e821f844e5d36f5eacb7dfb33d91e83b18216ad0ebeba2bef7721e10d436c3984daafd8654ed881c581d6be0bdc9ebfaee0dc5f9374d7184d60aae5aa85385690220690e21bc93209fb8a8cc25a6abf1108d8277f7c3d38217b47744d7", + "razorpay_account_id": "acc_Dhk2qDbmu6FwZH" +} +``` + +------------------------------------------------------------------------------------------------------- + +### Get Access token using refresh token +```rb +options = { + 'client_id' => '', + 'client_secret' => '', + 'refresh_token' => 'def5020096e1c470c901d34cd60fa53abdaf3662sa0' +} +oauth_token = Razorpay::OAuthToken.refresh_token(options) +``` + +**Parameters:** + +| Name | Type | Description | +|----------------|-----------|--------------------------------------------| +| client_id* | string | Unique client identifier. | +| client_secret* | string | Client secret string. | +| refresh_token* | string | The previously-stored refresh token value. | + +**Response:** +```json +{ + "public_token": "rzp_test_oauth_9xu1rkZqoXlClS", + "token_type": "Bearer", + "expires_in": 7862400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6Ijl4dTF", + "refresh_token": "def5020096e1c470c901d34cd60fa53abdaf36620e823ffa53" +} +``` + +------------------------------------------------------------------------------------------------------- + +### Revoke a token +```rb +options = { + 'client_id' => '', + 'client_secret' => '', + 'token' => 'def5020096e1c470c901d34cd60fa53abdaf36620e823ffa53' + 'token_type_hint' => 'access_token' +} +response = Razorpay::OAuthToken.revoke_token(options) +``` + +**Parameters:** + +| Name | Type | Description | +|------------------|----------|----------------------------------------------------------------------------------------------------------| +| client_id* | string | Unique client identifier. | +| client_secret* | string | Client secret string. | +| token_type_hint* | string | The type of token for the request. Possible values:
  • access_token
  • refresh_token
| +| token* | string | The token whose access should be revoked. | + +**Response:** +```json +{ + "message": "Token Revoked" +} +``` +------------------------------------------------------------------------------------------------------- + +**PN: * indicates mandatory fields** +
+
+**For reference click [here](https://razorpay.com/docs/partners/platform/onboard-businesses/integrate-oauth/integration-steps)** + diff --git a/lib/razorpay.rb b/lib/razorpay.rb index 6e81f82..61c8284 100644 --- a/lib/razorpay.rb +++ b/lib/razorpay.rb @@ -27,15 +27,22 @@ require 'razorpay/stakeholder' require 'razorpay/account' require 'razorpay/document' +require 'razorpay/oauth_token' # Base Razorpay module module Razorpay class << self - attr_accessor :auth, :custom_headers + attr_accessor :auth, :custom_headers, :access_token, :auth_type end def self.setup(key_id, key_secret) self.auth = { username: key_id, password: key_secret } + self.auth_type = Razorpay::PRIVATE_AUTH + end + + def self.setup_with_oauth(access_token) + self.access_token = access_token + self.auth_type = Razorpay::OAUTH end def self.headers=(headers = {}) diff --git a/lib/razorpay/constants.rb b/lib/razorpay/constants.rb index d75de8d..a3bec21 100644 --- a/lib/razorpay/constants.rb +++ b/lib/razorpay/constants.rb @@ -2,5 +2,10 @@ module Razorpay BASE_URI = 'https://api.razorpay.com'.freeze TEST_URL = 'https://api.razorpay.com/'.freeze - VERSION = '3.2.0'.freeze + VERSION = '3.2.2'.freeze + AUTH_URL = 'https://auth.razorpay.com'.freeze + API_HOST = 'API'.freeze + AUTH_HOST = 'AUTH'.freeze + PRIVATE_AUTH = 'Private'.freeze + OAUTH = 'OAuth'.freeze end diff --git a/lib/razorpay/oauth_token.rb b/lib/razorpay/oauth_token.rb new file mode 100644 index 0000000..f851bf3 --- /dev/null +++ b/lib/razorpay/oauth_token.rb @@ -0,0 +1,109 @@ +require 'razorpay/request' +require 'razorpay/entity' +require 'razorpay/payload_validator' +require 'razorpay/validation_config' + +module Razorpay + # OAuth APIs allow to you create and manage access tokens + class OAuthToken < Entity + def self.request + Razorpay::Request.new('token', Razorpay::AUTH_HOST) + end + + def self.get_auth_url(options) + validate_auth_url_request(options) + uri = URI.join(Razorpay::AUTH_URL, '/authorize') + + query_params = { + 'response_type' => 'code', + 'client_id' => options['client_id'], + 'redirect_uri' => options['redirect_uri'], + 'state' => options['state'] + } + + options['scopes'].each { |scope| query_params["scope[]"] = scope } + + if options.has_key?('onboarding_signature') + query_params['onboarding_signature'] = options['onboarding_signature'] + end + uri.query = URI.encode_www_form(query_params) + uri.to_s + end + + def self.get_access_token(options) + validate_access_token_request(options) + r = request + r.request :post, "/token", options + end + + def self.refresh_token(options) + options['grant_type'] = 'refresh_token' + validate_refresh_token_request(options) + r = request + r.request :post, "/token", options + end + + def self.revoke_token(options) + validate_revoke_token_request(options) + r = request + r.request :post, "/revoke", options + end + + class << self + + private + + def validate_auth_url_request(options) + Razorpay::PayloadValidator.validate(options, get_validations_for_auth_request_url) + end + + def validate_access_token_request(options) + Razorpay::PayloadValidator.validate(options, get_validations_for_access_token_request) + end + + def validate_refresh_token_request(options) + Razorpay::PayloadValidator.validate(options, get_validations_for_refresh_token_request) + end + + def validate_revoke_token_request(options) + Razorpay::PayloadValidator.validate(options, get_validations_for_revoke_token_request) + end + + def get_validations_for_auth_request_url + [ + Razorpay::ValidationConfig.new('client_id', [:id]), + Razorpay::ValidationConfig.new('redirect_uri', [:non_empty_string, :url]), + Razorpay::ValidationConfig.new('scopes', [:non_null]), + Razorpay::ValidationConfig.new('state', [:non_empty_string]) + ] + end + + def get_validations_for_access_token_request + [ + Razorpay::ValidationConfig.new('client_id', [:id]), + Razorpay::ValidationConfig.new('client_secret', [:non_empty_string]), + Razorpay::ValidationConfig.new('redirect_uri', [:non_empty_string, :url]), + Razorpay::ValidationConfig.new('grant_type', [:token_grant]) + ] + end + + def get_validations_for_refresh_token_request + [ + Razorpay::ValidationConfig.new('client_id', [:id]), + Razorpay::ValidationConfig.new('client_secret', [:non_empty_string]), + Razorpay::ValidationConfig.new('refresh_token', [:non_empty_string]), + Razorpay::ValidationConfig.new('grant_type', [:token_grant]) + ] + end + + def get_validations_for_revoke_token_request + [ + Razorpay::ValidationConfig.new('client_id', [:id]), + Razorpay::ValidationConfig.new('client_secret', [:non_empty_string]), + Razorpay::ValidationConfig.new('token', [:non_empty_string]), + Razorpay::ValidationConfig.new('token_type_hint', [:non_empty_string]) + ] + end + end + end +end \ No newline at end of file diff --git a/lib/razorpay/payload_validator.rb b/lib/razorpay/payload_validator.rb new file mode 100644 index 0000000..fa9de3c --- /dev/null +++ b/lib/razorpay/payload_validator.rb @@ -0,0 +1,93 @@ +module Razorpay + + ValidationType = { + non_null: :non_null, + non_empty_string: :non_empty_string, + url: :url, + id: :id, + mode: :mode, + token_grant: :token_grant + } + + # PayloadValidator allows to perform basic validations + class PayloadValidator + def self.validate(request, validation_configs) + validation_configs.each do |config| + field_name = config.field_name + config.validations.each do |validation_type| + apply_validation(request, field_name, validation_type) + end + end + end + + class << self + + private + + def apply_validation(payload, field, validation_type) + case validation_type + when ValidationType[:non_null] + validate_non_null(payload, field) + when ValidationType[:non_empty_string] + validate_non_empty_string(payload, field) + when ValidationType[:url] + validate_url(payload, field) + when ValidationType[:id] + validate_id(payload, field) + when ValidationType[:mode] + validate_mode(payload, field) + when ValidationType[:token_grant] + validate_grant_type(payload, field) + end + end + + def validate_non_null(payload, field) + raise Razorpay::Error.new, "Field #{field} cannot be null" unless payload.key?(field) && !payload[field].nil? + end + + def validate_non_empty_string(payload, field) + raise Razorpay::Error.new, "Field #{field} cannot be empty" if payload[field].to_s.strip.empty? + end + + def validate_url(payload, field) + url = payload[field] + url_regex = /^(http[s]?):\/\/[^\s\/$.?#].[^\s]*$/ + + unless url_regex.match?(url) + error_message = "Field #{field} is not a valid URL" + raise Razorpay::Error.new, error_message + end + end + + def validate_id(payload, field) + validate_non_null(payload, field) + validate_non_empty_string(payload, field) + value = payload[field] + id_regex = /^[A-Za-z0-9]{1,14}$/ + + unless value.match?(id_regex) + error_message = "Field #{field} is not a valid ID" + raise Razorpay::Error.new, error_message + end + end + + def validate_mode(payload, field) + validate_non_null(payload, field) + unless ["test", "live"].include?(payload[field]) + error_message = "Invalid value provided for field #{field}" + raise Razorpay::Error.new, error_message + end + end + + def validate_grant_type(payload, field) + validate_non_null(payload, field) + case payload[field] + when 'authorization_code' + validate_non_null(payload, 'code'); + when 'refresh_token' + validate_non_null(payload, 'refresh_token'); + end + end + end + end +end \ No newline at end of file diff --git a/lib/razorpay/request.rb b/lib/razorpay/request.rb index 29faf14..e33ed8d 100644 --- a/lib/razorpay/request.rb +++ b/lib/razorpay/request.rb @@ -11,20 +11,30 @@ class Request ssl_ca_file File.dirname(__FILE__) + '/../ca-bundle.crt' - def initialize(entity_name = nil) - self.class.base_uri(Razorpay::BASE_URI) + def initialize(entity_name = nil, host = Razorpay::API_HOST) + self.class.base_uri(get_base_url(host)) @entity_name = entity_name custom_headers = Razorpay.custom_headers || {} predefined_headers = { 'User-Agent' => "Razorpay-Ruby/#{Razorpay::VERSION}; Ruby/#{RUBY_VERSION}" } + # Order is important to give precedence to predefined headers headers = custom_headers.merge(predefined_headers) - @options = { - basic_auth: Razorpay.auth, - timeout: 30, - headers: headers - } + + if Razorpay.auth_type == Razorpay::OAUTH + @options = { + timeout: 30, + headers: headers + } + headers['Authorization'] = 'Bearer ' + Razorpay.access_token + else + @options = { + basic_auth: Razorpay.auth, + timeout: 30, + headers: headers + } + end end def fetch(id, version="v1") @@ -74,6 +84,13 @@ def raw_request(method, url, data = {}) self.class.send(method, url, @options) end + def get_base_url(host) + if host == Razorpay::AUTH_HOST + return Razorpay::AUTH_URL + end + Razorpay::BASE_URI + end + # Since we need to change the base route def make_test_request self.class.get Razorpay::TEST_URL, @options diff --git a/lib/razorpay/utility.rb b/lib/razorpay/utility.rb index 9071ede..aaab9c7 100644 --- a/lib/razorpay/utility.rb +++ b/lib/razorpay/utility.rb @@ -26,6 +26,11 @@ def self.verify_webhook_signature(body, signature, secret) verify_signature(body, signature, secret) end + def self.generate_onboarding_signature(body, secret) + json_data = body.to_json + encrypt(json_data, secret); + end + class << self private @@ -52,6 +57,25 @@ def secure_compare(a, b) r.zero? end + + def encrypt(data, secret) + iv = secret[0, 12] + key = secret[0, 16] + + cipher = OpenSSL::Cipher.new('aes-128-gcm') + cipher.encrypt + cipher.key = key + cipher.iv = iv + + cipher.auth_data = "" + + encrypted = cipher.update(data) + cipher.final + + tag = cipher.auth_tag + combined_encrypted_data = encrypted + tag + + encrypted_data_hex = combined_encrypted_data.unpack1("H*") + end end end end diff --git a/lib/razorpay/validation_config.rb b/lib/razorpay/validation_config.rb new file mode 100644 index 0000000..9d3edc7 --- /dev/null +++ b/lib/razorpay/validation_config.rb @@ -0,0 +1,11 @@ +module Razorpay + + class ValidationConfig + attr_reader :field_name, :validations + + def initialize(field_name, validations) + @field_name = field_name + @validations = validations + end + end +end \ No newline at end of file diff --git a/test/fixtures/fake_oauth_token.json b/test/fixtures/fake_oauth_token.json new file mode 100644 index 0000000..fb40404 --- /dev/null +++ b/test/fixtures/fake_oauth_token.json @@ -0,0 +1,8 @@ +{ + "public_token": "rzp_test_oauth_9xu1rkZqoXlClS", + "token_type": "Bearer", + "expires_in": 7862400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IkY1Z0NQYkhhRzRjcUpnIn0.eyJhdWQiOiJGNFNNeEgxanMxbkpPZiIsImp0aSI6IkY1Z0NQYkhhRzRjcUpnIiwiaWF0IjoxNTkyODMxMDExLCJuYmYiOjE1OTI4MzEwMTEsInN1YiI6IiIsImV4cCI6MTYwMDc3OTgxMSwidXNlcl9pZCI6IkYycVBpejJEdzRPRVFwIiwibWVyY2hhbnRfaWQiOiJGMnFQaVZ3N0lNV01GSyIsInNjb3BlcyI6WyJyZWFkX29ubHkiXX0.Wwqt5czhoWpVzP5_aoiymKXoGj-ydo-4A_X2jf_7rrSvk4pXdqzbA5BMrHxPdPbeFQWV6vsnsgbf99Q3g-W4kalHyH67LfAzc3qnJ-mkYDkFY93tkeG-MCco6GJW-Jm8xhaV9EPUak7z9J9jcdluu9rNXYMtd5qxD8auyRYhEgs", + "refresh_token": "def50200f42e07aded65a323f6c53181d802cc797b62cc5e78dd8038d6dff253e5877da9ad32f463a4da0ad895e3de298cbce40e162202170e763754122a6cb97910a1f58e2378ee3492dc295e1525009cccc45635308cce8575bdf373606c453ebb5eb2bec062ca197ac23810cf9d6cf31fbb9fcf5b7d4de9bf524c89a4aa90599b0151c9e4e2fa08acb6d2fe17f30a6cfecdfd671f090787e821f844e5d36f5eacb7dfb33d91e83b18216ad0ebeba2bef7721e10d436c3984daafd8654ed881c581d6be0bdc9ebfaee0dc5f9374d7184d60aae5aa85385690220690e21bc93209fb8a8cc25a6abf1108d8277f7c3d38217b47744d7", + "razorpay_account_id": "acc_Dhk2qDbmu6FwZH" +} \ No newline at end of file diff --git a/test/fixtures/fake_revoke_token.json b/test/fixtures/fake_revoke_token.json new file mode 100644 index 0000000..d5c6b1f --- /dev/null +++ b/test/fixtures/fake_revoke_token.json @@ -0,0 +1,3 @@ +{ + "message": "Token Revoked" +} \ No newline at end of file diff --git a/test/razorpay/test_oauth_token.rb b/test/razorpay/test_oauth_token.rb new file mode 100644 index 0000000..a8624ea --- /dev/null +++ b/test/razorpay/test_oauth_token.rb @@ -0,0 +1,105 @@ +require 'test_helper' + +module Razorpay + # Tests for Razorpay::OauthToken + class RazorpayOAuthTokenTest < Minitest::Test + class OAuthToken < Razorpay::Entity; end + + def test_get_auth_url + options = { + 'client_id' => '8DXCMTshWSWECc', + 'redirect_uri' => 'https://example.com/razorpay_callback', + 'state' => 'NOBYtv8r6c75ex6WZ', + 'scopes' => ["read_write"] + } + + expected_auth_url = "https://auth.razorpay.com/authorize?response_type=code&client_id=8DXCMTshWSWECc&redirect_uri=https%3A%2F%2Fexample.com%2Frazorpay_callback&state=NOBYtv8r6c75ex6WZ&scope%5B%5D=read_write" + auth_url = Razorpay::OAuthToken.get_auth_url(options) + assert_equal expected_auth_url, auth_url + end + + def test_request_validation_for_get_auth_url + options = { + 'client_id' => '8DXCMTshWSWECc', + 'redirect_uri' => 'https://example.com/razorpay_callback', + 'scopes' => ["read_write"] + } + assert_raises(Razorpay::Error) do + Razorpay::OAuthToken.get_auth_url(options) + end + end + + def test_get_access_token + options = { + 'client_id' => '8DXCMTshWSWECc', + 'client_secret' => 'AESSECRETKEY', + 'grant_type' => 'client_credentials', + 'redirect_uri' => 'http://example.com/razorpay_callback', + 'mode' => 'test' + } + stub_post(/token$/,'fake_oauth_token',options) + oauth_token = Razorpay::OAuthToken.get_access_token(options) + assert_instance_of Razorpay::Entity, oauth_token, 'OAuthToken not an instance of Entity class' + assert_equal 'rzp_test_oauth_9xu1rkZqoXlClS', oauth_token.public_token, 'Public Tokens do not match' + end + + def test_get_access_token_validation_failure + options = { + 'client_id' => '8DXCMTshWSWECc', + 'grant_type' => 'client_credentials', + 'redirect_uri' => 'http://example.com/razorpay_callback', + 'mode' => 'test' + } + assert_raises(Razorpay::Error) do + Razorpay::OAuthToken.get_access_token(options) + end + end + + def test_refresh_token + options = { + 'client_id' => '8DXCMTshWSWECc', + 'client_secret' => 'AESSECRETKEY', + 'refresh_token' => 'def5020096e1c470c901d34cd60fa53abdaf3662sa0' + } + expected_request_payload = options.merge('grant_type': 'refresh_token') + stub_post(/token$/,'fake_oauth_token',expected_request_payload) + oauth_token = Razorpay::OAuthToken.refresh_token(options) + assert_instance_of Razorpay::Entity, oauth_token, 'OAuthToken not an instance of Entity class' + assert_equal 'rzp_test_oauth_9xu1rkZqoXlClS', oauth_token.public_token, 'Public Tokens do not match' + end + + def test_refresh_token_validation_failure + options = { + 'client_id' => '8DXCMTshWSWECc', + 'refresh_token' => 'def5020096e1c470c901d34cd60fa53abdaf3662sa0' + } + assert_raises(Razorpay::Error) do + Razorpay::OAuthToken.refresh_token(options) + end + end + + def test_revoke_token + options = { + 'client_id' => '8DXCMTshWSWECc', + 'client_secret' => 'AESSECRETKEY', + 'token_type_hint' => 'access_token', + 'token' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJKQTFwODVudE1ySEpoQSIsImp0aSI6IkpPZkd0aHFDTmhqQUhTIiwiaWF0IjoxNjUxMTI0NTU0LCJuYmYiOjE2NTExMjQ1NTQsInN1YiI6IiIsImV4cCI6MTY1ODk4Njk1Miw' + } + stub_post(/revoke$/,'fake_revoke_token',options) + oauth_token = Razorpay::OAuthToken.revoke_token(options) + assert_instance_of Razorpay::Entity, oauth_token, 'OAuthToken not an instance of Entity class' + assert_equal 'Token Revoked', oauth_token.message, 'Messages do not match' + end + + def test_revoke_token_validation_failure + options = { + 'client_id' => '8DXCMTshWSWECc', + 'client_secret' => 'AESSECRETKEY', + 'token_type_hint' => 'access_token' + } + assert_raises(Razorpay::Error) do + Razorpay::OAuthToken.revoke_token(options) + end + end + end +end \ No newline at end of file diff --git a/test/razorpay/test_payload_validator.rb b/test/razorpay/test_payload_validator.rb new file mode 100644 index 0000000..4bef52d --- /dev/null +++ b/test/razorpay/test_payload_validator.rb @@ -0,0 +1,61 @@ +require 'test_helper' +require 'razorpay/validation_config' + +module Razorpay + # Tests for Razorpay::PayloadValidator + class RazorpayPayloadValidatorTest < Minitest::Test + def test_validate_mode + payload = { + 'mode1' => 'test', + 'mode2' => 'live' + } + assert_silent do + Razorpay::PayloadValidator.validate(payload, [ + Razorpay::ValidationConfig.new('mode1', [:mode]), + Razorpay::ValidationConfig.new('mode2', [:mode]), + ]) + end + end + + def test_mode_validation_failure + payload = { + 'mode' => 'testvalue' + } + assert_raises(Razorpay::Error) do + Razorpay::PayloadValidator.validate(payload, [ + Razorpay::ValidationConfig.new('mode', [:mode]) + ]) + end + end + + def test_url_validation_failure + payload = { + 'redirect_uri' => 'test.com' + } + assert_raises(Razorpay::Error) do + Razorpay::PayloadValidator.validate(payload, [ + Razorpay::ValidationConfig.new('redirect_uri', [:url]) + ]) + end + end + + def test_non_null_validation_failure + assert_raises(Razorpay::Error) do + Razorpay::PayloadValidator.validate({}, [ + Razorpay::ValidationConfig.new('redirect_uri', [:non_null]) + ]) + end + end + + def test_id_validation_failure + payload = { + 'client_id' => 'fjidhf' + } + assert_raises(Razorpay::Error) do + Razorpay::PayloadValidator.validate({}, [ + Razorpay::ValidationConfig.new('client_id', [:id]) + ]) + end + end + end +end \ No newline at end of file diff --git a/test/razorpay/test_razorpay.rb b/test/razorpay/test_razorpay.rb index 517c942..3c46dc6 100644 --- a/test/razorpay/test_razorpay.rb +++ b/test/razorpay/test_razorpay.rb @@ -49,5 +49,22 @@ def test_auth_header_and_user_agent headers: headers, times: 1 end + + def test_oauth_setup + Razorpay.setup_with_oauth('access_token') + assert_equal 'access_token', Razorpay.access_token + end + + # # We mock this request + def test_auth_header_and_user_agent_for_oauth + stub_get(/$/, 'hello_response') + Razorpay.setup_with_oauth('access_token') + Razorpay::Request.new('dummy').make_test_request + user_agent = "Razorpay-Ruby/#{Razorpay::VERSION}; Ruby/#{RUBY_VERSION}" + headers = { 'User-Agent' => user_agent, 'Authorization' => 'Bearer access_token' } + assert_requested :get, 'https://api.razorpay.com/', + headers: headers, + times: 1 + end end end diff --git a/test/razorpay/test_utility.rb b/test/razorpay/test_utility.rb index 50b24ee..0f806ff 100644 --- a/test/razorpay/test_utility.rb +++ b/test/razorpay/test_utility.rb @@ -1,4 +1,5 @@ require 'test_helper' +require 'json' module Razorpay # Tests for Razorpay::Utility @@ -80,5 +81,38 @@ def test_webhook_signature_verification Razorpay::Utility.verify_webhook_signature(webhook_body, signature, secret) end end + + def test_generate_onboarding_signature + secret = "EnLs21M47BllR3X8PSFtjtbd" + timestamp = Time.now.to_i + body = { + submerchant_id: 'NSgKfYIR2f9v2y', + timestamp: timestamp + } + encryptedData = Razorpay::Utility.generate_onboarding_signature(body, secret) + json_data = decrypt(encryptedData, secret) + body = JSON.parse(json_data) + assert_equal 'NSgKfYIR2f9v2y', body['submerchant_id'], 'Submerchant IDs do not match' + assert_equal timestamp, body['timestamp'], 'Timestamps do not match' + end + + def decrypt(data, secret) + combined_encrypted_data = [data].pack("H*") + + iv = secret[0, 12] + key = secret[0, 16] + tag = combined_encrypted_data[-16..] + + encrypted_data = combined_encrypted_data[0...-16] + + cipher = OpenSSL::Cipher.new('aes-128-gcm') + cipher.decrypt + cipher.key = key + cipher.iv = iv + cipher.auth_tag = tag + cipher.auth_data = "" + + cipher.update(encrypted_data) + cipher.final + end end end