lpt-ruby 0.4.1

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.
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module ApiOperations
5
+ module Create
6
+ def create(request, path: nil)
7
+ client = Lpt.client
8
+ resource = new
9
+ path ||= resource.resources_path
10
+ response = client.post(path, request.to_json)
11
+ handle_response resource, response
12
+ resource
13
+ end
14
+
15
+ protected
16
+
17
+ def post_request_failed?(response)
18
+ response.blank? || response.status > 201
19
+ end
20
+
21
+ def handle_response(resource, response)
22
+ return false if post_request_failed? response
23
+
24
+ resource.load_from_response(response.body)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module ApiOperations
5
+ module Retrieve
6
+ def retrieve(id, _opts = {})
7
+ client = Lpt.client
8
+ resource = new(id: id)
9
+ response = client.get(resource.resource_path)
10
+ new(response.body)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module ApiOperations
5
+ module Update
6
+ def update(id, request)
7
+ client = Lpt.client
8
+ resource = new(id: id)
9
+ response = client.put(resource.resource_path, request.to_json)
10
+ resource.load_from_response(response.body)
11
+ resource
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Authentication
5
+ attr_accessor :api_username, :api_password, :merchant, :merchant_account,
6
+ :entity
7
+
8
+ def client
9
+ assert_client_is_configured!
10
+ env = Lpt::Environment.factory(environment: environment)
11
+ Lpt::LptClient.factory(environment: env)
12
+ end
13
+
14
+ protected
15
+
16
+ def assert_client_is_configured!
17
+ assert_username!
18
+ assert_password!
19
+ assert_merchant!
20
+ assert_entity!
21
+ end
22
+
23
+ def assert_username!
24
+ msg = "Invalid API Username: #{api_username}"
25
+ raise ArgumentError, msg if api_username.blank?
26
+ end
27
+
28
+ def assert_password!
29
+ msg = "Invalid API Password"
30
+ raise ArgumentError, msg if api_password.blank?
31
+ end
32
+
33
+ def assert_merchant!
34
+ msg = "Invalid Merchant: #{merchant}"
35
+ raise ArgumentError, msg unless merchant.start_with? PREFIX_MERCHANT
36
+ end
37
+
38
+ def assert_entity!
39
+ msg = "Invalid Entity: #{entity}"
40
+ raise ArgumentError, msg unless entity.start_with? PREFIX_ENTITY
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ class Environment
5
+ PRODUCTION = 90
6
+ DEMO = 80
7
+ STAGING = 60
8
+ SANDBOX = 50
9
+ NEXT = 40
10
+ IVV = 30
11
+ TEST = 20
12
+ DEV = 10
13
+
14
+ ENVIRONMENTS = {
15
+ "production" => PRODUCTION,
16
+ "demo" => DEMO,
17
+ "staging" => STAGING,
18
+ "sandbox" => SANDBOX,
19
+ "next" => NEXT,
20
+ "ivv" => IVV,
21
+ "test" => TEST,
22
+ "dev" => DEV
23
+ }.freeze
24
+
25
+ BASE_DOMAINS = {
26
+ PRODUCTION => "lacorepayments.com",
27
+ DEMO => "dmo.lacorepayments.com",
28
+ STAGING => "lacorepayments.com",
29
+ SANDBOX => "sbx.lacorepayments.com",
30
+ NEXT => "nxt.lacorepayments.com",
31
+ IVV => "ivv.lacorepayments.com",
32
+ TEST => "test.lpt.local",
33
+ DEV => "lpt.local"
34
+ }.freeze
35
+
36
+ attr_accessor :api_base, :cx_base, :cx_api_base, :base_domain
37
+
38
+ def initialize(api_base: "", cx_base: "", cx_api_base: "", base_domain: "")
39
+ @api_base = api_base
40
+ @cx_base = cx_base
41
+ @cx_api_base = cx_api_base
42
+ @base_domain = base_domain
43
+ end
44
+
45
+ def ==(other)
46
+ api_base == other.api_base && base_domain == other.base_domain &&
47
+ cx_base == other.cx_base && cx_api_base == other.cx_api_base
48
+ end
49
+
50
+ def api_base_url(with_protocol: true)
51
+ base_url(api_base, with_protocol: with_protocol)
52
+ end
53
+
54
+ def cx_base_url
55
+ base_url(cx_base)
56
+ end
57
+
58
+ def cx_api_base_url
59
+ base_url(cx_api_base)
60
+ end
61
+
62
+ class << self
63
+ def staging
64
+ factory(environment: STAGING)
65
+ end
66
+
67
+ def production
68
+ factory(environment: PRODUCTION)
69
+ end
70
+
71
+ def factory(environment: DEV)
72
+ args = Lpt.base_addresses(environment: environment)
73
+ args[:base_domain] = BASE_DOMAINS[environment]
74
+ new(**args)
75
+ end
76
+ end
77
+
78
+ protected
79
+
80
+ def base_url(cname, with_protocol: true)
81
+ protocol = "https://" if with_protocol
82
+ "#{protocol}#{cname}.#{base_domain}"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ class LptClient < ::SimpleDelegator
5
+ def initialize(api_client:)
6
+ super(api_client)
7
+ end
8
+
9
+ class << self
10
+ def factory(environment:)
11
+ options = { request: request_options }
12
+ client = initialize_client(api_base_url: environment.api_base_url,
13
+ options: options)
14
+ new(api_client: client)
15
+ end
16
+
17
+ protected
18
+
19
+ def request_options
20
+ { open_timeout: Lpt.open_timeout, read_timeout: Lpt.read_timeout,
21
+ write_timeout: Lpt.write_timeout }
22
+ end
23
+
24
+ def logging_options
25
+ { headers: true, bodies: true, log_level: Lpt.log_level }
26
+ end
27
+
28
+ def configure_request(config)
29
+ config.request :authorization, :basic, Lpt.api_username, Lpt.api_password
30
+ config.request :json
31
+ end
32
+
33
+ def configure_response(config)
34
+ config.response :json
35
+ config.response :raise_error
36
+ end
37
+
38
+ def configure_logging(config)
39
+ return if Lpt.log_level.negative?
40
+
41
+ config.response :logger, nil, **logging_options do |fmt|
42
+ fmt.filter(/^(Authorization: ).*$/i, '\1[REDACTED]')
43
+ end
44
+ end
45
+
46
+ def assign_adapter(config)
47
+ config.adapter :net_http
48
+ end
49
+
50
+ def initialize_client(api_base_url:, options:)
51
+ Faraday.new(url: api_base_url, **options) do |config|
52
+ configure_request config
53
+ configure_response config
54
+ configure_logging config
55
+ assign_adapter config
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class ApiRequest
6
+ attr_accessor :entity
7
+
8
+ def initialize(attributes = {})
9
+ attributes.each_pair do |k, v|
10
+ setter = :"#{k}="
11
+ public_send(setter, v)
12
+ end
13
+ assign_default_entity
14
+ end
15
+
16
+ protected
17
+
18
+ def assign_default_entity
19
+ self.entity ||= Lpt.entity
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class EmptyRequest
6
+ def to_json(*)
7
+ nil
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class InstrumentRequest < ApiRequest
6
+ attr_accessor :profile, :instrument_id, :reference_id, :category, :type,
7
+ :name, :contact, :address, :account, :expiration,
8
+ :security_code, :authentication, :token, :external_token,
9
+ :network_token
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class InstrumentTokenRequest < ApiRequest
6
+ attr_accessor :profile, :instrument_id, :reference_id, :name, :contact,
7
+ :address, :expiration, :account, :security_code, :token
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class PaymentCaptureRequest < ApiRequest
6
+ attr_accessor :invoice, :order, :payment_id, :reference_id, :amount,
7
+ :session
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class PaymentRefundRequest < ApiRequest
6
+ attr_accessor :reference_id, :amount, :session
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class PaymentRequest < ApiRequest
6
+ INITIATION_CUSTOMER = "CUSTOMER"
7
+ INITIATION_MERCHANT = "MERCHANT"
8
+
9
+ WORKFLOW_SALE = "SALE"
10
+ WORKFLOW_AUTH_CAPTURE = "AUTH_CAPTURE"
11
+
12
+ attr_accessor :merchant, :merchant_account, :instrument, :invoice, :order,
13
+ :payment_id, :reference_id, :amount, :currency, :session,
14
+ :initiation, :url, :recurring, :workflow
15
+
16
+ def initialize(*)
17
+ super
18
+ assign_merchant
19
+ end
20
+
21
+ def as_auth_capture
22
+ self.workflow = WORKFLOW_AUTH_CAPTURE
23
+ self
24
+ end
25
+
26
+ def as_sale
27
+ self.workflow = WORKFLOW_SALE
28
+ self
29
+ end
30
+
31
+ def as_recurring_payment
32
+ self.initiation = INITIATION_MERCHANT
33
+ self
34
+ end
35
+
36
+ protected
37
+
38
+ def assign_merchant
39
+ self.merchant ||= Lpt.merchant
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class PaymentReversalRequest < ApiRequest
6
+ attr_accessor :reference_id, :amount, :session
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class ProfileRequest < ApiRequest
6
+ attr_accessor :metadata, :profile_id, :reference_id, :name, :contact,
7
+ :address
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Requests
5
+ class VerificationRequest < ApiRequest
6
+ attr_accessor :metadata, :verification_id, :reference_id, :category,
7
+ :type, :instrument, :profile, :subject, :merchant,
8
+ :merchant_account, :url
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Resources
5
+ class ApiResource
6
+ attr_accessor :id, :entity, :metadata, :created, :updated, :status,
7
+ :created_at, :updated_at
8
+
9
+ def initialize(attributes = {})
10
+ hydrate_attributes attributes
11
+ assign_object_name
12
+ end
13
+
14
+ def object_name
15
+ assert_object_name_implemented!
16
+ @object_name
17
+ end
18
+
19
+ def base_path
20
+ "/#{Lpt.api_version}"
21
+ end
22
+
23
+ def resources_path
24
+ assert_concrete_class_used!
25
+ # Namespaces are separated in object names with periods (.) and in URLs
26
+ # with forward slashes (/), so replace the former with the latter.
27
+ "#{base_path}/#{object_name.downcase.tr('.', '/')}s"
28
+ end
29
+
30
+ def resource_path
31
+ assert_concrete_class_used!
32
+ assert_valid_id_exists!
33
+ "#{resources_path}/#{CGI.escape(id)}"
34
+ end
35
+
36
+ def load_from_response(response)
37
+ return if response.blank?
38
+
39
+ hydrate_attributes response
40
+ end
41
+
42
+ protected
43
+
44
+ def assign_object_name; end
45
+
46
+ def assert_concrete_class_used!
47
+ return unless instance_of? ApiResource
48
+
49
+ msg = <<~MSG
50
+ APIResource is an abstract class. You should perform actions on its
51
+ subclasses (Payment, Instrument, etc.)
52
+ MSG
53
+ raise NotImplementedError, msg
54
+ end
55
+
56
+ def assert_object_name_implemented!
57
+ return unless @object_name.blank?
58
+
59
+ msg = "Resources must implement #object_name"
60
+ raise NotImplementedError, msg
61
+ end
62
+
63
+ def assert_valid_id_exists!
64
+ return unless id.blank?
65
+
66
+ msg = <<~MSG
67
+ Could not determine which URL to request: #{self.class} instance has
68
+ an invalid ID: #{id.inspect}
69
+ MSG
70
+ raise ArgumentError, msg
71
+ end
72
+
73
+ def hydrate_attributes(attributes)
74
+ return unless attributes.respond_to? :each_pair
75
+
76
+ attributes.each_pair do |k, v|
77
+ setter = :"#{k}="
78
+ public_send(setter, v)
79
+ rescue NoMethodError
80
+ # noop
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Resources
5
+ class Instrument < ApiResource
6
+ extend Lpt::ApiOperations::Retrieve
7
+ extend Lpt::ApiOperations::Create
8
+ extend Lpt::ApiOperations::Update
9
+
10
+ attr_accessor :profile, :metadata, :instrument_id, :reference_id,
11
+ :category, :type, :account_type, :name, :contact,
12
+ :address, :identifier, :brand, :network, :issuer_identifier,
13
+ :issuer, :expiration, :fingerprint, :token
14
+
15
+ def id_prefix
16
+ Lpt::PREFIX_INSTRUMENT
17
+ end
18
+
19
+ def expiration_month
20
+ return false unless expiration.respond_to? :with_indifferent_access
21
+
22
+ expiration.with_indifferent_access["month"]
23
+ end
24
+
25
+ def expiration_year
26
+ return false unless expiration.respond_to? :with_indifferent_access
27
+
28
+ expiration.with_indifferent_access["year"]
29
+ end
30
+
31
+ def auth(payment_request)
32
+ assert_valid_id_exists!
33
+ payment_request.instrument = id
34
+ Lpt::Resources::Payment.auth(payment_request)
35
+ rescue ArgumentError
36
+ false
37
+ end
38
+
39
+ def charge(payment_request)
40
+ assert_valid_id_exists!
41
+ payment_request.instrument = id
42
+ Lpt::Resources::Payment.sale(payment_request)
43
+ rescue ArgumentError
44
+ false
45
+ end
46
+
47
+ def self.tokenize(instrument_token_request)
48
+ path = token_path
49
+ Lpt::Resources::Instrument.create(instrument_token_request, path: path)
50
+ end
51
+
52
+ def self.token_path(entity: nil)
53
+ resource = new
54
+ [resource.resources_path, "token", entity].compact.join("/")
55
+ end
56
+
57
+ protected
58
+
59
+ def assign_object_name
60
+ @object_name = "instrument"
61
+ end
62
+
63
+ def assert_valid_id_exists!
64
+ return if id.present?
65
+
66
+ raise ArgumentError, "An instrument ID is required"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Resources
5
+ class Payment < ApiResource
6
+ extend Lpt::ApiOperations::Retrieve
7
+ extend Lpt::ApiOperations::Create
8
+ extend Lpt::ApiOperations::Update
9
+
10
+ attr_accessor :payment_id, :reference_id, :instrument,
11
+ :instrument_identifier, :initiation, :merchant,
12
+ :merchant_account, :workflow, :amount, :currency, :order,
13
+ :invoice, :session, :profile, :authorization, :presentment,
14
+ :reversal, :refunds, :amount_refundable, :result, :url
15
+
16
+ def approved?
17
+ return false unless result.respond_to? :with_indifferent_access
18
+
19
+ result.with_indifferent_access["approved"] == true
20
+ end
21
+
22
+ def id_prefix
23
+ Lpt::PREFIX_PAYMENT
24
+ end
25
+
26
+ def capture(request = Lpt::Requests::EmptyRequest.new)
27
+ path = "#{resource_path}/capture"
28
+ Lpt::Resources::Payment.create(request, path: path)
29
+ end
30
+
31
+ def refund(request = Lpt::Requests::EmptyRequest.new)
32
+ path = "#{resource_path}/refund"
33
+ Lpt::Resources::Payment.create(request, path: path)
34
+ end
35
+
36
+ def reverse(request = Lpt::Requests::EmptyRequest.new)
37
+ path = "#{resource_path}/reverse"
38
+ Lpt::Resources::Payment.create(request, path: path)
39
+ end
40
+
41
+ def void(request = Lpt::Requests::EmptyRequest.new)
42
+ path = "#{resource_path}/void"
43
+ Lpt::Resources::Payment.create(request, path: path)
44
+ end
45
+
46
+ def self.auth(payment_request)
47
+ payment_request.as_auth_capture
48
+ Lpt::Resources::Payment.create(payment_request)
49
+ end
50
+
51
+ def self.sale(payment_request)
52
+ payment_request.as_sale
53
+ Lpt::Resources::Payment.create(payment_request)
54
+ end
55
+
56
+ protected
57
+
58
+ def assign_object_name
59
+ @object_name = "payment"
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lpt
4
+ module Resources
5
+ class Profile < ApiResource
6
+ extend Lpt::ApiOperations::Retrieve
7
+ extend Lpt::ApiOperations::Create
8
+ extend Lpt::ApiOperations::Update
9
+
10
+ attr_accessor :profile_id, :reference_id, :name, :contact, :address
11
+
12
+ def id_prefix
13
+ Lpt::PREFIX_PROFILE
14
+ end
15
+
16
+ def associate_instrument(instrument_token_request)
17
+ assert_valid_id_exists!
18
+ assert_token_exists! instrument_token_request
19
+ instrument_token_request.profile = id
20
+ Lpt::Resources::Instrument.create(instrument_token_request)
21
+ rescue ArgumentError
22
+ false
23
+ end
24
+
25
+ protected
26
+
27
+ def assign_object_name
28
+ @object_name = "profile"
29
+ end
30
+
31
+ def assert_valid_id_exists!
32
+ return if id.present?
33
+
34
+ raise ArgumentError, "A profile ID is required"
35
+ end
36
+
37
+ def assert_token_exists!(request)
38
+ return if request.token.present?
39
+
40
+ raise ArgumentError, "An instrument token is required"
41
+ end
42
+ end
43
+ end
44
+ end