-
Notifications
You must be signed in to change notification settings - Fork 0
Paypal Authentication migration to gem #165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
92e29d3
Integrate auth process with paypal express
praweb 03b7306
Add Paypal Auth Process specs
praweb 0666fa2
add address verification
praweb ec020f4
add specs for address verification
praweb acda40b
Update address_verification.rb
praweb 7a6b00e
Add Retry module, and add retries to the paypal api calls
praweb c265037
Delete address verification module
praweb 98d9179
fix auth specs
praweb 09b0d33
Add new relic agent monitoring
praweb 7f3b51e
modify retry count constant
praweb 7b56b0c
Fix specs
praweb 9144b24
remove recordings
praweb 9ad750a
add recordings
praweb 5497926
remove unwanted comment
praweb 306053d
Merge branch 'feature/revert_json_api_changes' into feature/migrate_p…
f0f3bff
Merge branch 'master' into feature/migrate_paypal_polling_to_gem
9ef3de2
remove recordings
ce30f0b
integrate with no of retires configuration
69e500c
correct spelling
a383a8f
add variable to config file
3639e7c
Update version.rb
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| module FlexCommerceApi | ||
| VERSION = '0.6.55' | ||
| VERSION = '0.6.56' | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| # frozen_string_literal: true | ||
| require_relative 'api' | ||
| require 'retry' | ||
|
|
||
| # @module FlexCommerce::PaypalExpress | ||
| module FlexCommerce | ||
| module PaypalExpress | ||
| # @class Setup | ||
| # | ||
| # This service authorises the payment via the Paypal gateway | ||
| class Auth | ||
| include ::Retry | ||
| include ::FlexCommerce::PaypalExpress::Api | ||
|
|
||
| DEFAULT_CURRENCY = "GBP" | ||
|
|
||
| # @initialize | ||
| # | ||
| # @param {String} token - Paypal token | ||
| # @param {String} payer_id - Paypal user id | ||
| def initialize(cart:, token:, payer_id:, payment_transaction:, gateway_class: ::ActiveMerchant::Billing::PaypalExpressGateway) | ||
| self.cart = cart | ||
| self.token = token | ||
| self.payer_id = payer_id | ||
| self.payment_transaction = payment_transaction | ||
| self.gateway_class = gateway_class | ||
| end | ||
|
|
||
| def call | ||
| process_with_gateway | ||
| end | ||
|
|
||
| private | ||
|
|
||
| attr_accessor :cart, :token, :payer_id, :payment_transaction, :gateway_class | ||
|
|
||
| def process_with_gateway | ||
| # Fetch Order details from Paypal | ||
| response = do_express_checkout_payment | ||
| unless response.success? | ||
| unless is_user_error?(response) | ||
| raise ::FlexCommerce::PaypalExpress::Exception::NotAuthorized.new("Payment not authorised - #{response.message}", response: response) | ||
| end | ||
| return mark_transaction_with_errors!(response) | ||
| end | ||
|
|
||
| # Authorizing transaction | ||
| auth_response = do_authorization(response) | ||
| unless auth_response.success? | ||
| unless is_user_error?(auth_response) | ||
| raise ::FlexCommerce::PaypalExpress::Exception::NotAuthorized.new("Failed authorising transaction - #{auth_response.message}", response: auth_response) | ||
| end | ||
| return mark_transaction_with_errors!(auth_response) | ||
| end | ||
|
|
||
| payment_transaction.attributes = { gateway_response: { payer_id: payer_id, token: token, transaction_id: response.params["transaction_id"], authorization_id: auth_response.params["transaction_id"]} } | ||
| payment_transaction.save | ||
| payment_transaction | ||
| rescue ::ActiveMerchant::ConnectionError => ex | ||
| raise ::FlexCommerce::PaypalExpress::Exception::ConnectionError.new("Failed authorising transaction due to a connection error. Original message was #{ex.message}") | ||
| end | ||
|
|
||
| def do_express_checkout_payment | ||
| Retry.call(no_of_retries: no_of_retires, rescue_errors: ::ActiveMerchant::ConnectionError) { | ||
| ::NewRelic::Agent.increment_metric('Custom/Paypal/Do_Express_Checkout_Payment') if defined?(NewRelic::Agent) | ||
| gateway.order(convert_amount(cart.total), token: token, payer_id: payer_id, currency: DEFAULT_CURRENCY) | ||
| } | ||
| end | ||
|
|
||
|
|
||
| def do_authorization(response) | ||
| Retry.call(no_of_retries: no_of_retires, rescue_errors: ::ActiveMerchant::ConnectionError) { | ||
| ::NewRelic::Agent.increment_metric('Custom/Paypal/Do_Auhtorization') if defined?(NewRelic::Agent) | ||
| gateway.authorize_transaction(response.params["transaction_id"], convert_amount(cart.total), transaction_entity: "Order", currency: DEFAULT_CURRENCY, payer_id: payer_id) | ||
| } | ||
| end | ||
|
|
||
| def no_of_retires | ||
| FlexCommerceApi.config.paypal_connection_errors_no_of_retries | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| require_relative 'transaction' | ||
|
|
||
| module FlexCommerce | ||
| module PaypalExpress | ||
| module Exception | ||
| class ConnectionError < Transaction | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| require_relative 'transaction' | ||
|
|
||
| module FlexCommerce | ||
| module PaypalExpress | ||
| module Exception | ||
| class NotAuthorized < Transaction | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # frozen_string_literal: true | ||
| module Retry | ||
|
|
||
| DEFAULT_MAX_NO_OF_RETRIES = 2 | ||
| DEFAULT_RESCUE_ERRORS = StandardError | ||
|
|
||
| def self.call(no_of_retries: DEFAULT_MAX_NO_OF_RETRIES, rescue_errors: DEFAULT_RESCUE_ERRORS, &blk) | ||
| total_attempts = 0 | ||
| begin | ||
| blk.call | ||
| rescue rescue_errors => ex | ||
| total_attempts += 1 | ||
| retry if total_attempts < no_of_retries | ||
| ensure | ||
| if total_attempts == no_of_retries | ||
| return | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| require "e2e_spec_helper" | ||
|
|
||
| RSpec.describe FlexCommerce::PaypalExpress::Auth, vcr: true, paypal: true do | ||
| include ActiveSupport::NumberHelper | ||
| include_context "context store" | ||
| include_context "housekeeping" | ||
|
|
||
| let(:token) { "fake-token" } | ||
| let(:payer_id) { "fake-payer-id" } | ||
| let(:cart) { build_stubbed(:cart, total: 100) } | ||
| let(:transaction) { | ||
| to_clean.transaction = FlexCommerce::PaymentTransaction.create( | ||
| cart_id: cart.id, | ||
| gateway_response: { | ||
| token: token, | ||
| payer_id: payer_id | ||
| }, | ||
| amount: cart.total, | ||
| status: "success", | ||
| transaction_type: 'authorisation', | ||
| currency: "GBP", | ||
| payment_gateway_reference: "paypal_reference" | ||
| ) | ||
| } | ||
| before(:each) do | ||
| # Ensure the service doesnt touch the cart or its addresses | ||
| cart.shipping_address.freeze | ||
| cart.billing_address.freeze | ||
| cart.freeze | ||
| end | ||
|
|
||
| subject { described_class.new(cart: cart, token: token, payer_id: payer_id, payment_transaction: transaction) } | ||
|
|
||
| shared_context "mocked active merchant" do |expect_login: true, test_mode: true| | ||
| # Mock active merchant | ||
| let(:active_merchant_gateway_class) { class_double("ActiveMerchant::Billing::PaypalExpressGateway", new: active_merchant_gateway).as_stubbed_const } | ||
| let(:active_merchant_gateway) { instance_spy("ActiveMerchant::Billing::PaypalExpressGateway") } | ||
| let!(:active_connection_error) { class_double("ActiveMerchant::ConnectionError").as_stubbed_const } | ||
|
|
||
| before(:each) do | ||
| expect(active_merchant_gateway_class).to receive(:new).with(test: true, login: ENV['PAYPAL_LOGIN'], password: ENV['PAYPAL_PASSWORD'], signature: ENV['PAYPAL_SIGNATURE']).and_return active_merchant_gateway if expect_login | ||
| end | ||
| let(:order_response) do | ||
| instance_double "ActiveMerchant::Billing::PaypalExpressResponse", "order_response", params: order_response_params, success?: true | ||
| end | ||
| let(:authorize_order_response) do | ||
| instance_double "ActiveMerchant::Billing::PaypalExpressResponse", "authorize_order_response", params: authorize_order_response_params, success?: true | ||
| end | ||
| let(:order_response_params) do | ||
| { | ||
| "token" => token, | ||
| "Token" => token, | ||
| "transaction_id" => transaction_id, | ||
| "transaction_type" => "express-checkout", | ||
| } | ||
| end | ||
| let(:authorize_order_response_params) do | ||
| { | ||
| "token" => token, | ||
| "Token" => token, | ||
| "transaction_id" => auth_transaction_id, | ||
| "transaction_type" => "express-checkout", | ||
| } | ||
| end | ||
|
|
||
| let(:transaction_id) { "fake-transaction-id" } | ||
| let(:auth_transaction_id) { "fake-auth-transaction-id" } | ||
| end | ||
|
|
||
| context "#call" do | ||
| context "happy path" do | ||
| include_context "mocked active merchant" | ||
|
|
||
| before(:each) do | ||
| expect(active_merchant_gateway).to receive(:order).with(convert_amount(cart.total), token: token, payer_id: payer_id, currency: "GBP").and_return order_response | ||
| expect(active_merchant_gateway).to receive(:authorize_transaction).with(transaction_id, convert_amount(cart.total), transaction_entity: "Order", payer_id: payer_id, currency: "GBP").and_return authorize_order_response | ||
| end | ||
|
|
||
| it "should set the gateway response" do | ||
| response = subject.call | ||
| expect(response.gateway_response).to include(transaction_id: transaction_id, authorization_id: auth_transaction_id) | ||
| end | ||
|
|
||
| end | ||
|
|
||
| context "happy path in production" do | ||
| include_context "mocked active merchant", test_mode: false | ||
|
|
||
| before(:each) do | ||
| expect(active_merchant_gateway).to receive(:order).with(convert_amount(cart.total), token: token, payer_id: payer_id, currency: "GBP").and_return order_response | ||
| expect(active_merchant_gateway).to receive(:authorize_transaction).with(transaction_id, convert_amount(cart.total), transaction_entity: "Order", payer_id: payer_id, currency: "GBP").and_return authorize_order_response | ||
| end | ||
|
|
||
| it "should set the gateway response" do | ||
| response = subject.call | ||
| expect(response.gateway_response).to include(transaction_id: transaction_id, authorization_id: auth_transaction_id) | ||
| end | ||
| end | ||
|
|
||
| context "with error scenarios" do | ||
| include_context "mocked active merchant" | ||
|
|
||
| it "should mark the transactions gateway_response as invalid when failure is recoverable in order stage" do | ||
| order_response = instance_double "ActiveMerchant::Billing::PaypalExpressGateway", "order_response", params: {"error_codes" => "10410", "message" => "Invalid token", "ack" => "Failure", "Ack" => "Failure"}, success?: false | ||
| expect(active_merchant_gateway).to receive(:order).with(convert_amount(cart.total), token: token, payer_id: payer_id, currency: "GBP").and_return order_response | ||
| expect(active_merchant_gateway).not_to receive(:authorize_transaction) | ||
| response = subject.call | ||
| expect(response[0][:gateway_response]).to include "Invalid token" | ||
| end | ||
|
|
||
| it "should mark the transactions gateway_response as invalid when failure is recoverable in auth stage" do | ||
| authorize_order_response = instance_double "ActiveMerchant::Billing::PaypalExpressGateway", "order_response", params: {"error_codes" => "10410", "message" => "Invalid token", "ack" => "Failure", "Ack" => "Failure"}, success?: false | ||
| expect(active_merchant_gateway).to receive(:order).with(convert_amount(cart.total), token: token, payer_id: payer_id, currency: "GBP").and_return order_response | ||
| expect(active_merchant_gateway).to receive(:authorize_transaction).with(transaction_id, convert_amount(cart.total), transaction_entity: "Order", payer_id: payer_id, currency: "GBP").and_return authorize_order_response | ||
| response = subject.call | ||
| expect(response[0][:gateway_response]).to include "Invalid token" | ||
| end | ||
|
|
||
| it "should raise an error when failure is not recoverable in order stage" do | ||
| order_response = instance_double "ActiveMerchant::Billing::PaypalExpressGateway", "order_response", params: {"error_codes" => "10002", "message" => "Receiving Limit exceeded", "ack" => "Failure", "Ack" => "Failure"}, message: "Receiving limit exceeded", success?: false | ||
| expect(active_merchant_gateway).to receive(:order).with(convert_amount(cart.total), token: token, payer_id: payer_id, currency: "GBP").and_return order_response | ||
| expect(active_merchant_gateway).not_to receive(:authorize_transaction) | ||
| expect { subject.call }.to raise_error ::FlexCommerce::PaypalExpress::Exception::NotAuthorized | ||
| end | ||
|
|
||
| it "should raise an error when failure is not recoverable in auth stage" do | ||
| authorize_order_response = instance_double "ActiveMerchant::Billing::PaypalExpressGateway", "authorize_order_response", params: {"error_codes" => "10002", "message" => "Receiving Limit exceeded", "ack" => "Failure", "Ack" => "Failure"}, message: "Receiving limit exceeded", success?: false | ||
| expect(active_merchant_gateway).to receive(:order).with(convert_amount(cart.total), token: token, payer_id: payer_id, currency: "GBP").and_return order_response | ||
| expect(active_merchant_gateway).to receive(:authorize_transaction).with(transaction_id, convert_amount(cart.total), transaction_entity: "Order", payer_id: payer_id, currency: "GBP").and_return authorize_order_response | ||
| expect { subject.call }.to raise_error ::FlexCommerce::PaypalExpress::Exception::NotAuthorized | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def convert_amount(amount) | ||
| (amount * 100.0).to_i | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this conditional. SurelytheUSER_ERRORS.keyswill always return an array of keys so there's no point in checking if it's present?.&will combine the arrays.