From 8cb41d076e07ba1d0f4c4cfb29830d9ceec661ad Mon Sep 17 00:00:00 2001 From: hf2186 Date: Fri, 8 Nov 2019 14:03:20 -0500 Subject: [PATCH 01/39] Basic API Functions to communicate with Amazon V2 API --- Gemfile | 1 + lib/amazon/charge.rb | 23 +++ lib/amazon/charge_permission.rb | 18 +++ lib/amazon/checkout_session.rb | 18 +++ lib/amazon/refund.rb | 13 ++ lib/amazon_pay.rb | 272 ++++++++++++++++++++++++++++++++ spree_amazon_payments.gemspec | 3 +- 7 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 lib/amazon/charge.rb create mode 100644 lib/amazon/charge_permission.rb create mode 100644 lib/amazon/checkout_session.rb create mode 100644 lib/amazon/refund.rb create mode 100644 lib/amazon_pay.rb diff --git a/Gemfile b/Gemfile index e97da04..b0adfdd 100644 --- a/Gemfile +++ b/Gemfile @@ -23,5 +23,6 @@ end gem 'pg' gem 'mysql2' +gem 'openssl', '~> 2.1.0' gemspec diff --git a/lib/amazon/charge.rb b/lib/amazon/charge.rb new file mode 100644 index 0000000..304cd8d --- /dev/null +++ b/lib/amazon/charge.rb @@ -0,0 +1,23 @@ +module AmazonPay + class Charge + def self.create(params) + response = AmazonPay.request('post', 'charges', params) + response.body + end + + def self.get(charge_id) + response = AmazonPay.request('get', "charges/#{charge_id}") + response.body + end + + def self.capture(charge_id, params) + response = AmazonPay.request('post', "charges/#{charge_id}", params) + response.body + end + + def self.cancel(charge_id, params) + response = AmazonPay.request('delete', "charges/#{charge_id}/cancel", params) + response.body + end + end +end diff --git a/lib/amazon/charge_permission.rb b/lib/amazon/charge_permission.rb new file mode 100644 index 0000000..0ce29af --- /dev/null +++ b/lib/amazon/charge_permission.rb @@ -0,0 +1,18 @@ +module AmazonPay + class ChargePermission + def self.get(charge_permission_id) + response = AmazonPay.request('get', "chargePermissions/#{charge_permission_id}") + response.body + end + + def self.update(charge_permission_id, params) + response = AmazonPay.request('patch', "chargePermissions/#{charge_permission_id}", params) + response.body + end + + def self.close(charge_permission_id, params) + response = AmazonPay.request('delete', "chargePermissions/#{charge_permission_id}/close", params) + response.body + end + end +end diff --git a/lib/amazon/checkout_session.rb b/lib/amazon/checkout_session.rb new file mode 100644 index 0000000..a05f482 --- /dev/null +++ b/lib/amazon/checkout_session.rb @@ -0,0 +1,18 @@ +module AmazonPay + class CheckoutSession + def self.create(params) + response = AmazonPay.request('post', 'checkoutSessions', params) + response.body + end + + def self.get(checkout_session_id) + response = AmazonPay.request('get', "checkoutSessions/#{checkout_session_id}") + response.body + end + + def self.update(checkout_session_id, params) + response = AmazonPay.request('patch', "checkoutSessions/#{checkout_session_id}", params) + response.body + end + end +end diff --git a/lib/amazon/refund.rb b/lib/amazon/refund.rb new file mode 100644 index 0000000..1111949 --- /dev/null +++ b/lib/amazon/refund.rb @@ -0,0 +1,13 @@ +module AmazonPay + class Refund + def self.create(params) + response = AmazonPay.request('post', 'refunds', params) + response.body + end + + def self.get(refund_id) + response = AmazonPay.request('get', "refunds/#{refund_id}") + response.body + end + end +end diff --git a/lib/amazon_pay.rb b/lib/amazon_pay.rb new file mode 100644 index 0000000..7839575 --- /dev/null +++ b/lib/amazon_pay.rb @@ -0,0 +1,272 @@ +require 'net/http' +require 'securerandom' +require 'openssl' +require 'time' +require 'active_support/core_ext' + +require 'amazon/charge_permission' +require 'amazon/charge' +require 'amazon/checkout_session' +require 'amazon/refund' + +module AmazonPay + @@amazon_signature_algorithm = 'AMZN-PAY-RSASSA-PSS'.freeze + @@hash_algorithm = 'SHA256'.freeze + @@public_key_id = nil + @@region = nil + @@sandbox = 'true' + @@store_id = nil + @@private_key = 'private.pem' + + def self.region=(region) + @@region = region + end + + def self.public_key_id=(public_key_id) + @@public_key_id = public_key_id + end + + def self.sandbox=(sandbox) + @@sandbox = sandbox + end + + def self.private_key=(private_key) + @@private_key = private_key + end + + def self.store_id=(store_id) + @@store_id = store_id + end + + def self.request(method_type, url, body = nil) + method_types = { + 'post' => Net::HTTP::Post, + 'get' => Net::HTTP::Get, + 'put' => Net::HTTP::Put, + 'patch' => Net::HTTP::Patch + } + + url = base_api_url + url + + uri = URI.parse(url) + request = method_types[method_type.downcase].new(uri) + request['content-type'] = 'application/json' + if method_type.downcase == 'post' + request['x-amz-pay-idempotency-key'] = SecureRandom.hex(10) + end + request['x-amz-pay-date'] = formatted_timestamp + + request_payload = JSON.dump(body) if body.present? + request.body = request_payload + + headers = request + request_parameters = {} + signed_headers = signed_headers(method_type.downcase, url, request_parameters, request_payload, headers) + signed_headers.each { |key, value| request[key] = value } + + response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| + http.request(request) + end + + response + end + + def self.signed_headers(http_request_method, request_uri, request_parameters, request_payload, other_presigned_headers = nil) + request_payload ||= '' + request_payload = check_for_payment_critical_data_api(request_uri, http_request_method, request_payload) + pre_signed_headers = {} + pre_signed_headers['accept'] = 'application/json' + pre_signed_headers['content-type'] = 'application/json' + pre_signed_headers['x-amz-pay-region'] = @@region + + if other_presigned_headers.present? + other_presigned_headers.each do |key, val| + next unless key.downcase == 'x-amz-pay-idempotency-key' && val.present? + pre_signed_headers['x-amz-pay-idempotency-key'] = val + end + end + + time_stamp = formatted_timestamp + signature = create_signature(http_request_method, request_uri, + request_parameters, pre_signed_headers, + request_payload, time_stamp) + headers = canonical_headers(pre_signed_headers) + headers['x-amz-pay-date'] = time_stamp + headers['x-amz-pay-host'] = amz_pay_host(request_uri) + signed_headers = "SignedHeaders=#{canonical_headers_names(headers)}, Signature=#{signature}" + # Do not add x-amz-pay-idempotency-key header here, as user-supplied headers get added later + header_array = {} + header_array['accept'] = string_from_array(headers['accept']) + header_array['content-type'] = string_from_array(headers['content-type']) + header_array['x-amz-pay-host'] = amz_pay_host(request_uri) + header_array['x-amz-pay-date'] = time_stamp + header_array['x-amz-pay-region'] = @@region + header_array['authorization'] = "#{@@amazon_signature_algorithm} PublicKeyId=#{@@public_key_id}, #{signed_headers}" + puts("\nAUTHORIZATION HEADER:\n" + header_array['authorization']) + + header_array.sort_by { |key, _value| key }.to_h + end + + def self.create_signature(http_request_method, request_uri, request_parameters, pre_signed_headers, request_payload, time_stamp) + rsa = OpenSSL::PKey::RSA.new(File.read(@@private_key)) + pre_signed_headers['x-amz-pay-date'] = time_stamp + pre_signed_headers['x-amz-pay-host'] = amz_pay_host(request_uri) + hashed_payload = hex_and_hash(request_payload) + canonical_uri = canonical_uri(request_uri) + canonical_query_string = create_canonical_query(request_parameters) + canonical_header = header_string(pre_signed_headers) + signed_headers = canonical_headers_names(pre_signed_headers) + canonical_request = "#{http_request_method.upcase}\n#{canonical_uri}\n#{canonical_query_string}\n#{canonical_header}\n#{signed_headers}\n#{hashed_payload}" + puts("\nCANONICAL REQUEST:\n" + canonical_request) + hashed_canonical_request = "#{@@amazon_signature_algorithm}\n#{hex_and_hash(canonical_request)}" + puts("\nSTRING TO SIGN:\n" + hashed_canonical_request) + Base64.strict_encode64(rsa.sign_pss(@@hash_algorithm, hashed_canonical_request, salt_length: 20, mgf1_hash: @@hash_algorithm)) + end + + def self.hex_and_hash(data) + Digest::SHA256.hexdigest(data) + end + + def self.check_for_payment_critical_data_api(request_uri, http_request_method, request_payload) + payment_critical_data_apis = ['/live/account-management/v1/accounts', '/sandbox/account-management/v1/accounts'] + allowed_http_methods = %w[post put patch] + # For APIs handling payment critical data, the payload shouldn't be + # considered in the signature calculation + payment_critical_data_apis.each do |api| + if request_uri.include?(api) && allowed_http_methods.include?(http_request_method.downcase) + return '' + end + end + request_payload + end + + def self.formatted_timestamp + Time.now.utc.iso8601.split(/[-,:]/).join + end + + def self.canonical_headers(headers) + sorted_canonical_array = {} + headers.each do |key, val| + sorted_canonical_array[key.to_s.downcase] = val if val.present? + end + sorted_canonical_array.sort_by { |key, _value| key }.to_h + end + + def self.amz_pay_host(url) + return '/' unless url.present? + parsed_url = URI.parse(url) + + if parsed_url.host.present? + parsed_url.host + else + '/' + end + end + + def self.canonical_headers_names(headers) + sorted_header = canonical_headers(headers) + parameters = [] + sorted_header.each { |key, _value| parameters << key } + parameters.sort! + parameters.join(';') + end + + def self.string_from_array(array_data) + if array_data.is_a?(Array) + collect_sub_val(array_data) + else + array_data + end + end + + def self.collect_sub_val(parameters) + category_index = 0 + collected_values = '' + + parameters.each do |value| + collected_values += ' ' unless category_index.zero? + collected_values += value + category_index += 1 + end + collected_values + end + + def self.canonical_uri(unencoded_uri) + return '/' if unencoded_uri == '' + + url_array = URI.parse(unencoded_uri) + if url_array.path.present? + url_array.path + else + '/' + end + end + + def self.create_canonical_query(request_parameters) + sorted_request_parameters = sort_canonical_array(request_parameters) + parameters_as_string(sorted_request_parameters) + end + + def self.sort_canonical_array(canonical_array) + sorted_canonical_array = {} + canonical_array.each do |key, val| + if val.is_a?(Object) + sub_arrays(val, key.to_s).each do |new_key, sub_val| + sorted_canonical_array[new_key.to_s] = sub_val + end + elsif !val.present + else + sorted_canonical_array[key.to_s] = val + end + end + sorted_canonical_array.sort_by { |key, _value| key }.to_h + end + + def self.sub_arrays(parameters, category) + category_index = 0 + new_parameters = {} + category_string = "#{category}." + parameters.each do |value| + category_index += 1 + new_parameters["#{category_string}#{category_index}"] = value + end + new_parameters + end + + def self.parameters_as_string(parameters) + query_parameters = [] + parameters.each do |key, value| + query_parameters << "#{key}=#{url_encode(value)}" + end + query_parameters.join('&') + end + + def self.url_encode(value) + URI::encode(value).gsub('%7E', '~') + end + + def self.header_string(headers) + query_parameters = [] + sorted_headers = canonical_headers(headers) + + sorted_headers.each do |key, value| + if value.is_a?(Array) + value = collect_sub_vals(value) + else + query_parameters << "#{key}:#{value}" + end + end + return_string = query_parameters.join("\n") + "#{return_string}\n" + end + + def self.base_api_url + sandbox = @@sandbox ? 'sandbox' : 'live' + { + 'us' => "https://pay-api.amazon.com/#{sandbox}/v1/", + 'uk' => "https://pay-api.amazon.eu/#{sandbox}/v1/", + 'de' => "https://pay-api.amazon.eu/#{sandbox}/v1/", + 'jp' => "https://pay-api.amazon.jp/#{sandbox}/v1/", + }.fetch(@@region) + end +end diff --git a/spree_amazon_payments.gemspec b/spree_amazon_payments.gemspec index c80f8fe..d669c73 100644 --- a/spree_amazon_payments.gemspec +++ b/spree_amazon_payments.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.summary = 'Spree Amazon Payments' s.description = '' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.3.0' s.required_rubygems_version = '>= 1.8.23' s.author = 'Amazon Payments' @@ -26,6 +26,7 @@ Gem::Specification.new do |s| s.add_dependency 'spree_core' s.add_dependency 'pay_with_amazon' s.add_dependency 'amazon_pay' + s.add_dependency 'openssl', '~> 2.1.0' s.add_development_dependency 'capybara' s.add_development_dependency 'coffee-rails' From 193b144a6d78556a05cd7a1946adc0160dbb29b8 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 10 Nov 2019 12:54:01 -0500 Subject: [PATCH 02/39] Updating Spree Functions to use V2 Flow and Functions Part 1 --- app/controllers/spree/amazon_controller.rb | 272 ------------------ app/controllers/spree/amazonpay_controller.rb | 176 ++++++++++++ app/models/spree/amazon_transaction.rb | 17 +- app/models/spree/gateway/amazon.rb | 158 +++++----- app/models/spree_amazon/address.rb | 88 +++--- app/models/spree_amazon/order.rb | 83 ------ app/models/spree_amazon/user.rb | 52 ++-- .../edit/add_pay_with_amazon.html.erb.deface | 2 +- app/views/spree/amazon/_login.html.erb | 46 --- app/views/spree/amazon/_payment.html.erb | 26 -- app/views/spree/amazon/address.html.erb | 111 ------- app/views/spree/amazonpay/_change.html.erb | 6 + app/views/spree/amazonpay/_login.html.erb | 31 ++ .../{amazon => amazonpay}/complete.html.erb | 0 .../{amazon => amazonpay}/confirm.html.erb | 5 +- .../{amazon => amazonpay}/delivery.html.erb | 0 config/routes.rb | 10 +- lib/amazon/charge.rb | 12 +- lib/amazon/checkout_session.rb | 4 +- lib/amazon_mws.rb | 153 ---------- lib/amazon_pay.rb | 2 +- lib/spree_amazon_payments.rb | 1 - 22 files changed, 374 insertions(+), 881 deletions(-) delete mode 100644 app/controllers/spree/amazon_controller.rb create mode 100644 app/controllers/spree/amazonpay_controller.rb delete mode 100644 app/models/spree_amazon/order.rb delete mode 100644 app/views/spree/amazon/_login.html.erb delete mode 100644 app/views/spree/amazon/_payment.html.erb delete mode 100644 app/views/spree/amazon/address.html.erb create mode 100644 app/views/spree/amazonpay/_change.html.erb create mode 100644 app/views/spree/amazonpay/_login.html.erb rename app/views/spree/{amazon => amazonpay}/complete.html.erb (100%) rename app/views/spree/{amazon => amazonpay}/confirm.html.erb (77%) rename app/views/spree/{amazon => amazonpay}/delivery.html.erb (100%) delete mode 100644 lib/amazon_mws.rb diff --git a/app/controllers/spree/amazon_controller.rb b/app/controllers/spree/amazon_controller.rb deleted file mode 100644 index e7072df..0000000 --- a/app/controllers/spree/amazon_controller.rb +++ /dev/null @@ -1,272 +0,0 @@ -## -# Amazon Payments - Login and Pay for Spree Commerce -# -# @category Amazon -# @package Amazon_Payments -# @copyright Copyright (c) 2014 Amazon.com -# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 -# -## -class Spree::AmazonController < Spree::StoreController - helper 'spree/orders' - before_action :check_current_order - before_action :gateway, only: [:address, :payment, :delivery] - before_action :check_amazon_reference_id, only: [:delivery, :complete] - before_action :normalize_addresses, only: [:address, :delivery] - skip_before_action :verify_authenticity_token, only: %i[payment confirm complete] - - respond_to :json - - def address - set_user_information! - - if current_order.cart? - current_order.next! - else - current_order.state = 'address' - current_order.save! - end - end - - def payment - payment = amazon_payment || current_order.payments.create - payment.payment_method = gateway - payment.source ||= Spree::AmazonTransaction.create( - order_reference: params[:order_reference], - order_id: current_order.id, - retry: current_order.amazon_transactions.unsuccessful.any? - ) - - payment.save! - - render json: {} - end - - def delivery - address = SpreeAmazon::Address.find( - current_order.amazon_order_reference_id, - gateway: gateway, - address_consent_token: access_token - ) - - current_order.state = 'address' - - if address - current_order.email = current_order.email || spree_current_user.try(:email) || "pending@amazon.com" - update_current_order_address!(address, spree_current_user.try(:ship_address)) - - current_order.save! - current_order.next - - current_order.reload - - if current_order.shipments.empty? - render plain: 'Not shippable to this address' - else - render layout: false - end - else - head :ok - end - end - - def confirm - if amazon_payment.present? && current_order.update_from_params(params, permitted_checkout_attributes, request.headers.env) - while !current_order.confirm? && current_order.next - end - - update_payment_amount! - current_order.next! unless current_order.confirm? - complete - else - redirect_to :back - end - end - - def complete - @order = current_order - authorize!(:edit, @order, cookies.signed[:guest_token]) - - unless @order.amazon_transaction.retry - amazon_response = set_order_reference_details! - unless amazon_response.constraints.blank? - redirect_to address_amazon_order_path, notice: amazon_response.constraints and return - end - end - - complete_amazon_order! - - if @order.confirm? && @order.next - @order.update_column(:bill_address, @order.ship_address_id) - @current_order = nil - flash.notice = Spree.t(:order_processed_successfully) - flash[:order_completed] = true - redirect_to spree.order_path(@order) - else - amazon_transaction = @order.amazon_transaction - @order.state = 'cart' - amazon_transaction.reload - if amazon_transaction.soft_decline - @order.save! - redirect_to address_amazon_order_path, notice: amazon_transaction.message - else - @order.amazon_transactions.destroy_all - @order.save! - redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) - end - end - end - - def gateway - @gateway ||= Spree::Gateway::Amazon.for_currency(current_order.currency) - end - - private - - def access_token - params[:access_token] - end - - def amazon_order - @amazon_order ||= SpreeAmazon::Order.new( - reference_id: current_order.amazon_order_reference_id, - gateway: gateway, - ) - end - - def amazon_payment - current_order.payments.valid.amazon.first - end - - def update_payment_amount! - payment = amazon_payment - payment.amount = current_order.order_total_after_store_credit - payment.save! - end - - def set_order_reference_details! - amazon_order.set_order_reference_details( - current_order.order_total_after_store_credit, - seller_order_id: current_order.number, - store_name: current_order.store.name, - ) - end - - def set_user_information! - return unless Gem::Specification::find_all_by_name('spree_social').any? && access_token - - auth_hash = SpreeAmazon::User.find(gateway: gateway, - access_token: access_token) - - return unless auth_hash - - authentication = Spree::UserAuthentication.find_by_provider_and_uid(auth_hash['provider'], auth_hash['uid']) - - if authentication.present? && authentication.try(:user).present? - user = authentication.user - sign_in(user, scope: :spree_user) - elsif spree_current_user - spree_current_user.apply_omniauth(auth_hash) - spree_current_user.save! - user = spree_current_user - else - email = auth_hash['info']['email'] - user = Spree::User.find_by_email(email) || Spree::User.new - user.apply_omniauth(auth_hash) - user.save! - sign_in(user, scope: :spree_user) - end - - # make sure to merge the current order with signed in user previous cart - set_current_order - - current_order.associate_user!(user) - session[:guest_token] = nil - end - - def complete_amazon_order! - amazon_order.confirm - end - - def checkout_params - params.require(:order).permit(permitted_checkout_attributes) - end - - def update_current_order_address!(amazon_address, spree_user_address = nil) - bill_address = current_order.bill_address - ship_address = current_order.ship_address - - new_address = Spree::Address.new address_attributes(amazon_address, spree_user_address) - if spree_address_book_available? - user_address = spree_current_user.addresses.find do |address| - address.same_as?(new_address) - end - - if user_address - current_order.update_column(:ship_address_id, user_address.id) - else - new_address.save! - current_order.update_column(:ship_address_id, new_address.id) - end - else - if ship_address.nil? || ship_address.empty? - new_address.save! - current_order.update_column(:ship_address_id, new_address.id) - else - ship_address.update_attributes(address_attributes(amazon_address, spree_user_address)) - end - end - end - - def address_attributes(amazon_address, spree_user_address = nil) - address_params = { - firstname: amazon_address.first_name || spree_user_address.try(:first_name) || "Amazon", - lastname: amazon_address.last_name || spree_user_address.try(:last_name) || "User", - address1: amazon_address.address1 || spree_user_address.try(:address1) || "N/A", - address2: amazon_address.address2, - phone: amazon_address.phone || spree_user_address.try(:phone) || "000-000-0000", - city: amazon_address.city || spree_user_address.try(:city), - zipcode: amazon_address.zipcode || spree_user_address.try(:zipcode), - state: amazon_address.state || spree_user_address.try(:state), - country: amazon_address.country || spree_user_address.try(:country) - } - - if spree_address_book_available? - address_params = address_params.merge(user: spree_current_user) - end - - address_params - end - - def check_current_order - unless current_order - head :ok - end - end - - def check_amazon_reference_id - unless current_order.amazon_order_reference_id - head :ok - end - end - - def spree_address_book_available? - Gem::Specification::find_all_by_name('spree_address_book').any? - end - - def normalize_addresses - # ensure that there is no validation errors and addresses were saved - return unless current_order.bill_address && current_order.ship_address && spree_address_book_available? - - bill_address = current_order.bill_address - ship_address = current_order.ship_address - if current_order.bill_address_id != current_order.ship_address_id && bill_address.same_as?(ship_address) - current_order.update_column(:bill_address_id, ship_address.id) - bill_address.destroy - else - bill_address.update_attribute(:user_id, spree_current_user.try(:id)) - end - - ship_address.update_attribute(:user_id, spree_current_user.try(:id)) - end -end diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb new file mode 100644 index 0000000..00126c2 --- /dev/null +++ b/app/controllers/spree/amazonpay_controller.rb @@ -0,0 +1,176 @@ +## +# Amazon Payments - Login and Pay for Spree Commerce +# +# @category Amazon +# @package Amazon_Payments +# @copyright Copyright (c) 2014 Amazon.com +# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 +# +## +class Spree::AmazonpayController < Spree::StoreController + helper 'spree/orders' + before_action :check_current_order + before_action :gateway, only: [:confirm, :create, :payment, :complete] + skip_before_action :verify_authenticity_token, only: %i[create complete] + + respond_to :json + + def create + if current_order.cart? + current_order.next! + else + current_order.state = 'address' + current_order.save! + end + + params = { webCheckoutDetail: + { checkoutReviewReturnUrl: 'http://localhost:3000/amazonpay/confirm' }, + storeId: @gateway.preferred_client_id } + + render json: AmazonPay::CheckoutSession.create(params) + end + + def confirm + redirect_to cart_path && return unless current_order.address? + + response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) + + amazon_user = SpreeAmazon::User.from_response(response) + set_user_information(amazon_user.auth_hash) + + amazon_address = SpreeAmazon::Address.from_response(response) + address_attributes = amazon_address.attributes + + if spree_address_book_available? + address_attributes = address_attributes.merge(user: spree_current_user) + end + + current_order.update_attributes!(bill_address_attributes: address_attributes, + ship_address_attributes: address_attributes, + email: amazon_user.email) + + current_order.next! + + if current_order.shipments.empty? + redirect_to cart_path, notice: 'Cannot ship to this address' + end + end + + def payment + authorize!(:edit, current_order, cookies.signed[:guest_token]) + + params = { + webCheckoutDetail: { + checkoutResultReturnUrl: 'http://localhost:3000/amazonpay/complete' + }, + paymentDetail: { + paymentIntent: 'Authorize', + canHandlePendingAuthorization: true, + chargeAmount: { + amount: current_order.order_total_after_store_credit, + currencyCode: current_currency + } + }, + merchantMetadata: { + merchantReferenceId: current_order.number, + merchantStoreName: current_store.name, + noteToBuyer: '', + customInformation: '' + } + } + + response = AmazonPay::CheckoutSession.update(amazon_checkout_session_id, params) + redirect_to response[:webCheckoutDetail][:amazonPayRedirectUrl] + end + + def complete + response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) + + @order = current_order + + payments = @order.payments + payment = payments.create + payment.payment_method = @gateway + payment.source ||= Spree::AmazonTransaction.create( + order_reference: response[:chargePermissionId], + order_id: @order.id, + capture_id: response[:chargeId], + retry: false + ) + payment.amount = @order.order_total_after_store_credit + payment.response_code = response[:chargeId] + payment.save! + + @order.reload + + while @order.next; end + + if @order.complete? + @current_order = nil + flash.notice = Spree.t(:order_processed_successfully) + flash[:order_completed] = true + redirect_to spree.order_path(@order) + else + amazon_transaction = @order.amazon_transaction + amazon_transaction.reload + if amazon_transaction.soft_decline + redirect_to confirm_amazonpay_path(amazonCheckoutSessionId: amazon_checkout_session_id), notice: amazon_transaction.message + else + @order.amazon_transactions.destroy_all + @order.save! + redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) + end + end + end + + def gateway + @gateway ||= Spree::Gateway::Amazon.for_currency(current_order.currency) + AmazonPay.region = @gateway.preferred_region + AmazonPay.public_key_id = @gateway.preferred_public_key_id + AmazonPay.sandbox = @gateway.preferred_test_mode + AmazonPay.private_key = @gateway.preferred_private_key_file_location + end + + private + + def amazon_checkout_session_id + params[:amazonCheckoutSessionId] + end + + def set_user_information(auth_hash) + return unless Gem::Specification.find_all_by_name('spree_social').any? && auth_hash + + authentication = Spree::UserAuthentication.find_by_provider_and_uid(auth_hash['provider'], auth_hash['uid']) + + if authentication.present? && authentication.try(:user).present? + user = authentication.user + sign_in(user, scope: :spree_user) + elsif spree_current_user + spree_current_user.apply_omniauth(auth_hash) + spree_current_user.save! + user = spree_current_user + else + email = auth_hash['info']['email'] + user = Spree::User.find_by_email(email) || Spree::User.new + user.apply_omniauth(auth_hash) + user.save! + sign_in(user, scope: :spree_user) + end + + # make sure to merge the current order with signed in user previous cart + set_current_order + + current_order.associate_user!(user) + session[:guest_token] = nil + end + + def check_current_order + unless current_order + redirect_to cart_path + end + end + + def spree_address_book_available? + Gem::Specification.find_all_by_name('spree_address_book').any? + end +end diff --git a/app/models/spree/amazon_transaction.rb b/app/models/spree/amazon_transaction.rb index 404efda..403de49 100644 --- a/app/models/spree/amazon_transaction.rb +++ b/app/models/spree/amazon_transaction.rb @@ -9,7 +9,7 @@ ## module Spree class AmazonTransaction < ActiveRecord::Base - has_many :payments, :as => :source + has_many :payments, as: :source scope :unsuccessful, -> { where(success: false) } @@ -42,23 +42,24 @@ def can_close?(payment) end def actions - %w{capture credit void close} + %w[capture credit void close] end def close!(payment) return true unless can_close?(payment) - amazon_order = SpreeAmazon::Order.new( - gateway: payment.payment_method, - reference_id: order_reference - ) + params = { + closureReason: 'No more charges required', + cancelPendingCharges: false + } - response = amazon_order.close_order_reference! + response = AmazonPay::ChargePermisson.close(order_reference, params) if response.success? update_attributes(closed_at: DateTime.now) else - gateway_error(response) + raise Spree::Core::GatewayError.new(text) + #gateway_error(response) end end diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 09a77fe..6a331d2 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -9,7 +9,7 @@ ## module Spree class Gateway::Amazon < Gateway - REGIONS = %w(us uk de jp).freeze + REGIONS = %w[us uk de jp].freeze preference :currency, :string, default: -> { Spree::Config.currency } preference :client_id, :string @@ -18,6 +18,8 @@ class Gateway::Amazon < Gateway preference :aws_secret_access_key, :string preference :region, :string, default: 'us' preference :site_domain, :string + preference :public_key_id, :string + preference :private_key_file_location, :string has_one :provider @@ -27,23 +29,12 @@ def self.for_currency(currency) where(active: true).detect { |gateway| gateway.preferred_currency == currency } end - def api_url - sandbox = preferred_test_mode ? '_Sandbox' : '' - { - 'us' => "https://mws.amazonservices.com/OffAmazonPayments#{sandbox}/2013-01-01", - 'uk' => "https://mws-eu.amazonservices.com/OffAmazonPayments#{sandbox}/2013-01-01", - 'de' => "https://mws-eu.amazonservices.com/OffAmazonPayments#{sandbox}/2013-01-01", - 'jp' => "https://mws.amazonservices.jp/OffAmazonPayments#{sandbox}/2013-01-01", - }.fetch(preferred_region) - end - def widgets_url - sandbox = preferred_test_mode ? '/sandbox' : '' { - 'us' => "https://static-na.payments-amazon.com/OffAmazonPayments/us#{sandbox}/js/Widgets.js", - 'uk' => "https://static-eu.payments-amazon.com/OffAmazonPayments/uk#{sandbox}/lpa/js/Widgets.js", - 'de' => "https://static-eu.payments-amazon.com/OffAmazonPayments/de#{sandbox}/lpa/js/Widgets.js", - 'jp' => "https://origin-na.ssl-images-amazon.com/images/G/09/EP/offAmazonPayments#{sandbox}/prod/lpa/js/Widgets.js", + 'us' => "https://static-na.payments-amazon.com/checkout.js", + 'uk' => "https://static-eu.payments-amazon.com/checkout.js", + 'de' => "https://static-eu.payments-amazon.com/checkout.js", + 'jp' => "https://static-fe.payments-amazon.com/checkout.js", }.fetch(preferred_region) end @@ -68,104 +59,86 @@ def source_required? end def authorize(amount, amazon_checkout, gateway_options={}) - if amount < 0 - return ActiveMerchant::Billing::Response.new(true, "Success", {}) - end + return ActiveMerchant::Billing::Response.new(true, 'Success', {}) if amount < 0 - order_number, payment_number = extract_order_and_payment_number(gateway_options) - order = Spree::Order.find_by!(number: order_number) - payment = Spree::Payment.find_by!(number: payment_number) - authorization_reference_id = operation_unique_id(payment) + load_amazon_pay - load_amazon_mws(order.amazon_order_reference_id) + params = { + chargePermissionId: amazon_checkout.order_reference, + chargeAmount: { + amount: (amount / 100.0).to_s, + currencyCode: gateway_options[:currency] + }, + captureNow: true, + canHandlePendingAuthorization: true + } - mws_res = begin - @mws.authorize( - authorization_reference_id, - amount / 100.0, - order.currency, - seller_authorization_note: sandbox_authorize_simulation_string(order), - ) - rescue RuntimeError => e - raise Spree::Core::GatewayError.new(e.to_s) - end + response = AmazonPay::Charge.create(params) - amazon_response = SpreeAmazon::Response::Authorization.new(mws_res) - parsed_response = amazon_response.parse rescue nil - - if amazon_response.state == 'Declined' - success = false - if amazon_response.reason_code == 'InvalidPaymentMethod' - soft_decline = true - message = amazon_response.error_message - else - soft_decline = false - message = "Authorization failure: #{amazon_response.reason_code}" - end - else - success = true - order.amazon_transaction.update!( - authorization_id: amazon_response.response_id - ) - message = 'Success' - soft_decline = nil + success = response.code.to_i == 200 || response.code.to_i == 201 + message = 'Success' + soft_decline = false + + unless success + body = JSON.parse(response.body, symbolize_names: true) + message = body[:message] + soft_decline = body[:reasonCode] == 'SoftDeclined' end # Saving information in last amazon transaction for error flow in amazon controller - order.amazon_transaction.update!( + amazon_checkout.update!( success: success, message: message, - authorization_reference_id: authorization_reference_id, + capture_id: body[:chargeId], soft_decline: soft_decline, retry: !success ) - ActiveMerchant::Billing::Response.new( - success, - message, - { - 'response' => mws_res, - 'parsed_response' => parsed_response, - }, - ) + ActiveMerchant::Billing::Response.new(success, message, 'response' => response.body) end def capture(amount, amazon_checkout, gateway_options={}) - if amount < 0 - return credit(amount.abs, nil, nil, gateway_options) - end - order_number, payment_number = extract_order_and_payment_number(gateway_options) - order = Spree::Order.find_by!(number: order_number) - payment = Spree::Payment.find_by!(number: payment_number) - authorization_id = order.amazon_transaction.authorization_id - capture_reference_id = operation_unique_id(payment) - load_amazon_mws(order.amazon_order_reference_id) + return credit(amount.abs, nil, nil, gateway_options) if amount < 0 + + load_amazon_pay - mws_res = @mws.capture(authorization_id, capture_reference_id, amount / 100.00, order.currency) + params = { + captureAmount: { + amount: (amount / 100.0).to_s, + currencyCode: gateway_options[:currency] + }, + softDescriptor: 'Store Name' + } - response = SpreeAmazon::Response::Capture.new(mws_res) + response = AmazonPay::Charge.capture(amazon_checkout.capture_id, params) - payment.response_code = response.response_id - payment.save! + success = response.code.to_i == 200 || response.code.to_i == 201 + message = 'Success' + soft_decline = false - t = order.amazon_transaction - t.capture_id = response.response_id - t.save! + unless success + body = JSON.parse(response.body, symbolize_names: true) + message = body[:message] + soft_decline = body[:reasonCode] == 'SoftDeclined' + end - ActiveMerchant::Billing::Response.new(response.success_state?, "OK", - { - 'response' => mws_res, - 'parsed_response' => response.parse, - } + # Saving information in last amazon transaction for error flow in amazon controller + amazon_checkout.update!( + success: success, + message: message, + soft_decline: soft_decline, + retry: !success ) + + ActiveMerchant::Billing::Response.new(success, message, 'response' => response.body) end def purchase(amount, amazon_checkout, gateway_options={}) - auth_result = authorize(amount, amazon_checkout, gateway_options) - if auth_result.success? + #auth_result = authorize(amount, amazon_checkout, gateway_options) + #if auth_result.success? capture(amount, amazon_checkout, gateway_options) - else - auth_result - end + #else + # auth_result + #end end def credit(amount, _response_code, gateway_options = {}) @@ -215,8 +188,11 @@ def cancel(response_code) private - def load_amazon_mws(reference) - @mws ||= AmazonMws.new(reference, gateway: self) + def load_amazon_pay + AmazonPay.region = preferred_region + AmazonPay.public_key_id = preferred_public_key_id + AmazonPay.sandbox = preferred_test_mode + AmazonPay.private_key = preferred_private_key_file_location end def extract_order_and_payment_number(gateway_options) diff --git a/app/models/spree_amazon/address.rb b/app/models/spree_amazon/address.rb index 51563a0..74ab896 100644 --- a/app/models/spree_amazon/address.rb +++ b/app/models/spree_amazon/address.rb @@ -1,46 +1,55 @@ module SpreeAmazon class Address class << self - def find(order_reference, gateway:, address_consent_token: nil) - response = mws( - order_reference, - gateway: gateway, - address_consent_token: address_consent_token, - ).fetch_order_data - from_response(response) + def from_response(response) + new attributes_from_response(response[:shippingAddress]) end - def from_response(response) - return nil if response.destination["PhysicalDestination"].blank? - new attributes_from_response(response.destination["PhysicalDestination"]) + def attributes_from_response(address_params) + @attributes = { + address1: address_params[:addressLine1], + address2: address_params[:addressLine2], + first_name: convert_first_name(address_params[:name]) || 'Amazon', + last_name: convert_last_name(address_params[:name]) || 'User', + city: address_params[:city], + zipcode: address_params[:postalCode], + state: convert_state(address_params[:stateOrRegion], + convert_country(address_params[:countryCode])), + country: convert_country(address_params[:countryCode]), + phone: address_params[:phoneNumber] || '0000000000' + } + end + + def attributes + @attributes end private - def mws(order_reference, gateway:, address_consent_token: nil) - AmazonMws.new( - order_reference, - gateway: gateway, - address_consent_token: address_consent_token, - ) + def convert_first_name(name) + return nil if name.blank? + name.split(' ').first end - def attributes_from_response(response) - { - address1: response["AddressLine1"], - address2: response["AddressLine2"], - name: response["Name"], - city: response["City"], - zipcode: response["PostalCode"], - state_name: response["StateOrRegion"], - country_code: response["CountryCode"], - phone: response["Phone"] - } + def convert_last_name(name) + return nil if name.blank? + names = name.split(' ') + names.shift + names = names.join(' ') + names.blank? ? nil : names + end + + def convert_country(country_code) + Spree::Country.find_by(iso: country_code) + end + + def convert_state(state_name, country) + Spree::State.find_by(abbr: state_name, country: country) end end - attr_accessor :name, :city, :zipcode, :state_name, :country_code, - :address1, :address2, :phone + attr_accessor :first_name, :last_name, :city, :zipcode, + :state, :country, :address1, :address2, :phone def initialize(attributes) attributes.each_pair do |key, value| @@ -48,25 +57,8 @@ def initialize(attributes) end end - def first_name - return nil if name.blank? - name.split(' ').first - end - - def last_name - return nil if name.blank? - names = name.split(' ') - names.shift - names = names.join(' ') - names.blank? ? nil : names - end - - def country - @country ||= Spree::Country.find_by(iso: country_code) - end - - def state - @state ||= Spree::State.find_by(abbr: state_name, country: country) + def attributes + self.class.attributes end end end diff --git a/app/models/spree_amazon/order.rb b/app/models/spree_amazon/order.rb deleted file mode 100644 index 077a2f3..0000000 --- a/app/models/spree_amazon/order.rb +++ /dev/null @@ -1,83 +0,0 @@ -class SpreeAmazon::Order - class CloseFailure < StandardError; end - - attr_accessor :state, :total, :email, :address, :reference_id, :currency, - :gateway, :address_consent_token - - def initialize(attributes) - if !attributes.key?(:gateway) - raise ArgumentError, "SpreeAmazon::Order requires a gateway parameter" - end - self.attributes = attributes - end - - def fetch - response = mws.fetch_order_data - self.attributes = attributes_from_response(response) - self - end - - def confirm - mws.confirm_order - end - - def close_order_reference! - response = mws.close_order_reference - parsed_response = Hash.from_xml(response.body) rescue nil - - if response.success - success = true - message = 'Success' - else - success = false - message = if parsed_response && parsed_response['ErrorResponse'] - error = parsed_response.fetch('ErrorResponse').fetch('Error') - "#{response.code} #{error.fetch('Code')}: #{error.fetch('Message')}" - else - "#{response.code} #{response.body}" - end - - end - - ActiveMerchant::Billing::Response.new( - success, - message, - { - 'response' => response, - 'parsed_response' => parsed_response, - }, - ) - end - - # @param total [String] The amount to set on the order - # @param amazon_options [Hash] These options are forwarded to the underlying - # call to PayWithAmazon::Client#set_order_reference_details - def set_order_reference_details(total, amazon_options={}) - SpreeAmazon::Response::SetOrderReferenceDetails.new mws.set_order_reference_details(total, amazon_options) - end - - private - - def attributes=(attributes) - attributes.each_pair do |key, value| - send("#{key}=", value) - end - end - - def mws - @mws ||= AmazonMws.new( - reference_id, - gateway: gateway, - address_consent_token: address_consent_token, - ) - end - - def attributes_from_response(response) - { - state: response.state, - total: response.total, - email: response.email, - address: SpreeAmazon::Address.from_response(response) - } - end -end diff --git a/app/models/spree_amazon/user.rb b/app/models/spree_amazon/user.rb index 5fa553f..d685e30 100644 --- a/app/models/spree_amazon/user.rb +++ b/app/models/spree_amazon/user.rb @@ -1,34 +1,38 @@ module SpreeAmazon class User class << self - def find(gateway:, access_token: nil) - response = lwa(gateway).get_login_profile(access_token) - from_response(response) - end - - def from_response(response) - return nil if response['email'].blank? - attributes_from_response(response) - end + def from_response(response) + new attributes_from_response(response[:buyer]) + end private - def lwa(gateway) - AmazonPay::Login.new(gateway.preferred_client_id, - region: :na, # Default: :na - sandbox: gateway.preferred_test_mode) # Default: false - end + def attributes_from_response(buyer_params) + { + name: buyer_params[:name], + email: buyer_params[:email], + uid: buyer_params[:buyerId] + } + end + end - def attributes_from_response(response) - { - 'provider' => 'amazon', - 'info' => { - 'email' => response['email'], - 'name' => response['name'] - }, - 'uid' => response['user_id'] - } - end + attr_accessor :email, :name, :uid + + def initialize(attributes) + attributes.each_pair do |key, value| + send("#{key}=", value) end + end + + def auth_hash + { + 'provider' => 'amazon', + 'info' => { + 'email' => email, + 'name' => name + }, + 'uid' => uid + } + end end end diff --git a/app/overrides/spree/orders/edit/add_pay_with_amazon.html.erb.deface b/app/overrides/spree/orders/edit/add_pay_with_amazon.html.erb.deface index b652b29..fcf11cd 100644 --- a/app/overrides/spree/orders/edit/add_pay_with_amazon.html.erb.deface +++ b/app/overrides/spree/orders/edit/add_pay_with_amazon.html.erb.deface @@ -1,4 +1,4 @@ <% if Spree::Gateway::Amazon.try(:first).try(:active?) %> - <%= render :partial => "spree/amazon/login" %> + <%= render :partial => "spree/amazonpay/login" %> <% end %> diff --git a/app/views/spree/amazon/_login.html.erb b/app/views/spree/amazon/_login.html.erb deleted file mode 100644 index ca08ff7..0000000 --- a/app/views/spree/amazon/_login.html.erb +++ /dev/null @@ -1,46 +0,0 @@ -<%# %> -<%# Amazon Payments - Login and Pay for Spree Commerce %> -<%# %> -<%# @category Amazon %> -<%# @package Amazon_Payments %> -<%# @copyright Copyright (c) 2014 Amazon.com %> -<%# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 %> -<%# %> - -<% - gateway = Spree::Gateway::Amazon.for_currency(current_order(create_order_if_necessary: true).currency) - button_type = 'Pay' unless local_assigns[:button_type] - button_color = 'LightGray' unless local_assigns[:button_color] - button_size = 'medium' unless local_assigns[:button_size] - return_path = '/amazon_order/address' unless local_assigns[:return_path] -%> - - - -<%= javascript_include_tag gateway.widgets_url %> - -
- - diff --git a/app/views/spree/amazon/_payment.html.erb b/app/views/spree/amazon/_payment.html.erb deleted file mode 100644 index cb91525..0000000 --- a/app/views/spree/amazon/_payment.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -<%# %> -<%# Amazon Payments - Login and Pay for Spree Commerce %> -<%# %> -<%# @category Amazon %> -<%# @package Amazon_Payments %> -<%# @copyright Copyright (c) 2014 Amazon.com %> -<%# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 %> -<%# %> -new OffAmazonPayments.Widgets.Wallet({ - sellerId: '<%= @gateway.preferred_merchant_id %>', - onPaymentSelect: function(orderReference) { - jQuery.post( - '/amazon_order/payment', - {"order_reference": order_reference}, - function() { - $('#continue_to_delivery').click(); - } - ); - }, - design: { - designMode: 'responsive' - }, - onError: function(error) { - // your error handling code - } -}).bind("walletWidgetDiv"); diff --git a/app/views/spree/amazon/address.html.erb b/app/views/spree/amazon/address.html.erb deleted file mode 100644 index f6eabb8..0000000 --- a/app/views/spree/amazon/address.html.erb +++ /dev/null @@ -1,111 +0,0 @@ -<%# %> -<%# Amazon Payments - Login and Pay for Spree Commerce %> -<%# %> -<%# @category Amazon %> -<%# @package Amazon_Payments %> -<%# @copyright Copyright (c) 2014 Amazon.com %> -<%# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 %> -<%# %> -<% content_for :head do %> - - - - -<%= javascript_include_tag @gateway.widgets_url %> - - -<% end %> - -
- -
-
-
-
- -
-
-
-
- - - -
- Loading... -
- -
diff --git a/app/views/spree/amazonpay/_change.html.erb b/app/views/spree/amazonpay/_change.html.erb new file mode 100644 index 0000000..d573487 --- /dev/null +++ b/app/views/spree/amazonpay/_change.html.erb @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/views/spree/amazonpay/_login.html.erb b/app/views/spree/amazonpay/_login.html.erb new file mode 100644 index 0000000..751183f --- /dev/null +++ b/app/views/spree/amazonpay/_login.html.erb @@ -0,0 +1,31 @@ +<%# %> +<%# Amazon Payments - Login and Pay for Spree Commerce %> +<%# %> +<%# @category Amazon %> +<%# @package Amazon_Payments %> +<%# @copyright Copyright (c) 2014 Amazon.com %> +<%# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 %> +<%# %> + +<% + gateway = Spree::Gateway::Amazon.for_currency(current_order(create_order_if_necessary: true).currency) + return_path = '/amazonpay/create' unless local_assigns[:return_path] +%> + +<%= javascript_include_tag gateway.widgets_url %> + +
+ + diff --git a/app/views/spree/amazon/complete.html.erb b/app/views/spree/amazonpay/complete.html.erb similarity index 100% rename from app/views/spree/amazon/complete.html.erb rename to app/views/spree/amazonpay/complete.html.erb diff --git a/app/views/spree/amazon/confirm.html.erb b/app/views/spree/amazonpay/confirm.html.erb similarity index 77% rename from app/views/spree/amazon/confirm.html.erb rename to app/views/spree/amazonpay/confirm.html.erb index 836e48a..64c0dbd 100644 --- a/app/views/spree/amazon/confirm.html.erb +++ b/app/views/spree/amazonpay/confirm.html.erb @@ -6,14 +6,17 @@ <%# @copyright Copyright (c) 2014 Amazon.com %> <%# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 %> <%# %> +<%= javascript_include_tag @gateway.widgets_url %> +
<%= Spree.t(:confirm) %> <%= render :partial => 'spree/shared/order_details', :locals => { :order => current_order } %>
-<%= form_tag spree.complete_amazon_order_path, :class => 'edit_order' do %> +<%= form_tag spree.payment_amazonpay_path, :class => 'edit_order' do %>
+ <%= hidden_field_tag :amazonCheckoutSessionId, params[:amazonCheckoutSessionId] %> <%= submit_tag Spree.t(:place_order), :class => 'btn btn-lg btn-success' %>
diff --git a/app/views/spree/amazon/delivery.html.erb b/app/views/spree/amazonpay/delivery.html.erb similarity index 100% rename from app/views/spree/amazon/delivery.html.erb rename to app/views/spree/amazonpay/delivery.html.erb diff --git a/config/routes.rb b/config/routes.rb index fc8436f..f94ac8c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,14 +8,14 @@ # ## Spree::Core::Engine.routes.draw do - resource :amazon_order, only: [], controller: "amazon" do + resource :amazonpay, only: [], controller: 'amazonpay' do member do - get 'address' + post 'create' + get 'confirm' + post 'delivery' post 'payment' - get 'delivery' - post 'confirm' post 'complete' - get 'complete' + get 'complete' end end diff --git a/lib/amazon/charge.rb b/lib/amazon/charge.rb index 304cd8d..adfbfc0 100644 --- a/lib/amazon/charge.rb +++ b/lib/amazon/charge.rb @@ -1,23 +1,19 @@ module AmazonPay class Charge def self.create(params) - response = AmazonPay.request('post', 'charges', params) - response.body + AmazonPay.request('post', 'charges', params) end def self.get(charge_id) - response = AmazonPay.request('get', "charges/#{charge_id}") - response.body + AmazonPay.request('get', "charges/#{charge_id}") end def self.capture(charge_id, params) - response = AmazonPay.request('post', "charges/#{charge_id}", params) - response.body + AmazonPay.request('post', "charges/#{charge_id}/capture", params) end def self.cancel(charge_id, params) - response = AmazonPay.request('delete', "charges/#{charge_id}/cancel", params) - response.body + AmazonPay.request('delete', "charges/#{charge_id}/cancel", params) end end end diff --git a/lib/amazon/checkout_session.rb b/lib/amazon/checkout_session.rb index a05f482..b6826c4 100644 --- a/lib/amazon/checkout_session.rb +++ b/lib/amazon/checkout_session.rb @@ -7,12 +7,12 @@ def self.create(params) def self.get(checkout_session_id) response = AmazonPay.request('get', "checkoutSessions/#{checkout_session_id}") - response.body + JSON.parse(response.body, symbolize_names: true) end def self.update(checkout_session_id, params) response = AmazonPay.request('patch', "checkoutSessions/#{checkout_session_id}", params) - response.body + JSON.parse(response.body, symbolize_names: true) end end end diff --git a/lib/amazon_mws.rb b/lib/amazon_mws.rb deleted file mode 100644 index 6a42818..0000000 --- a/lib/amazon_mws.rb +++ /dev/null @@ -1,153 +0,0 @@ -## -# Amazon Payments - Login and Pay for Spree Commerce -# -# @category Amazon -# @package Amazon_Payments -# @copyright Copyright (c) 2014 Amazon.com -# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 -# -## - -require 'pay_with_amazon' - -class AmazonMwsOrderResponse - def initialize(response) - @response = Hash.from_xml(response.body).fetch("GetOrderReferenceDetailsResponse", {}) - end - - def destination - @response.fetch("GetOrderReferenceDetailsResult", {}).fetch("OrderReferenceDetails", {}).fetch("Destination", {}) - end - - def constraints - @response.fetch("GetOrderReferenceDetailsResult", {}).fetch("OrderReferenceDetails", {}).fetch("Constraints", {}).fetch("Constraint", {}) - end - - def state - @response.fetch("GetOrderReferenceDetailsResult", {}).fetch("OrderReferenceDetails", {}).fetch("OrderReferenceStatus", {}).fetch("State", {}) - end - - def total - total_block = @response.fetch("GetOrderReferenceDetailsResult", {}).fetch("OrderReferenceDetails", {}).fetch("OrderTotal", {}) - Spree::Money.new(total_block.fetch("Amount", 0), :with_currency => total_block.fetch("CurrencyCode", "USD")) - end - - def email - @response.fetch("GetOrderReferenceDetailsResult", {}).fetch("OrderReferenceDetails", {}).fetch("Buyer", {}).fetch("Email", {}) - end -end - -class AmazonMws - delegate :get_order_reference_details, to: :client - - def initialize(amazon_order_reference_id, gateway:, address_consent_token: nil) - @amazon_order_reference_id = amazon_order_reference_id - @gateway = gateway - @address_consent_token = address_consent_token - end - - - def fetch_order_data - AmazonMwsOrderResponse.new( - get_order_reference_details(@amazon_order_reference_id, address_consent_token: @address_consent_token) - ) - end - - # @param total [String] The amount to set on the order - # @param amazon_options [Hash] These options are forwarded to the underlying - # call to PayWithAmazon::Client#set_order_reference_details - def set_order_reference_details(total, amazon_options={}) - client.set_order_reference_details( - @amazon_order_reference_id, - total, - amazon_options - ) - end - - def set_order_data(total, currency) - client.set_order_reference_details( - @amazon_order_reference_id, - total, - currency_code: currency - ) - end - - def confirm_order - client.confirm_order_reference(@amazon_order_reference_id) - end - - def authorize(authorization_reference_id, total, currency, seller_authorization_note: nil) - client.authorize( - @amazon_order_reference_id, - authorization_reference_id, - total, - currency_code: currency, - transaction_timeout: 0, # 0 is synchronous mode - capture_now: false, - seller_authorization_note: seller_authorization_note, - ) - end - - def get_authorization_details(auth_id) - client.get_authorization_details(auth_id) - end - - def capture(auth_number, ref_number, total, currency, seller_capture_note: nil) - client.capture( - auth_number, - ref_number, - total, - currency_code: currency, - seller_capture_note: seller_capture_note - ) - end - - def get_capture_details(capture_id) - client.get_capture_details(capture_id) - end - - def refund(capture_id, ref_number, total, currency, seller_refund_note: nil) - client.refund( - capture_id, - ref_number, - total, - currency_code: currency, - seller_refund_note: seller_refund_note - ) - end - - def get_refund_details(refund_id) - client.get_refund_details(refund_id) - end - - def cancel - client.cancel_order_reference(@amazon_order_reference_id) - end - - # Amazon's description: - # > Call the CloseOrderReference operation to indicate that a previously - # > confirmed order reference has been fulfilled (fully or partially) and that - # > you do not expect to create any new authorizations on this order - # > reference. You can still capture funds against open authorizations on the - # > order reference. - # > After you call this operation, the order reference is moved into the - # > Closed state. - # https://payments.amazon.com/documentation/apireference/201752000 - def close_order_reference - client.close_order_reference(@amazon_order_reference_id) - end - - private - - def client - @client ||= PayWithAmazon::Client.new( - @gateway.preferred_merchant_id, - @gateway.preferred_aws_access_key_id, - @gateway.preferred_aws_secret_access_key, - region: @gateway.preferred_region.to_sym, - currency_code: @gateway.preferred_currency, - sandbox: @gateway.preferred_test_mode, - platform_id: nil, # TODO: Get a platform id for spree_amazon_payments - ) - end -end diff --git a/lib/amazon_pay.rb b/lib/amazon_pay.rb index 7839575..7b622ca 100644 --- a/lib/amazon_pay.rb +++ b/lib/amazon_pay.rb @@ -251,7 +251,7 @@ def self.header_string(headers) sorted_headers.each do |key, value| if value.is_a?(Array) - value = collect_sub_vals(value) + value = collect_sub_val(value) else query_parameters << "#{key}:#{value}" end diff --git a/lib/spree_amazon_payments.rb b/lib/spree_amazon_payments.rb index 4e40639..a8827a0 100644 --- a/lib/spree_amazon_payments.rb +++ b/lib/spree_amazon_payments.rb @@ -9,5 +9,4 @@ ## require 'spree_core' require 'spree_amazon_payments/engine' -require 'amazon_mws' require 'amazon_pay' From 5c71400795599d4e2f8febb0d02cd42ef1378214 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 10 Nov 2019 21:58:58 -0500 Subject: [PATCH 03/39] Updating Payment Processing and Library Functions --- app/controllers/spree/amazonpay_controller.rb | 3 +- app/models/spree/gateway/amazon.rb | 138 +++++++++--------- lib/amazon/charge_permission.rb | 9 +- lib/amazon/refund.rb | 6 +- 4 files changed, 79 insertions(+), 77 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 00126c2..066cd9c 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -114,7 +114,8 @@ def complete amazon_transaction = @order.amazon_transaction amazon_transaction.reload if amazon_transaction.soft_decline - redirect_to confirm_amazonpay_path(amazonCheckoutSessionId: amazon_checkout_session_id), notice: amazon_transaction.message + redirect_to confirm_amazonpay_path(amazonCheckoutSessionId: amazon_checkout_session_id), + notice: amazon_transaction.message else @order.amazon_transactions.destroy_all @order.save! diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 6a331d2..51835e8 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -31,10 +31,10 @@ def self.for_currency(currency) def widgets_url { - 'us' => "https://static-na.payments-amazon.com/checkout.js", - 'uk' => "https://static-eu.payments-amazon.com/checkout.js", - 'de' => "https://static-eu.payments-amazon.com/checkout.js", - 'jp' => "https://static-fe.payments-amazon.com/checkout.js", + 'us' => 'https://static-na.payments-amazon.com/checkout.js', + 'uk' => 'https://static-eu.payments-amazon.com/checkout.js', + 'de' => 'https://static-eu.payments-amazon.com/checkout.js', + 'jp' => 'https://static-fe.payments-amazon.com/checkout.js' }.fetch(preferred_region) end @@ -58,13 +58,13 @@ def source_required? true end - def authorize(amount, amazon_checkout, gateway_options={}) + def authorize(amount, amazon_transaction, gateway_options={}) return ActiveMerchant::Billing::Response.new(true, 'Success', {}) if amount < 0 load_amazon_pay params = { - chargePermissionId: amazon_checkout.order_reference, + chargePermissionId: amazon_transaction.order_reference, chargeAmount: { amount: (amount / 100.0).to_s, currencyCode: gateway_options[:currency] @@ -76,17 +76,17 @@ def authorize(amount, amazon_checkout, gateway_options={}) response = AmazonPay::Charge.create(params) success = response.code.to_i == 200 || response.code.to_i == 201 + body = JSON.parse(response.body, symbolize_names: true) message = 'Success' soft_decline = false unless success - body = JSON.parse(response.body, symbolize_names: true) message = body[:message] soft_decline = body[:reasonCode] == 'SoftDeclined' end # Saving information in last amazon transaction for error flow in amazon controller - amazon_checkout.update!( + amazon_transaction.update!( success: success, message: message, capture_id: body[:chargeId], @@ -96,8 +96,11 @@ def authorize(amount, amazon_checkout, gateway_options={}) ActiveMerchant::Billing::Response.new(success, message, 'response' => response.body) end - def capture(amount, amazon_checkout, gateway_options={}) - return credit(amount.abs, nil, nil, gateway_options) if amount < 0 + def capture(amount, response_code, gateway_options={}) + return credit(amount.abs, response_code, gateway_options) if amount < 0 + + payment = Spree::Payment.find_by!(response_code: response_code) + amazon_transaction = payment.source load_amazon_pay @@ -106,10 +109,12 @@ def capture(amount, amazon_checkout, gateway_options={}) amount: (amount / 100.0).to_s, currencyCode: gateway_options[:currency] }, - softDescriptor: 'Store Name' + softDescriptor: Spree::Store.current.name } - response = AmazonPay::Charge.capture(amazon_checkout.capture_id, params) + capture_id = update_for_backwards_compatibility(amazon_transaction.capture_id) + + response = AmazonPay::Charge.capture(capture_id, params) success = response.code.to_i == 200 || response.code.to_i == 201 message = 'Success' @@ -122,68 +127,79 @@ def capture(amount, amazon_checkout, gateway_options={}) end # Saving information in last amazon transaction for error flow in amazon controller - amazon_checkout.update!( + amazon_transaction.update!( success: success, message: message, soft_decline: soft_decline, retry: !success ) - ActiveMerchant::Billing::Response.new(success, message, 'response' => response.body) + ActiveMerchant::Billing::Response.new(success, message) end - def purchase(amount, amazon_checkout, gateway_options={}) - #auth_result = authorize(amount, amazon_checkout, gateway_options) - #if auth_result.success? - capture(amount, amazon_checkout, gateway_options) - #else - # auth_result - #end + def purchase(amount, amazon_checkout, gateway_options = {}) + capture(amount, amazon_checkout.capture_id, gateway_options) end - def credit(amount, _response_code, gateway_options = {}) - payment = gateway_options[:originator].payment + def credit(amount, response_code, _gateway_options = {}) + payment = Spree::Payment.find_by!(response_code: response_code) amazon_transaction = payment.source - load_amazon_mws(amazon_transaction.order_reference) - mws_res = @mws.refund( - amazon_transaction.capture_id, - operation_unique_id(payment), - amount / 100.00, - payment.currency - ) + load_amazon_pay - response = SpreeAmazon::Response::Refund.new(mws_res) - ActiveMerchant::Billing::Response.new(true, "Success", response.parse, authorization: response.response_id) - end + capture_id = update_for_backwards_compatibility(amazon_transaction.capture_id) - def void(response_code, gateway_options) - order = Spree::Order.find_by(:number => gateway_options[:order_id].split("-")[0]) - load_amazon_mws(order.amazon_order_reference_id) - capture_id = order.amazon_transaction.capture_id + params = { + chargeId: capture_id, + refundAmount: { + amount: (amount / 100.0).to_s, + currencyCode: payment.currency + }, + softDescriptor: Spree::Store.current.name + } - if capture_id.nil? - response = @mws.cancel - else - response = @mws.refund(capture_id, gateway_options[:order_id], order.order_total_after_store_credit, order.currency) + response = AmazonPay::Refund.create(params) + + success = response.code.to_i == 200 || response.code.to_i == 201 + body = JSON.parse(response.body, symbolize_names: true) + message = 'Success' + + unless success + message = body[:message] end - ActiveMerchant::Billing::Response.new(true, "Success", Hash.from_xml(response.body)) + ActiveMerchant::Billing::Response.new(success, message, + authorization: body[:refundId]) + end + + def void(response_code, gateway_options = {}) + cancel(response_code || extract_order_and_payment_number(gateway_options[:order_id]).second) end def cancel(response_code) payment = Spree::Payment.find_by!(response_code: response_code) - order = payment.order - load_amazon_mws(payment.source.order_reference) - capture_id = order.amazon_transaction.capture_id + amazon_transaction = payment.source + + if amazon_transaction.capture_id.nil? + + params = { + cancellationReason: 'Cancelled Order' + } + + response = AmazonPay::Charge.cancel(amazon_transaction.order_reference, params) + + success = response.code.to_i == 200 || response.code.to_i == 201 + body = JSON.parse(response.body, symbolize_names: true) + message = 'Success' + + unless success + message = body[:message] + end - if capture_id.nil? - response = @mws.cancel + ActiveMerchant::Billing::Response.new(success, message) else - response = @mws.refund(capture_id, order.number, payment.credit_allowed, payment.currency) + credit(payment.credit_allowed * 100, response_code) end - - ActiveMerchant::Billing::Response.new(true, "#{order.number}-cancel", Hash.from_xml(response.body)) end private @@ -195,22 +211,12 @@ def load_amazon_pay AmazonPay.private_key = preferred_private_key_file_location end - def extract_order_and_payment_number(gateway_options) - gateway_options[:order_id].split('-', 2) - end - - # Amazon requires unique ids. Calling with the same id multiple times means - # the result of the previous call will be returned again. This can be good - # for things like asynchronous retries, but would break things like multiple - # captures on a single authorization. - def operation_unique_id(payment) - "#{payment.number}-#{random_suffix}" - end - - # A random string of lowercase alphanumeric characters (i.e. "base 36") - def random_suffix - length = 10 - SecureRandom.random_number(36 ** length).to_s(36).rjust(length, '0') + def update_for_backwards_compatibility(capture_id) + if capture_id[20] == 'A' + capture_id[20, 1] = 'C' + else + capture_id + end end # Allows simulating errors in sandbox mode if the *last* name of the diff --git a/lib/amazon/charge_permission.rb b/lib/amazon/charge_permission.rb index 0ce29af..4125368 100644 --- a/lib/amazon/charge_permission.rb +++ b/lib/amazon/charge_permission.rb @@ -1,18 +1,15 @@ module AmazonPay class ChargePermission def self.get(charge_permission_id) - response = AmazonPay.request('get', "chargePermissions/#{charge_permission_id}") - response.body + AmazonPay.request('get', "chargePermissions/#{charge_permission_id}") end def self.update(charge_permission_id, params) - response = AmazonPay.request('patch', "chargePermissions/#{charge_permission_id}", params) - response.body + AmazonPay.request('patch', "chargePermissions/#{charge_permission_id}", params) end def self.close(charge_permission_id, params) - response = AmazonPay.request('delete', "chargePermissions/#{charge_permission_id}/close", params) - response.body + AmazonPay.request('delete', "chargePermissions/#{charge_permission_id}/close", params) end end end diff --git a/lib/amazon/refund.rb b/lib/amazon/refund.rb index 1111949..94be018 100644 --- a/lib/amazon/refund.rb +++ b/lib/amazon/refund.rb @@ -1,13 +1,11 @@ module AmazonPay class Refund def self.create(params) - response = AmazonPay.request('post', 'refunds', params) - response.body + AmazonPay.request('post', 'refunds', params) end def self.get(refund_id) - response = AmazonPay.request('get', "refunds/#{refund_id}") - response.body + AmazonPay.request('get', "refunds/#{refund_id}") end end end From cd05a9804b9bc5465240acef8af4d242f17fe08a Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 10 Nov 2019 22:18:00 -0500 Subject: [PATCH 04/39] Refactoring and streamlining functions --- app/models/spree/gateway/amazon.rb | 47 ++++++++++-------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 51835e8..a20e740 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -77,13 +77,8 @@ def authorize(amount, amazon_transaction, gateway_options={}) success = response.code.to_i == 200 || response.code.to_i == 201 body = JSON.parse(response.body, symbolize_names: true) - message = 'Success' - soft_decline = false - - unless success - message = body[:message] - soft_decline = body[:reasonCode] == 'SoftDeclined' - end + message = success ? 'Success' : body[:message][0...255] + soft_decline = success ? nil : body[:reasonCode] == 'SoftDeclined' # Saving information in last amazon transaction for error flow in amazon controller amazon_transaction.update!( @@ -93,7 +88,7 @@ def authorize(amount, amazon_transaction, gateway_options={}) soft_decline: soft_decline, retry: !success ) - ActiveMerchant::Billing::Response.new(success, message, 'response' => response.body) + ActiveMerchant::Billing::Response.new(success, message, body) end def capture(amount, response_code, gateway_options={}) @@ -117,14 +112,9 @@ def capture(amount, response_code, gateway_options={}) response = AmazonPay::Charge.capture(capture_id, params) success = response.code.to_i == 200 || response.code.to_i == 201 - message = 'Success' - soft_decline = false - - unless success - body = JSON.parse(response.body, symbolize_names: true) - message = body[:message] - soft_decline = body[:reasonCode] == 'SoftDeclined' - end + body = JSON.parse(response.body, symbolize_names: true) + message = success ? 'Success' : body[:message][0...255] + soft_decline = success ? nil : body[:reasonCode] == 'SoftDeclined' # Saving information in last amazon transaction for error flow in amazon controller amazon_transaction.update!( @@ -137,8 +127,8 @@ def capture(amount, response_code, gateway_options={}) ActiveMerchant::Billing::Response.new(success, message) end - def purchase(amount, amazon_checkout, gateway_options = {}) - capture(amount, amazon_checkout.capture_id, gateway_options) + def purchase(amount, amazon_transaction, gateway_options = {}) + capture(amount, amazon_transaction.capture_id, gateway_options) end def credit(amount, response_code, _gateway_options = {}) @@ -162,18 +152,14 @@ def credit(amount, response_code, _gateway_options = {}) success = response.code.to_i == 200 || response.code.to_i == 201 body = JSON.parse(response.body, symbolize_names: true) - message = 'Success' - - unless success - message = body[:message] - end + message = success ? 'Success' : body[:message][0...255] - ActiveMerchant::Billing::Response.new(success, message, + ActiveMerchant::Billing::Response.new(success, message, body, authorization: body[:refundId]) end - def void(response_code, gateway_options = {}) - cancel(response_code || extract_order_and_payment_number(gateway_options[:order_id]).second) + def void(response_code, _gateway_options = {}) + cancel(response_code) end def cancel(response_code) @@ -186,15 +172,12 @@ def cancel(response_code) cancellationReason: 'Cancelled Order' } - response = AmazonPay::Charge.cancel(amazon_transaction.order_reference, params) + response = AmazonPay::Charge.cancel(amazon_transaction.order_reference, + params) success = response.code.to_i == 200 || response.code.to_i == 201 body = JSON.parse(response.body, symbolize_names: true) - message = 'Success' - - unless success - message = body[:message] - end + message = success ? 'Success' : body[:message][0...255] ActiveMerchant::Billing::Response.new(success, message) else From e56052503fd8ab86cb19c7f8985c65ba71a28193 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 10 Nov 2019 22:57:04 -0500 Subject: [PATCH 05/39] Update Complete Flow to check for order state --- app/controllers/spree/amazonpay_controller.rb | 7 ++++ app/models/spree/gateway/amazon.rb | 42 +------------------ 2 files changed, 8 insertions(+), 41 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 066cd9c..4fe3190 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -86,6 +86,13 @@ def payment def complete response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) + status_detail = response[:statusDetail] + + unless status_detail[:state] == 'Completed' + redirect_to cart_path, notice: status_detail[:reasonDescription] + return + end + @order = current_order payments = @order.payments diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index a20e740..c05002b 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -195,47 +195,7 @@ def load_amazon_pay end def update_for_backwards_compatibility(capture_id) - if capture_id[20] == 'A' - capture_id[20, 1] = 'C' - else - capture_id - end - end - - # Allows simulating errors in sandbox mode if the *last* name of the - # shipping address is "SandboxSimulation" and the *first* name is one of: - # - # InvalidPaymentMethodHard- (- is optional. between 1-240.) - # InvalidPaymentMethodSoft- (- is optional. between 1-240.) - # AmazonRejected - # TransactionTimedOut - # ExpiredUnused- (- is optional. between 1-60.) - # AmazonClosed - # - # E.g. a full name like: "AmazonRejected SandboxSimulation" - # - # See https://payments.amazon.com/documentation/lpwa/201956480 for more - # details on Amazon Payments Sandbox Simulations. - def sandbox_authorize_simulation_string(order) - return nil if !preferred_test_mode - return nil if order.ship_address.nil? - return nil if order.ship_address.lastname != 'SandboxSimulation' - - reason, minutes = order.ship_address.firstname.to_s.split('-', 2) - # minutes is optional and is only used for some of the reason codes - minutes ||= '1' - - case reason - when 'InvalidPaymentMethodHard' then %({"SandboxSimulation": {"State":"Declined", "ReasonCode":"InvalidPaymentMethod", "PaymentMethodUpdateTimeInMins":#{minutes}}}) - when 'InvalidPaymentMethodSoft' then %({"SandboxSimulation": {"State":"Declined", "ReasonCode":"InvalidPayment Method", "PaymentMethodUpdateTimeInMins":#{minutes}, "SoftDecline":"true"}}) - when 'AmazonRejected' then '{"SandboxSimulation": {"State":"Declined", "ReasonCode":"AmazonRejected"}}' - when 'TransactionTimedOut' then '{"SandboxSimulation": {"State":"Declined", "ReasonCode":"TransactionTimedOut"}}' - when 'ExpiredUnused' then %({"SandboxSimulation": {"State":"Closed", "ReasonCode":"ExpiredUnused", "ExpirationTimeInMins":#{minutes}}}) - when 'AmazonClosed' then '{"SandboxSimulation": {"State":"Closed", "ReasonCode":"AmazonClosed"}}' - else - Rails.logger.error('"SandboxSimulation" was given as the shipping first name but the last name was not a valid reason code: ' + order.ship_address.firstname.inspect) - nil - end + capture_id[20] == 'A' ? capture_id[20, 1] = 'C' : capture_id end end end From f0b0aa4d9a7eb7abbc8d95e346a15093fded3ecd Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 10 Nov 2019 23:09:37 -0500 Subject: [PATCH 06/39] check if null webcheckout --- app/controllers/spree/amazonpay_controller.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 4fe3190..28f0a48 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -80,7 +80,14 @@ def payment } response = AmazonPay::CheckoutSession.update(amazon_checkout_session_id, params) - redirect_to response[:webCheckoutDetail][:amazonPayRedirectUrl] + + web_checkout_detail = response[:webCheckoutDetail] + + if web_checkout_detail + redirect_to web_checkout_detail[:amazonPayRedirectUrl] + else + redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) + end end def complete From b44df8bb7f63e423734a29d59bf5b6a7f60e7d00 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 11 Nov 2019 15:07:20 -0500 Subject: [PATCH 07/39] Updating logic to account for delayed capture --- app/controllers/spree/amazonpay_controller.rb | 2 +- app/models/spree/gateway/amazon.rb | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 28f0a48..ee1f994 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -65,7 +65,7 @@ def payment }, paymentDetail: { paymentIntent: 'Authorize', - canHandlePendingAuthorization: true, + canHandlePendingAuthorization: false, chargeAmount: { amount: current_order.order_total_after_store_credit, currencyCode: current_currency diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index c05002b..1e4b5db 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -61,6 +61,12 @@ def source_required? def authorize(amount, amazon_transaction, gateway_options={}) return ActiveMerchant::Billing::Response.new(true, 'Success', {}) if amount < 0 + # If there already is a capture_id available then we don't need to create + # a charage and can immediately capture + if amazon_transaction.try(:capture_id) + return capture(amount, amazon_transaction.capture_id, gateway_options) + end + load_amazon_pay params = { @@ -69,8 +75,8 @@ def authorize(amount, amazon_transaction, gateway_options={}) amount: (amount / 100.0).to_s, currencyCode: gateway_options[:currency] }, - captureNow: true, - canHandlePendingAuthorization: true + captureNow: false, + canHandlePendingAuthorization: false } response = AmazonPay::Charge.create(params) @@ -104,7 +110,7 @@ def capture(amount, response_code, gateway_options={}) amount: (amount / 100.0).to_s, currencyCode: gateway_options[:currency] }, - softDescriptor: Spree::Store.current.name + softDescriptor: Spree::Store.current.try(:name) } capture_id = update_for_backwards_compatibility(amazon_transaction.capture_id) @@ -145,7 +151,7 @@ def credit(amount, response_code, _gateway_options = {}) amount: (amount / 100.0).to_s, currencyCode: payment.currency }, - softDescriptor: Spree::Store.current.name + softDescriptor: Spree::Store.current.try(:name) } response = AmazonPay::Refund.create(params) @@ -166,6 +172,8 @@ def cancel(response_code) payment = Spree::Payment.find_by!(response_code: response_code) amazon_transaction = payment.source + load_amazon_pay + if amazon_transaction.capture_id.nil? params = { From 2e8102f432ad8b7911f120cb55f8cbdb50fee137 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 11 Nov 2019 15:30:37 -0500 Subject: [PATCH 08/39] Updating IPN to use V2 JSON --- .../spree/amazon_callback_controller.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/controllers/spree/amazon_callback_controller.rb b/app/controllers/spree/amazon_callback_controller.rb index 6c9a5c4..0ed609c 100644 --- a/app/controllers/spree/amazon_callback_controller.rb +++ b/app/controllers/spree/amazon_callback_controller.rb @@ -10,13 +10,24 @@ class Spree::AmazonCallbackController < ApplicationController skip_before_action :verify_authenticity_token + # This is the body that is sent from Amazon's IPN + # + # { + # "merchantId": "Relevant Merchant for the notification", + # "objectType": "one of: Charge, Refund", + # "objectId": "Id of relevant object", + # "notificationType": "STATE_CHANGE", + # "notificationId": "Randomly generated Id, used for tracking only", + # "notificationVersion": "V1" + # } + def new - response = JSON.parse(request.body.read) - if JSON.parse(response["Message"])["NotificationType"] == "PaymentRefund" - refund_id = Hash.from_xml(JSON.parse(response["Message"])[ "NotificationData"])["RefundNotification"]["RefundDetails"]["AmazonRefundId"] + response = JSON.parse(response.body, symbolize_names: true) + if response[:objectType] == 'Refund' + refund_id = response[:objectId] payment = Spree::LogEntry.where('details LIKE ?', "%#{refund_id}%").last.try(:source) if payment - l = payment.log_entries.build(details: response.to_yaml) + l = payment.log_entries.build(details: response) l.save end end From 9112d4f13f09e261274b5f5225fb620f1cc5078f Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 11 Nov 2019 15:52:18 -0500 Subject: [PATCH 09/39] Updating Close and Fixing Logic --- app/controllers/spree/amazonpay_controller.rb | 10 ++++---- app/models/spree/amazon_transaction.rb | 23 +++++++++++-------- app/models/spree/gateway/amazon.rb | 4 ++-- lib/amazon_pay.rb | 3 ++- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index ee1f994..33eb346 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -25,7 +25,7 @@ def create params = { webCheckoutDetail: { checkoutReviewReturnUrl: 'http://localhost:3000/amazonpay/confirm' }, - storeId: @gateway.preferred_client_id } + storeId: gateway.preferred_client_id } render json: AmazonPay::CheckoutSession.create(params) end @@ -104,7 +104,7 @@ def complete payments = @order.payments payment = payments.create - payment.payment_method = @gateway + payment.payment_method = gateway payment.source ||= Spree::AmazonTransaction.create( order_reference: response[:chargePermissionId], order_id: @order.id, @@ -140,10 +140,8 @@ def complete def gateway @gateway ||= Spree::Gateway::Amazon.for_currency(current_order.currency) - AmazonPay.region = @gateway.preferred_region - AmazonPay.public_key_id = @gateway.preferred_public_key_id - AmazonPay.sandbox = @gateway.preferred_test_mode - AmazonPay.private_key = @gateway.preferred_private_key_file_location + @gateway.load_amazon_pay + @gateway end private diff --git a/app/models/spree/amazon_transaction.rb b/app/models/spree/amazon_transaction.rb index 403de49..7220ca0 100644 --- a/app/models/spree/amazon_transaction.rb +++ b/app/models/spree/amazon_transaction.rb @@ -50,29 +50,32 @@ def close!(payment) params = { closureReason: 'No more charges required', - cancelPendingCharges: false + cancelPendingCharges: true } - response = AmazonPay::ChargePermisson.close(order_reference, params) + payment.payment_method.load_amazon_pay - if response.success? - update_attributes(closed_at: DateTime.now) + response = AmazonPay::ChargePermission.close(order_reference, params) + + success = response.code.to_i == 200 || response.code.to_i == 201 + body = JSON.parse(response.body, symbolize_names: true) + + if success + update_attributes(closed_at: Time.current) else - raise Spree::Core::GatewayError.new(text) - #gateway_error(response) + gateway_error(body) end end private def gateway_error(error) - text = error.params['message'] || error.params['response_reason_text'] || error.message + text = error[:message][0...255] || error[:reasonCode] logger.error(Spree.t(:gateway_error)) - logger.error(" #{error.to_yaml}") + logger.error(" #{error}") - raise Spree::Core::GatewayError.new(text) + raise Spree::Core::GatewayError, text end - end end diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 1e4b5db..34a968d 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -193,8 +193,6 @@ def cancel(response_code) end end - private - def load_amazon_pay AmazonPay.region = preferred_region AmazonPay.public_key_id = preferred_public_key_id @@ -202,6 +200,8 @@ def load_amazon_pay AmazonPay.private_key = preferred_private_key_file_location end + private + def update_for_backwards_compatibility(capture_id) capture_id[20] == 'A' ? capture_id[20, 1] = 'C' : capture_id end diff --git a/lib/amazon_pay.rb b/lib/amazon_pay.rb index 7b622ca..7ad76ae 100644 --- a/lib/amazon_pay.rb +++ b/lib/amazon_pay.rb @@ -43,7 +43,8 @@ def self.request(method_type, url, body = nil) 'post' => Net::HTTP::Post, 'get' => Net::HTTP::Get, 'put' => Net::HTTP::Put, - 'patch' => Net::HTTP::Patch + 'patch' => Net::HTTP::Patch, + 'delete' => Net::HTTP::Delete } url = base_api_url + url From 14da6bf90de7cadb1460cbf247801fb108159b1f Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 11 Nov 2019 20:32:17 -0500 Subject: [PATCH 10/39] Removed unused code --- lib/amazon_pay.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/amazon_pay.rb b/lib/amazon_pay.rb index 7ad76ae..c241600 100644 --- a/lib/amazon_pay.rb +++ b/lib/amazon_pay.rb @@ -15,7 +15,6 @@ module AmazonPay @@public_key_id = nil @@region = nil @@sandbox = 'true' - @@store_id = nil @@private_key = 'private.pem' def self.region=(region) @@ -34,10 +33,6 @@ def self.private_key=(private_key) @@private_key = private_key end - def self.store_id=(store_id) - @@store_id = store_id - end - def self.request(method_type, url, body = nil) method_types = { 'post' => Net::HTTP::Post, From 6c0e666ebb52e4620c1968ac5c4994fd8d84dad1 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 12 Nov 2019 14:56:31 -0500 Subject: [PATCH 11/39] Clean up & Generalize Reponses --- app/controllers/spree/amazonpay_controller.rb | 31 +++-- app/models/spree/gateway/amazon.rb | 32 +++--- app/models/spree_amazon/response.rb | 106 ------------------ lib/amazon/checkout_session.rb | 9 +- lib/amazon/response.rb | 28 +++++ lib/amazon_pay.rb | 3 +- 6 files changed, 68 insertions(+), 141 deletions(-) delete mode 100644 app/models/spree_amazon/response.rb create mode 100644 lib/amazon/response.rb diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 33eb346..fe0d4af 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -27,7 +27,9 @@ def create { checkoutReviewReturnUrl: 'http://localhost:3000/amazonpay/confirm' }, storeId: gateway.preferred_client_id } - render json: AmazonPay::CheckoutSession.create(params) + response = AmazonPay::CheckoutSession.create(params) + + render json: response.body end def confirm @@ -35,10 +37,15 @@ def confirm response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) - amazon_user = SpreeAmazon::User.from_response(response) + unless response.success? + redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) + return + end + + amazon_user = SpreeAmazon::User.from_response(response.body) set_user_information(amazon_user.auth_hash) - amazon_address = SpreeAmazon::Address.from_response(response) + amazon_address = SpreeAmazon::Address.from_response(response.body) address_attributes = amazon_address.attributes if spree_address_book_available? @@ -81,9 +88,9 @@ def payment response = AmazonPay::CheckoutSession.update(amazon_checkout_session_id, params) - web_checkout_detail = response[:webCheckoutDetail] + web_checkout_detail = response.body[:webCheckoutDetail] - if web_checkout_detail + if web_checkout_detail && response.success? redirect_to web_checkout_detail[:amazonPayRedirectUrl] else redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) @@ -93,7 +100,13 @@ def payment def complete response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) - status_detail = response[:statusDetail] + unless response.success? + redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) + return + end + + body = response.body + status_detail = body[:statusDetail] unless status_detail[:state] == 'Completed' redirect_to cart_path, notice: status_detail[:reasonDescription] @@ -106,13 +119,13 @@ def complete payment = payments.create payment.payment_method = gateway payment.source ||= Spree::AmazonTransaction.create( - order_reference: response[:chargePermissionId], + order_reference: body[:chargePermissionId], order_id: @order.id, - capture_id: response[:chargeId], + capture_id: body[:chargeId], retry: false ) payment.amount = @order.order_total_after_store_credit - payment.response_code = response[:chargeId] + payment.response_code = body[:chargeId] payment.save! @order.reload diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 34a968d..32ae9aa 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -81,20 +81,20 @@ def authorize(amount, amazon_transaction, gateway_options={}) response = AmazonPay::Charge.create(params) - success = response.code.to_i == 200 || response.code.to_i == 201 - body = JSON.parse(response.body, symbolize_names: true) - message = success ? 'Success' : body[:message][0...255] + success = response.success? + message = response.message[0..255] soft_decline = success ? nil : body[:reasonCode] == 'SoftDeclined' # Saving information in last amazon transaction for error flow in amazon controller amazon_transaction.update!( success: success, message: message, - capture_id: body[:chargeId], + capture_id: response.body[:chargeId], soft_decline: soft_decline, retry: !success ) - ActiveMerchant::Billing::Response.new(success, message, body) + + ActiveMerchant::Billing::Response.new(success, message, response.body) end def capture(amount, response_code, gateway_options={}) @@ -117,9 +117,8 @@ def capture(amount, response_code, gateway_options={}) response = AmazonPay::Charge.capture(capture_id, params) - success = response.code.to_i == 200 || response.code.to_i == 201 - body = JSON.parse(response.body, symbolize_names: true) - message = success ? 'Success' : body[:message][0...255] + success = response.success? + message = response.message[0..255] soft_decline = success ? nil : body[:reasonCode] == 'SoftDeclined' # Saving information in last amazon transaction for error flow in amazon controller @@ -156,12 +155,10 @@ def credit(amount, response_code, _gateway_options = {}) response = AmazonPay::Refund.create(params) - success = response.code.to_i == 200 || response.code.to_i == 201 - body = JSON.parse(response.body, symbolize_names: true) - message = success ? 'Success' : body[:message][0...255] - - ActiveMerchant::Billing::Response.new(success, message, body, - authorization: body[:refundId]) + ActiveMerchant::Billing::Response.new(response.success?, + response.message[0...255], + response.body, + authorization: response.body[:refundId]) end def void(response_code, _gateway_options = {}) @@ -183,11 +180,8 @@ def cancel(response_code) response = AmazonPay::Charge.cancel(amazon_transaction.order_reference, params) - success = response.code.to_i == 200 || response.code.to_i == 201 - body = JSON.parse(response.body, symbolize_names: true) - message = success ? 'Success' : body[:message][0...255] - - ActiveMerchant::Billing::Response.new(success, message) + ActiveMerchant::Billing::Response.new(response.success?, + response.message[0...255]) else credit(payment.credit_allowed * 100, response_code) end diff --git a/app/models/spree_amazon/response.rb b/app/models/spree_amazon/response.rb deleted file mode 100644 index 78a45e9..0000000 --- a/app/models/spree_amazon/response.rb +++ /dev/null @@ -1,106 +0,0 @@ -class SpreeAmazon::Response - attr_reader :type, :response - - def initialize(response) - @type = self.class.name.demodulize - @response = response - end - - def fetch(path, element) - response.get_element(path, element) - end - - def response_details - "#{@type}Response/#{@type}Result/#{@type}Details" - end - - def response_id - fetch("#{response_details}", "Amazon#{@type}Id") - end - - def reference_id - fetch("#{response_details}", "#{@type}ReferenceId") - end - - def amount - fetch("#{response_details}/#{@type}Amount", "Amount") - end - - def currency_code - fetch("#{response_details}/#{@type}Amount", "CurrencyCode") - end - - def state - fetch("#{response_details}/#{@type}Status", "State") - end - - def success_state? - %w{Pending Draft Open Completed}.include?(state) - end - - def success? - response.success - end - - def reason_code - return nil if success_state? - - fetch("#{response_details}/#{@type}Status", "ReasonCode") - end - - def response_code - response.code - end - - def error_code - return nil if success? - - fetch("ErrorResponse/Error", "Code") - end - - def error_message - return nil if success? - - fetch("ErrorResponse/Error", "Message") - end - - def error_response_present? - !parse["ErrorResponse"].nil? - end - - def body - response.body - end - - def parse - Hash.from_xml(body) - end - - class Authorization < SpreeAmazon::Response - def response_details - "AuthorizeResponse/AuthorizeResult/AuthorizationDetails" - end - - def soft_decline? - fetch(response_details, 'SoftDecline').to_s != 'false' - end - end - - class Capture < SpreeAmazon::Response - end - - class Refund < SpreeAmazon::Response - end - - class SetOrderReferenceDetails < SpreeAmazon::Response - - def response_details - "SetOrderReferenceDetailsResponse/SetOrderReferenceDetailsResult/OrderReferenceDetails" - end - - def constraints - fetch("#{response_details}/Constraints/Constraint" , 'Description') - end - - end -end diff --git a/lib/amazon/checkout_session.rb b/lib/amazon/checkout_session.rb index b6826c4..f472c29 100644 --- a/lib/amazon/checkout_session.rb +++ b/lib/amazon/checkout_session.rb @@ -1,18 +1,15 @@ module AmazonPay class CheckoutSession def self.create(params) - response = AmazonPay.request('post', 'checkoutSessions', params) - response.body + AmazonPay.request('post', 'checkoutSessions', params) end def self.get(checkout_session_id) - response = AmazonPay.request('get', "checkoutSessions/#{checkout_session_id}") - JSON.parse(response.body, symbolize_names: true) + AmazonPay.request('get', "checkoutSessions/#{checkout_session_id}") end def self.update(checkout_session_id, params) - response = AmazonPay.request('patch', "checkoutSessions/#{checkout_session_id}", params) - JSON.parse(response.body, symbolize_names: true) + AmazonPay.request('patch', "checkoutSessions/#{checkout_session_id}", params) end end end diff --git a/lib/amazon/response.rb b/lib/amazon/response.rb new file mode 100644 index 0000000..06bda01 --- /dev/null +++ b/lib/amazon/response.rb @@ -0,0 +1,28 @@ +module AmazonPay + class Response + attr_reader :type, :response, :body + + def initialize(response) + @type = self.class.name.demodulize + @response = response + @body = JSON.parse(response.body, symbolize_names: true) + end + + def success? + response_code == 200 || response_code == 201 + end + + def response_code + response.code.to_i + end + + def reason_code + body[:reasonCode] + end + + def message + return 'Success' if success? + body[:message] + end + end +end diff --git a/lib/amazon_pay.rb b/lib/amazon_pay.rb index c241600..bcf5bd1 100644 --- a/lib/amazon_pay.rb +++ b/lib/amazon_pay.rb @@ -8,6 +8,7 @@ require 'amazon/charge' require 'amazon/checkout_session' require 'amazon/refund' +require 'amazon/response' module AmazonPay @@amazon_signature_algorithm = 'AMZN-PAY-RSASSA-PSS'.freeze @@ -64,7 +65,7 @@ def self.request(method_type, url, body = nil) http.request(request) end - response + AmazonPay::Response.new response end def self.signed_headers(http_request_method, request_uri, request_parameters, request_payload, other_presigned_headers = nil) From 250c265f779fe929b4fe874c858f2295ac726818 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 12 Nov 2019 15:16:21 -0500 Subject: [PATCH 12/39] Fixing Address Creation issues --- app/controllers/spree/amazonpay_controller.rb | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index fe0d4af..ee2b7d9 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -45,6 +45,10 @@ def confirm amazon_user = SpreeAmazon::User.from_response(response.body) set_user_information(amazon_user.auth_hash) + if spree_current_user.nil? + current_order.update_attributes!(email: amazon_user.email) + end + amazon_address = SpreeAmazon::Address.from_response(response.body) address_attributes = amazon_address.attributes @@ -52,9 +56,7 @@ def confirm address_attributes = address_attributes.merge(user: spree_current_user) end - current_order.update_attributes!(bill_address_attributes: address_attributes, - ship_address_attributes: address_attributes, - email: amazon_user.email) + update_current_order_address!(address_attributes) current_order.next! @@ -196,6 +198,32 @@ def check_current_order end end + def update_current_order_address!(address_attributes, spree_user_address = nil) + bill_address = current_order.bill_address + ship_address = current_order.ship_address + + new_address = Spree::Address.new address_attributes + if spree_address_book_available? + user_address = spree_current_user.addresses.find do |address| + address.same_as?(new_address) + end + + if user_address + current_order.update_column(:ship_address_id, user_address.id) + else + new_address.save! + current_order.update_column(:ship_address_id, new_address.id) + end + else + if ship_address.nil? || ship_address.empty? + new_address.save! + current_order.update_column(:ship_address_id, new_address.id) + else + ship_address.update_attributes(address_attributes) + end + end + end + def spree_address_book_available? Gem::Specification.find_all_by_name('spree_address_book').any? end From 560907b9de74b7b34aba420d3d314bea28d669fe Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 12 Nov 2019 15:23:49 -0500 Subject: [PATCH 13/39] Fixing response logic on failures --- app/models/spree/amazon_transaction.rb | 7 ++----- app/models/spree/gateway/amazon.rb | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/models/spree/amazon_transaction.rb b/app/models/spree/amazon_transaction.rb index 7220ca0..e48ab23 100644 --- a/app/models/spree/amazon_transaction.rb +++ b/app/models/spree/amazon_transaction.rb @@ -57,13 +57,10 @@ def close!(payment) response = AmazonPay::ChargePermission.close(order_reference, params) - success = response.code.to_i == 200 || response.code.to_i == 201 - body = JSON.parse(response.body, symbolize_names: true) - - if success + if response.success? update_attributes(closed_at: Time.current) else - gateway_error(body) + gateway_error(response.body) end end diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 32ae9aa..10dcf90 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -83,7 +83,7 @@ def authorize(amount, amazon_transaction, gateway_options={}) success = response.success? message = response.message[0..255] - soft_decline = success ? nil : body[:reasonCode] == 'SoftDeclined' + soft_decline = success ? body[:reasonCode] == 'SoftDeclined' : nil # Saving information in last amazon transaction for error flow in amazon controller amazon_transaction.update!( @@ -119,7 +119,7 @@ def capture(amount, response_code, gateway_options={}) success = response.success? message = response.message[0..255] - soft_decline = success ? nil : body[:reasonCode] == 'SoftDeclined' + soft_decline = success ? body[:reasonCode] == 'SoftDeclined' : nil # Saving information in last amazon transaction for error flow in amazon controller amazon_transaction.update!( From 1509c5fe990868167c8e6b27ffb877d45976f136 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 12 Nov 2019 15:52:39 -0500 Subject: [PATCH 14/39] Updating URL Generation --- app/controllers/spree/amazonpay_controller.rb | 5 ++--- app/models/spree/gateway/amazon.rb | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index ee2b7d9..a78fa8c 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -24,7 +24,7 @@ def create end params = { webCheckoutDetail: - { checkoutReviewReturnUrl: 'http://localhost:3000/amazonpay/confirm' }, + { checkoutReviewReturnUrl: gateway.base_url(request.ssl?) + 'confirm' }, storeId: gateway.preferred_client_id } response = AmazonPay::CheckoutSession.create(params) @@ -70,7 +70,7 @@ def payment params = { webCheckoutDetail: { - checkoutResultReturnUrl: 'http://localhost:3000/amazonpay/complete' + checkoutResultReturnUrl: gateway.base_url(request.ssl?) + 'complete' }, paymentDetail: { paymentIntent: 'Authorize', @@ -199,7 +199,6 @@ def check_current_order end def update_current_order_address!(address_attributes, spree_user_address = nil) - bill_address = current_order.bill_address ship_address = current_order.ship_address new_address = Spree::Address.new address_attributes diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 10dcf90..3524735 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -19,6 +19,7 @@ class Gateway::Amazon < Gateway preference :region, :string, default: 'us' preference :site_domain, :string preference :public_key_id, :string + preference :use_ssl, :string preference :private_key_file_location, :string has_one :provider @@ -29,6 +30,10 @@ def self.for_currency(currency) where(active: true).detect { |gateway| gateway.preferred_currency == currency } end + def base_url(use_ssl) + "http#{use_ssl ? 's' : ''}://#{preferred_site_domain}/amazonpay/" + end + def widgets_url { 'us' => 'https://static-na.payments-amazon.com/checkout.js', From bde351d9df5a5aa84db1fd2cd670bbcbf6d795f3 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 12 Nov 2019 16:01:17 -0500 Subject: [PATCH 15/39] Fixing errors --- app/models/spree/gateway/amazon.rb | 10 +++++----- lib/amazon/response.rb | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 3524735..ee1aa5d 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -88,14 +88,13 @@ def authorize(amount, amazon_transaction, gateway_options={}) success = response.success? message = response.message[0..255] - soft_decline = success ? body[:reasonCode] == 'SoftDeclined' : nil # Saving information in last amazon transaction for error flow in amazon controller amazon_transaction.update!( success: success, message: message, capture_id: response.body[:chargeId], - soft_decline: soft_decline, + soft_decline: response.soft_decline?, retry: !success ) @@ -124,13 +123,12 @@ def capture(amount, response_code, gateway_options={}) success = response.success? message = response.message[0..255] - soft_decline = success ? body[:reasonCode] == 'SoftDeclined' : nil # Saving information in last amazon transaction for error flow in amazon controller amazon_transaction.update!( success: success, message: message, - soft_decline: soft_decline, + soft_decline: response.soft_decline?, retry: !success ) @@ -160,10 +158,12 @@ def credit(amount, response_code, _gateway_options = {}) response = AmazonPay::Refund.create(params) + authorization = response.success? ? response.body[:refundId] : nil + ActiveMerchant::Billing::Response.new(response.success?, response.message[0...255], response.body, - authorization: response.body[:refundId]) + authorization: authorization) end def void(response_code, _gateway_options = {}) diff --git a/lib/amazon/response.rb b/lib/amazon/response.rb index 06bda01..56f4c31 100644 --- a/lib/amazon/response.rb +++ b/lib/amazon/response.rb @@ -17,9 +17,15 @@ def response_code end def reason_code + return nil if success? body[:reasonCode] end + def soft_decline? + return nil if success? + reason_code == 'SoftDeclined' + end + def message return 'Success' if success? body[:message] From 048e7862002a415ad1a63daa8bc2dcbbbc08b553 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 12 Nov 2019 17:03:08 -0500 Subject: [PATCH 16/39] Fix multiple payments issue --- app/controllers/spree/amazonpay_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index a78fa8c..af43924 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -58,6 +58,7 @@ def confirm update_current_order_address!(address_attributes) + current_order.unprocessed_payments.map(&:invalidate!) current_order.next! if current_order.shipments.empty? @@ -90,9 +91,8 @@ def payment response = AmazonPay::CheckoutSession.update(amazon_checkout_session_id, params) - web_checkout_detail = response.body[:webCheckoutDetail] - - if web_checkout_detail && response.success? + if response.success? + web_checkout_detail = response.body[:webCheckoutDetail] redirect_to web_checkout_detail[:amazonPayRedirectUrl] else redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) From 1222defedf922aa8e19a0dbf5c31f81bf8279b88 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 12 Nov 2019 21:41:55 -0500 Subject: [PATCH 17/39] Adding Address Restrictions --- app/controllers/spree/amazonpay_controller.rb | 11 ++++++++++- app/models/spree/gateway/amazon.rb | 1 - config/locales/en.yml | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index af43924..59ff6fb 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -56,13 +56,17 @@ def confirm address_attributes = address_attributes.merge(user: spree_current_user) end + if address_restrictions(amazon_address) + redirect_to cart_path, notice: Spree.t(:cannot_ship_to_address) && return + end + update_current_order_address!(address_attributes) current_order.unprocessed_payments.map(&:invalidate!) current_order.next! if current_order.shipments.empty? - redirect_to cart_path, notice: 'Cannot ship to this address' + redirect_to cart_path, notice: Spree.t(:cannot_ship_to_address) end end @@ -165,6 +169,11 @@ def amazon_checkout_session_id params[:amazonCheckoutSessionId] end + # Override this function if you need to restrict shipping locations + def address_restrictions(amazon_address) + amazon_address.nil? + end + def set_user_information(auth_hash) return unless Gem::Specification.find_all_by_name('spree_social').any? && auth_hash diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index ee1aa5d..7d86dea 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -19,7 +19,6 @@ class Gateway::Amazon < Gateway preference :region, :string, default: 'us' preference :site_domain, :string preference :public_key_id, :string - preference :use_ssl, :string preference :private_key_file_location, :string has_one :provider diff --git a/config/locales/en.yml b/config/locales/en.yml index feaa06f..0c600b3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -17,5 +17,8 @@ en: merchant_id: Merchant ID aws_access_key_id: MWS Access Key ID aws_secret_access_key: MWS Secret Access Key + public_key_id: Public Key ID + private_key_file_location: Private Key File Location order_reference_id: Order Reference ID order_processed_unsuccessfully: Your payment could not be processed. Please try to place the order again using another payment method. + cannot_ship_to_address: Cannot ship to this address. From 868b89c9ce1958500541b6f3ffffd389a1326622 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 12 Nov 2019 22:09:01 -0500 Subject: [PATCH 18/39] Fixing returns --- app/controllers/spree/amazonpay_controller.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 59ff6fb..e6a7eab 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -33,7 +33,10 @@ def create end def confirm - redirect_to cart_path && return unless current_order.address? + unless current_order.address? + redirect_to cart_path + return + end response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) @@ -57,7 +60,8 @@ def confirm end if address_restrictions(amazon_address) - redirect_to cart_path, notice: Spree.t(:cannot_ship_to_address) && return + redirect_to cart_path, notice: Spree.t(:cannot_ship_to_address) + return end update_current_order_address!(address_attributes) From af546677a9ad5eeac052e832f3db618814e01122 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Wed, 13 Nov 2019 03:21:40 -0500 Subject: [PATCH 19/39] Adding Change of Payment / Address --- app/controllers/spree/amazonpay_controller.rb | 4 +- .../add_buttons_to_delivery.html.erb.deface | 4 - app/views/spree/amazonpay/_change.html.erb | 6 - app/views/spree/amazonpay/_order_details.erb | 131 ++++++++++++++++++ app/views/spree/amazonpay/complete.html.erb | 30 ---- app/views/spree/amazonpay/confirm.html.erb | 13 +- app/views/spree/amazonpay/delivery.html.erb | 11 -- 7 files changed, 145 insertions(+), 54 deletions(-) delete mode 100644 app/overrides/spree/checkout/_delivery/add_buttons_to_delivery.html.erb.deface delete mode 100644 app/views/spree/amazonpay/_change.html.erb create mode 100644 app/views/spree/amazonpay/_order_details.erb delete mode 100644 app/views/spree/amazonpay/complete.html.erb delete mode 100644 app/views/spree/amazonpay/delivery.html.erb diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index e6a7eab..8d2cf75 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -34,8 +34,8 @@ def create def confirm unless current_order.address? - redirect_to cart_path - return + current_order.state = 'address' + current_order.save! end response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) diff --git a/app/overrides/spree/checkout/_delivery/add_buttons_to_delivery.html.erb.deface b/app/overrides/spree/checkout/_delivery/add_buttons_to_delivery.html.erb.deface deleted file mode 100644 index a62fbf3..0000000 --- a/app/overrides/spree/checkout/_delivery/add_buttons_to_delivery.html.erb.deface +++ /dev/null @@ -1,4 +0,0 @@ - -
- <%= submit_tag Spree.t(:place_order), :class => 'btn btn-lg btn-success' %> -
diff --git a/app/views/spree/amazonpay/_change.html.erb b/app/views/spree/amazonpay/_change.html.erb deleted file mode 100644 index d573487..0000000 --- a/app/views/spree/amazonpay/_change.html.erb +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/app/views/spree/amazonpay/_order_details.erb b/app/views/spree/amazonpay/_order_details.erb new file mode 100644 index 0000000..2802456 --- /dev/null +++ b/app/views/spree/amazonpay/_order_details.erb @@ -0,0 +1,131 @@ +
+ <% if order.has_step?("address") %> + <% if order.has_step?("delivery") %> +
+

<%= Spree.t(:shipping_address) %>

+ <%= render 'spree/shared/address', address: order.ship_address %> +
+ +
+

<%= Spree.t(:shipments) %> <%= link_to "(#{Spree.t(:edit)})", checkout_state_path(:delivery) unless order.completed? %>

+
+ <% order.shipments.each do |shipment| %> +
+ + <%= Spree.t(:shipment_details, stock_location: shipment.stock_location.name, shipping_method: shipment.selected_shipping_rate.name) %> +
+ <% end %> +
+ <%= render 'spree/shared/shipment_tracking', order: order if order.shipped? %> +
+ <% end %> + <% end %> + + <% if order.has_step?("payment") %> +
+

<%= Spree.t(:payment_information) %>

+
+ <% order.payments.valid.each do |payment| %> + <%= render payment %>
+ <% end %> +
+
+ <% end %> +
+ +
+ + + + + + + + + + + + + + + + + + + <% order.line_items.each do |item| %> + + + + + + + + <% end %> + + + + + + + + + + + + + + + + <% if order.line_item_adjustments.exists? %> + <% if order.line_item_adjustments.promotion.eligible.exists? %> + + <% order.line_item_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %> + + + + + <% end %> + + <% end %> + <% end %> + + + <% order.shipments.group_by { |s| s.selected_shipping_rate.name }.each do |name, shipments| %> + + + + + <% end %> + + + <% if order.all_adjustments.tax.exists? %> + + <% order.all_adjustments.tax.group_by(&:label).each do |label, adjustments| %> + + + + + <% end %> + + <% end %> + + + <% order.adjustments.eligible.each do |adjustment| %> + <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %> + + + + + <% end %> + +
<%= Spree.t(:item) %><%= Spree.t(:price) %><%= Spree.t(:qty) %><%= Spree.t(:total) %>
+ <% if item.variant.images.length == 0 %> + <%= link_to small_image(item.variant.product), item.variant.product %> + <% else %> + <%= link_to image_tag(item.variant.images.first.attachment.url(:small)), item.variant.product %> + <% end %> + +

<%= item.variant.product.name %>

+ <%= truncated_product_description(item.variant.product) %> + <%= "(" + item.variant.options_text + ")" unless item.variant.option_values.empty? %> +
<%= item.single_money.to_html %><%= item.quantity %><%= item.display_amount.to_html %>
<%= Spree.t(:order_total) %>:<%= order.display_total.to_html %>
<%= Spree.t(:subtotal) %>:<%= order.display_item_total.to_html %>
<%= Spree.t(:promotion) %>: <%= label %><%= Spree::Money.new(adjustments.sum(&:amount), currency: order.currency) %>
<%= Spree.t(:shipping) %>: <%= name %><%= Spree::Money.new(shipments.sum(&:discounted_cost), currency: order.currency).to_html %>
<%= Spree.t(:tax) %>: <%= label %><%= Spree::Money.new(adjustments.sum(&:amount), currency: order.currency) %>
<%= adjustment.label %><%= adjustment.display_amount.to_html %>
\ No newline at end of file diff --git a/app/views/spree/amazonpay/complete.html.erb b/app/views/spree/amazonpay/complete.html.erb deleted file mode 100644 index 3b26b8c..0000000 --- a/app/views/spree/amazonpay/complete.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -<%# %> -<%# Amazon Payments - Login and Pay for Spree Commerce %> -<%# %> -<%# @category Amazon %> -<%# @package Amazon_Payments %> -<%# @copyright Copyright (c) 2014 Amazon.com %> -<%# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 %> -<%# %> -
- <%= Spree.t(:order_number, :number => @order.number) %> -

<%= accurate_title %>

- <% if order_just_completed?(@order) %> - <%= Spree.t(:thank_you_for_your_order) %> - <% end %> - -
- <%= render :partial => 'spree/shared/order_details', :locals => { :order => @order } %> - -
- -

- <%= link_to Spree.t(:back_to_store), spree.root_path, :class => "button" %> - <% unless order_just_completed?(@order) %> - <% if try_spree_current_user && respond_to?(:spree_account_path) %> - <%= link_to Spree.t(:my_account), spree_account_path, :class => "button" %> - <% end %> - <% end %> -

-
-
diff --git a/app/views/spree/amazonpay/confirm.html.erb b/app/views/spree/amazonpay/confirm.html.erb index 64c0dbd..9624125 100644 --- a/app/views/spree/amazonpay/confirm.html.erb +++ b/app/views/spree/amazonpay/confirm.html.erb @@ -11,7 +11,7 @@
<%= Spree.t(:confirm) %> - <%= render :partial => 'spree/shared/order_details', :locals => { :order => current_order } %> + <%= render :partial => 'spree/amazonpay/order_details', :locals => { :order => current_order } %>
<%= form_tag spree.payment_amazonpay_path, :class => 'edit_order' do %> @@ -21,3 +21,14 @@ <% end %> + + \ No newline at end of file diff --git a/app/views/spree/amazonpay/delivery.html.erb b/app/views/spree/amazonpay/delivery.html.erb deleted file mode 100644 index c65b050..0000000 --- a/app/views/spree/amazonpay/delivery.html.erb +++ /dev/null @@ -1,11 +0,0 @@ -<%# %> -<%# Amazon Payments - Login and Pay for Spree Commerce %> -<%# %> -<%# @category Amazon %> -<%# @package Amazon_Payments %> -<%# @copyright Copyright (c) 2014 Amazon.com %> -<%# @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 %> -<%# %> -<%= form_for current_order, :url => confirm_amazon_order_path, :method => :post do |form| %> - <%= render partial: 'spree/checkout/delivery', locals: {form: form} %> -<% end %> \ No newline at end of file From 6dd9dcca0b437a00b8bde24e17866aba1b54e98e Mon Sep 17 00:00:00 2001 From: hf2186 Date: Thu, 14 Nov 2019 22:46:51 -0500 Subject: [PATCH 20/39] Updating gemspec and removing puts --- Gemfile | 7 +------ app/models/spree/gateway/amazon.rb | 9 ++------- lib/amazon_pay.rb | 4 ---- spree_amazon_payments.gemspec | 2 +- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/Gemfile b/Gemfile index b0adfdd..f26c667 100644 --- a/Gemfile +++ b/Gemfile @@ -9,8 +9,7 @@ ## source 'https://rubygems.org' -branch = ENV.fetch('SPREE_BRANCH', 'master') -gem 'spree' #, github: 'spree/spree', branch: branch +gem 'spree' group :development, :test do gem 'pry-rails' @@ -21,8 +20,4 @@ group :test do gem 'webmock' end -gem 'pg' -gem 'mysql2' -gem 'openssl', '~> 2.1.0' - gemspec diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 7d86dea..1418157 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -173,14 +173,9 @@ def cancel(response_code) payment = Spree::Payment.find_by!(response_code: response_code) amazon_transaction = payment.source - load_amazon_pay - if amazon_transaction.capture_id.nil? - - params = { - cancellationReason: 'Cancelled Order' - } - + load_amazon_pay + params = { cancellationReason: 'Cancelled Order' } response = AmazonPay::Charge.cancel(amazon_transaction.order_reference, params) diff --git a/lib/amazon_pay.rb b/lib/amazon_pay.rb index bcf5bd1..4df2e3b 100644 --- a/lib/amazon_pay.rb +++ b/lib/amazon_pay.rb @@ -99,8 +99,6 @@ def self.signed_headers(http_request_method, request_uri, request_parameters, re header_array['x-amz-pay-date'] = time_stamp header_array['x-amz-pay-region'] = @@region header_array['authorization'] = "#{@@amazon_signature_algorithm} PublicKeyId=#{@@public_key_id}, #{signed_headers}" - puts("\nAUTHORIZATION HEADER:\n" + header_array['authorization']) - header_array.sort_by { |key, _value| key }.to_h end @@ -114,9 +112,7 @@ def self.create_signature(http_request_method, request_uri, request_parameters, canonical_header = header_string(pre_signed_headers) signed_headers = canonical_headers_names(pre_signed_headers) canonical_request = "#{http_request_method.upcase}\n#{canonical_uri}\n#{canonical_query_string}\n#{canonical_header}\n#{signed_headers}\n#{hashed_payload}" - puts("\nCANONICAL REQUEST:\n" + canonical_request) hashed_canonical_request = "#{@@amazon_signature_algorithm}\n#{hex_and_hash(canonical_request)}" - puts("\nSTRING TO SIGN:\n" + hashed_canonical_request) Base64.strict_encode64(rsa.sign_pss(@@hash_algorithm, hashed_canonical_request, salt_length: 20, mgf1_hash: @@hash_algorithm)) end diff --git a/spree_amazon_payments.gemspec b/spree_amazon_payments.gemspec index d669c73..21866af 100644 --- a/spree_amazon_payments.gemspec +++ b/spree_amazon_payments.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'spree_amazon_payments' - s.version = '3.1.0' + s.version = '3.2.0' s.summary = 'Spree Amazon Payments' s.description = '' From 7dac0728a809ba3b7e921f9180c0141b8fb7263b Mon Sep 17 00:00:00 2001 From: hf2186 Date: Fri, 15 Nov 2019 15:50:30 -0500 Subject: [PATCH 21/39] make sure to move to payment so tax and payment calculations are correct --- app/controllers/spree/amazonpay_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 8d2cf75..5c3d396 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -71,6 +71,8 @@ def confirm if current_order.shipments.empty? redirect_to cart_path, notice: Spree.t(:cannot_ship_to_address) + else + current_order.next! end end From 40c87594dbc7e24650c388bc44f15c5ea50d5bb3 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 17 Nov 2019 02:20:09 -0500 Subject: [PATCH 22/39] Changing inhertance and fixing order processing --- app/controllers/spree/amazonpay_controller.rb | 94 +++++++++++-------- app/models/spree/gateway/amazon.rb | 16 ++-- 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 5c3d396..0aa5f4f 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -7,21 +7,15 @@ # @license http://opensource.org/licenses/Apache-2.0 Apache License, Version 2.0 # ## -class Spree::AmazonpayController < Spree::StoreController +class Spree::AmazonpayController < Spree::CheckoutController helper 'spree/orders' - before_action :check_current_order before_action :gateway, only: [:confirm, :create, :payment, :complete] skip_before_action :verify_authenticity_token, only: %i[create complete] respond_to :json def create - if current_order.cart? - current_order.next! - else - current_order.state = 'address' - current_order.save! - end + update_order_state('cart') params = { webCheckoutDetail: { checkoutReviewReturnUrl: gateway.base_url(request.ssl?) + 'confirm' }, @@ -33,10 +27,7 @@ def create end def confirm - unless current_order.address? - current_order.state = 'address' - current_order.save! - end + update_order_state('address') response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) @@ -45,14 +36,23 @@ def confirm return end - amazon_user = SpreeAmazon::User.from_response(response.body) + body = response.body + status_detail = body[:statusDetail] + + # if the order was already completed then they shouldn't be at this step + if status_detail[:state] == 'Completed' + redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) + return + end + + amazon_user = SpreeAmazon::User.from_response(body) set_user_information(amazon_user.auth_hash) if spree_current_user.nil? - current_order.update_attributes!(email: amazon_user.email) + @order.update_attributes!(email: amazon_user.email) end - amazon_address = SpreeAmazon::Address.from_response(response.body) + amazon_address = SpreeAmazon::Address.from_response(body) address_attributes = amazon_address.attributes if spree_address_book_available? @@ -64,20 +64,25 @@ def confirm return end - update_current_order_address!(address_attributes) + update_order_address!(address_attributes) - current_order.unprocessed_payments.map(&:invalidate!) - current_order.next! + @order.unprocessed_payments.map(&:invalidate!) - if current_order.shipments.empty? + if !@order.next || @order.shipments.empty? redirect_to cart_path, notice: Spree.t(:cannot_ship_to_address) else - current_order.next! + @order.next end end def payment - authorize!(:edit, current_order, cookies.signed[:guest_token]) + update_order_state('payment') + + unless @order.next + flash[:error] = @order.errors.full_messages.join("\n") + redirect_to cart_path + return + end params = { webCheckoutDetail: { @@ -87,12 +92,12 @@ def payment paymentIntent: 'Authorize', canHandlePendingAuthorization: false, chargeAmount: { - amount: current_order.order_total_after_store_credit, + amount: @order.order_total_after_store_credit, currencyCode: current_currency } }, merchantMetadata: { - merchantReferenceId: current_order.number, + merchantReferenceId: @order.number, merchantStoreName: current_store.name, noteToBuyer: '', customInformation: '' @@ -120,13 +125,13 @@ def complete body = response.body status_detail = body[:statusDetail] + # Make sure the order status from Amazon is completed otherwise + # Redirect to cart for the consumer to start over unless status_detail[:state] == 'Completed' redirect_to cart_path, notice: status_detail[:reasonDescription] return end - @order = current_order - payments = @order.payments payment = payments.create payment.payment_method = gateway @@ -148,7 +153,7 @@ def complete @current_order = nil flash.notice = Spree.t(:order_processed_successfully) flash[:order_completed] = true - redirect_to spree.order_path(@order) + redirect_to completion_route else amazon_transaction = @order.amazon_transaction amazon_transaction.reload @@ -164,13 +169,20 @@ def complete end def gateway - @gateway ||= Spree::Gateway::Amazon.for_currency(current_order.currency) + @gateway ||= Spree::Gateway::Amazon.for_currency(@order.currency) @gateway.load_amazon_pay @gateway end private + def update_order_state(state) + if @order.state != state + @order.state = state + @order.save! + end + end + def amazon_checkout_session_id params[:amazonCheckoutSessionId] end @@ -203,18 +215,12 @@ def set_user_information(auth_hash) # make sure to merge the current order with signed in user previous cart set_current_order - current_order.associate_user!(user) + @order.associate_user!(user) session[:guest_token] = nil end - def check_current_order - unless current_order - redirect_to cart_path - end - end - - def update_current_order_address!(address_attributes, spree_user_address = nil) - ship_address = current_order.ship_address + def update_order_address!(address_attributes, spree_user_address = nil) + ship_address = @order.ship_address new_address = Spree::Address.new address_attributes if spree_address_book_available? @@ -223,21 +229,31 @@ def update_current_order_address!(address_attributes, spree_user_address = nil) end if user_address - current_order.update_column(:ship_address_id, user_address.id) + @order.update_column(:ship_address_id, user_address.id) else new_address.save! - current_order.update_column(:ship_address_id, new_address.id) + @order.update_column(:ship_address_id, new_address.id) end else if ship_address.nil? || ship_address.empty? new_address.save! - current_order.update_column(:ship_address_id, new_address.id) + @order.update_column(:ship_address_id, new_address.id) else ship_address.update_attributes(address_attributes) end end end + def rescue_from_spree_gateway_error(exception) + flash.now[:error] = Spree.t(:spree_gateway_error_flash_for_checkout) + @order.errors.add(:base, exception.message) + redirect_to cart_path + end + + def skip_state_validation? + true + end + def spree_address_book_available? Gem::Specification.find_all_by_name('spree_address_book').any? end diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index 1418157..d6493b0 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -103,8 +103,7 @@ def authorize(amount, amazon_transaction, gateway_options={}) def capture(amount, response_code, gateway_options={}) return credit(amount.abs, response_code, gateway_options) if amount < 0 - payment = Spree::Payment.find_by!(response_code: response_code) - amazon_transaction = payment.source + _payment, amazon_transaction = find_payment_and_transaction(response_code) load_amazon_pay @@ -139,8 +138,7 @@ def purchase(amount, amazon_transaction, gateway_options = {}) end def credit(amount, response_code, _gateway_options = {}) - payment = Spree::Payment.find_by!(response_code: response_code) - amazon_transaction = payment.source + payment, amazon_transaction = find_payment_and_transaction(response_code) load_amazon_pay @@ -170,8 +168,7 @@ def void(response_code, _gateway_options = {}) end def cancel(response_code) - payment = Spree::Payment.find_by!(response_code: response_code) - amazon_transaction = payment.source + payment, amazon_transaction = find_payment_and_transaction(response_code) if amazon_transaction.capture_id.nil? load_amazon_pay @@ -195,6 +192,13 @@ def load_amazon_pay private + def find_payment_and_transaction(response_code) + payment = Spree::Payment.find_by(response_code: response_code) + (raise Spree::Core::GatewayError, 'Payment not found') unless payment + amazon_transaction = payment.source + [payment, amazon_transaction] + end + def update_for_backwards_compatibility(capture_id) capture_id[20] == 'A' ? capture_id[20, 1] = 'C' : capture_id end From 543b83779b66ca59ad070b9d140ca4fd9316645a Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 17 Nov 2019 18:21:39 -0500 Subject: [PATCH 23/39] Fixing order address issues and cleaning up code --- app/controllers/spree/amazonpay_controller.rb | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 0aa5f4f..c666344 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -8,8 +8,7 @@ # ## class Spree::AmazonpayController < Spree::CheckoutController - helper 'spree/orders' - before_action :gateway, only: [:confirm, :create, :payment, :complete] + before_action :gateway skip_before_action :verify_authenticity_token, only: %i[create complete] respond_to :json @@ -67,6 +66,7 @@ def confirm update_order_address!(address_attributes) @order.unprocessed_payments.map(&:invalidate!) + @order.temporary_address = true if !@order.next || @order.shipments.empty? redirect_to cart_path, notice: Spree.t(:cannot_ship_to_address) @@ -219,7 +219,7 @@ def set_user_information(auth_hash) session[:guest_token] = nil end - def update_order_address!(address_attributes, spree_user_address = nil) + def update_order_address!(address_attributes) ship_address = @order.ship_address new_address = Spree::Address.new address_attributes @@ -234,13 +234,11 @@ def update_order_address!(address_attributes, spree_user_address = nil) new_address.save! @order.update_column(:ship_address_id, new_address.id) end + elsif ship_address.nil? || ship_address.empty? + new_address.save! + @order.update_column(:ship_address_id, new_address.id) else - if ship_address.nil? || ship_address.empty? - new_address.save! - @order.update_column(:ship_address_id, new_address.id) - else - ship_address.update_attributes(address_attributes) - end + ship_address.update_attributes(address_attributes) end end From f1cefd34c4a3b1c5acd688ec446b48d25951a71d Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 18 Nov 2019 00:51:13 -0500 Subject: [PATCH 24/39] Fixing address issues --- app/controllers/spree/amazonpay_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index c666344..0dfca51 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -221,6 +221,7 @@ def set_user_information(auth_hash) def update_order_address!(address_attributes) ship_address = @order.ship_address + bill_address = @order.bill_address new_address = Spree::Address.new address_attributes if spree_address_book_available? @@ -230,15 +231,19 @@ def update_order_address!(address_attributes) if user_address @order.update_column(:ship_address_id, user_address.id) + @order.update_column(:bill_address_id, user_address.id) else new_address.save! @order.update_column(:ship_address_id, new_address.id) + @order.update_column(:bill_address_id, new_address.id) end elsif ship_address.nil? || ship_address.empty? new_address.save! @order.update_column(:ship_address_id, new_address.id) + @order.update_column(:bill_address_id, new_address.id) else ship_address.update_attributes(address_attributes) + bill_address.update_attributes(address_attributes) end end From 74a8072294bef536d6726adf346898a12079e1a5 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 18 Nov 2019 12:14:51 -0500 Subject: [PATCH 25/39] Override Registration Check --- app/controllers/spree/amazonpay_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 0dfca51..c2e1de5 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -257,6 +257,11 @@ def skip_state_validation? true end + # We are logging the user in so there is no need to check registration + def check_registration + true + end + def spree_address_book_available? Gem::Specification.find_all_by_name('spree_address_book').any? end From 571fe5f948c66a67373d64b6ecc88f4973172f7f Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 18 Nov 2019 15:40:26 -0500 Subject: [PATCH 26/39] Adding Additional Error Handling --- app/controllers/spree/amazonpay_controller.rb | 13 +++++++++++-- config/locales/en.yml | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index c2e1de5..7d77660 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -9,8 +9,11 @@ ## class Spree::AmazonpayController < Spree::CheckoutController before_action :gateway + skip_before_action :verify_authenticity_token, only: %i[create complete] + rescue_from ActiveRecord::RecordInvalid, with: :rescue_from_active_record_error + respond_to :json def create @@ -224,7 +227,7 @@ def update_order_address!(address_attributes) bill_address = @order.bill_address new_address = Spree::Address.new address_attributes - if spree_address_book_available? + if spree_current_user.respond_to?(:addresses) user_address = spree_current_user.addresses.find do |address| address.same_as?(new_address) end @@ -250,7 +253,13 @@ def update_order_address!(address_attributes) def rescue_from_spree_gateway_error(exception) flash.now[:error] = Spree.t(:spree_gateway_error_flash_for_checkout) @order.errors.add(:base, exception.message) - redirect_to cart_path + render :confirm + end + + def rescue_from_active_record_error(exception) + flash.now[:error] = Spree.t(:spree_active_record_error_flash_for_checkout) + @order.errors.add(:base, exception.message) + render :confirm end def skip_state_validation? diff --git a/config/locales/en.yml b/config/locales/en.yml index 0c600b3..368a53e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -22,3 +22,4 @@ en: order_reference_id: Order Reference ID order_processed_unsuccessfully: Your payment could not be processed. Please try to place the order again using another payment method. cannot_ship_to_address: Cannot ship to this address. + spree_active_record_error_flash_for_checkout: "We could not complete your order at this time. An item may have become unavailable." From 611a17ddaffb7db1da39888c7bc0a5c620a7125e Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 18 Nov 2019 22:55:46 -0500 Subject: [PATCH 27/39] Fixing order state issues --- app/controllers/spree/amazonpay_controller.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 7d77660..3e81540 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -17,8 +17,6 @@ class Spree::AmazonpayController < Spree::CheckoutController respond_to :json def create - update_order_state('cart') - params = { webCheckoutDetail: { checkoutReviewReturnUrl: gateway.base_url(request.ssl?) + 'confirm' }, storeId: gateway.preferred_client_id } @@ -29,7 +27,7 @@ def create end def confirm - update_order_state('address') + update_order_state('cart') response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) @@ -68,6 +66,8 @@ def confirm update_order_address!(address_attributes) + update_order_state('address') + @order.unprocessed_payments.map(&:invalidate!) @order.temporary_address = true @@ -158,6 +158,7 @@ def complete flash[:order_completed] = true redirect_to completion_route else + update_order_state('cart') amazon_transaction = @order.amazon_transaction amazon_transaction.reload if amazon_transaction.soft_decline From 7fcd556381519c5571fcd7213a1aa2c2b18bf3cd Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 18 Nov 2019 23:46:19 -0500 Subject: [PATCH 28/39] removing state change --- app/controllers/spree/amazonpay_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 3e81540..6ad21cd 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -27,8 +27,6 @@ def create end def confirm - update_order_state('cart') - response = AmazonPay::CheckoutSession.get(amazon_checkout_session_id) unless response.success? From fdd16a1bcd85262ca33951288ba78f2a301b9331 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 19 Nov 2019 10:45:05 -0500 Subject: [PATCH 29/39] Fixing phone number being empty --- app/models/spree_amazon/address.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/spree_amazon/address.rb b/app/models/spree_amazon/address.rb index 74ab896..c50b6c2 100644 --- a/app/models/spree_amazon/address.rb +++ b/app/models/spree_amazon/address.rb @@ -16,7 +16,7 @@ def attributes_from_response(address_params) state: convert_state(address_params[:stateOrRegion], convert_country(address_params[:countryCode])), country: convert_country(address_params[:countryCode]), - phone: address_params[:phoneNumber] || '0000000000' + phone: convert_phone(address_params[:phoneNumber]) || '0000000000' } end @@ -46,6 +46,11 @@ def convert_country(country_code) def convert_state(state_name, country) Spree::State.find_by(abbr: state_name, country: country) end + + def convert_phone(phone_number) + return nil if phone_number.blank? + phone_number + end end attr_accessor :first_name, :last_name, :city, :zipcode, From e7eebfa4967680c83986c9b49d22e0e8a3ff5b94 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 19 Nov 2019 14:53:51 -0500 Subject: [PATCH 30/39] Fixing order address clone issues --- app/controllers/spree/amazonpay_controller.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 6ad21cd..843c52f 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -63,23 +63,21 @@ def confirm end update_order_address!(address_attributes) - update_order_state('address') @order.unprocessed_payments.map(&:invalidate!) - @order.temporary_address = true - if !@order.next || @order.shipments.empty? + if !order_next || @order.shipments.empty? redirect_to cart_path, notice: Spree.t(:cannot_ship_to_address) else - @order.next + order_next end end def payment update_order_state('payment') - unless @order.next + unless order_next flash[:error] = @order.errors.full_messages.join("\n") redirect_to cart_path return @@ -148,7 +146,7 @@ def complete @order.reload - while @order.next; end + while order_next; end if @order.complete? @current_order = nil @@ -180,6 +178,7 @@ def gateway def update_order_state(state) if @order.state != state + @order.temporary_address = true @order.state = state @order.save! end @@ -270,6 +269,11 @@ def check_registration true end + def order_next + @order.temporary_address = true + @order.next + end + def spree_address_book_available? Gem::Specification.find_all_by_name('spree_address_book').any? end From 74662bc194c62965ec310eff6e7875c7136762cd Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 19 Nov 2019 14:56:24 -0500 Subject: [PATCH 31/39] adding temp address --- app/controllers/spree/amazonpay_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 843c52f..cf7f839 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -162,6 +162,7 @@ def complete notice: amazon_transaction.message else @order.amazon_transactions.destroy_all + @order.temporary_address = true @order.save! redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) end From 19ad789c8015693adc92c79fd8a5df9cdd11d4f3 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Thu, 21 Nov 2019 22:01:48 -0500 Subject: [PATCH 32/39] Fixes issue with state being null --- app/models/spree_amazon/address.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/spree_amazon/address.rb b/app/models/spree_amazon/address.rb index c50b6c2..ba7d2af 100644 --- a/app/models/spree_amazon/address.rb +++ b/app/models/spree_amazon/address.rb @@ -44,7 +44,8 @@ def convert_country(country_code) end def convert_state(state_name, country) - Spree::State.find_by(abbr: state_name, country: country) + Spree::State.find_by(abbr: state_name, country: country) || + Spree::State.find_by(name: state_name, country: country) end def convert_phone(phone_number) From 5140780d8bd51fc6c3b9e3e8c125d242b84b3c63 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Fri, 22 Nov 2019 15:26:45 -0500 Subject: [PATCH 33/39] Fix Phone number issues --- app/models/spree_amazon/address.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/spree_amazon/address.rb b/app/models/spree_amazon/address.rb index ba7d2af..685898e 100644 --- a/app/models/spree_amazon/address.rb +++ b/app/models/spree_amazon/address.rb @@ -49,7 +49,9 @@ def convert_state(state_name, country) end def convert_phone(phone_number) - return nil if phone_number.blank? + return nil if phone_number.blank? || + phone_number.length < 10 || + phone_number.length > 15 phone_number end end From 5612013d17bce901c2f9214dfc91298146d14401 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Mon, 25 Nov 2019 15:18:10 -0500 Subject: [PATCH 34/39] Adding null buyer fixes --- app/controllers/spree/amazonpay_controller.rb | 10 ++++------ lib/amazon/response.rb | 5 +++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index cf7f839..31dbf4c 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -36,9 +36,10 @@ def confirm body = response.body status_detail = body[:statusDetail] + no_buyer = response.find_constraint('BuyerNotAssociated') # if the order was already completed then they shouldn't be at this step - if status_detail[:state] == 'Completed' + if status_detail[:state] == 'Completed' || no_buyer redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) return end @@ -157,15 +158,12 @@ def complete update_order_state('cart') amazon_transaction = @order.amazon_transaction amazon_transaction.reload - if amazon_transaction.soft_decline - redirect_to confirm_amazonpay_path(amazonCheckoutSessionId: amazon_checkout_session_id), - notice: amazon_transaction.message - else + unless amazon_transaction.soft_decline @order.amazon_transactions.destroy_all @order.temporary_address = true @order.save! - redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) end + redirect_to cart_path, notice: Spree.t(:order_processed_unsuccessfully) end end diff --git a/lib/amazon/response.rb b/lib/amazon/response.rb index 56f4c31..0cef369 100644 --- a/lib/amazon/response.rb +++ b/lib/amazon/response.rb @@ -26,6 +26,11 @@ def soft_decline? reason_code == 'SoftDeclined' end + def find_constraint(constraint) + return nil unless body[:constraints] + body[:constraints].find { |c| c[:constraintId] == constraint } + end + def message return 'Success' if success? body[:message] From 7ba4c05315d74292062b5bfcae0194bd4d27f157 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Tue, 17 Dec 2019 20:39:41 -0500 Subject: [PATCH 35/39] Make sure a uid and email is there before moving forward --- app/controllers/spree/amazonpay_controller.rb | 6 ++++++ app/models/spree_amazon/user.rb | 4 ++++ config/locales/en.yml | 1 + 3 files changed, 11 insertions(+) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 31dbf4c..564022f 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -45,6 +45,12 @@ def confirm end amazon_user = SpreeAmazon::User.from_response(body) + + unless amazon_user.valid? + redirect_to cart_path, notice: Spree.t(:uid_not_set) + return + end + set_user_information(amazon_user.auth_hash) if spree_current_user.nil? diff --git a/app/models/spree_amazon/user.rb b/app/models/spree_amazon/user.rb index d685e30..b7c47ee 100644 --- a/app/models/spree_amazon/user.rb +++ b/app/models/spree_amazon/user.rb @@ -24,6 +24,10 @@ def initialize(attributes) end end + def valid? + uid.present? && email.present? + end + def auth_hash { 'provider' => 'amazon', diff --git a/config/locales/en.yml b/config/locales/en.yml index 368a53e..0f8c53c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -23,3 +23,4 @@ en: order_processed_unsuccessfully: Your payment could not be processed. Please try to place the order again using another payment method. cannot_ship_to_address: Cannot ship to this address. spree_active_record_error_flash_for_checkout: "We could not complete your order at this time. An item may have become unavailable." + uid_not_set: "We couldn't locate an Amazon User ID. Please try again." From 11458aad7f3174a6af78189761d068baf342df75 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Wed, 11 Mar 2020 16:05:54 -0400 Subject: [PATCH 36/39] Making redirects dynamic --- app/controllers/spree/amazonpay_controller.rb | 4 ++-- app/models/spree/gateway/amazon.rb | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 564022f..23bb539 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -18,7 +18,7 @@ class Spree::AmazonpayController < Spree::CheckoutController def create params = { webCheckoutDetail: - { checkoutReviewReturnUrl: gateway.base_url(request.ssl?) + 'confirm' }, + { checkoutReviewReturnUrl: confirm_amazonpay_url }, storeId: gateway.preferred_client_id } response = AmazonPay::CheckoutSession.create(params) @@ -92,7 +92,7 @@ def payment params = { webCheckoutDetail: { - checkoutResultReturnUrl: gateway.base_url(request.ssl?) + 'complete' + checkoutResultReturnUrl: complete_amazonpay_url }, paymentDetail: { paymentIntent: 'Authorize', diff --git a/app/models/spree/gateway/amazon.rb b/app/models/spree/gateway/amazon.rb index d6493b0..539a1a6 100644 --- a/app/models/spree/gateway/amazon.rb +++ b/app/models/spree/gateway/amazon.rb @@ -29,10 +29,6 @@ def self.for_currency(currency) where(active: true).detect { |gateway| gateway.preferred_currency == currency } end - def base_url(use_ssl) - "http#{use_ssl ? 's' : ''}://#{preferred_site_domain}/amazonpay/" - end - def widgets_url { 'us' => 'https://static-na.payments-amazon.com/checkout.js', From f0759cb627438df29bb67666e8088dd182d91697 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 21 Jun 2020 18:28:39 -0400 Subject: [PATCH 37/39] Adding Amazon Delivery Notifications Code --- app/jobs/alexa_delivery_notification_job.rb | 23 +++++++++++++++++++++ app/models/spree/order_decorator.rb | 4 ++++ app/models/spree/shipment_decorator.rb | 19 +++++++++++++++++ lib/amazon/delivery_trackers.rb | 7 +++++++ lib/amazon_pay.rb | 1 + 5 files changed, 54 insertions(+) create mode 100644 app/jobs/alexa_delivery_notification_job.rb create mode 100644 app/models/spree/shipment_decorator.rb create mode 100644 lib/amazon/delivery_trackers.rb diff --git a/app/jobs/alexa_delivery_notification_job.rb b/app/jobs/alexa_delivery_notification_job.rb new file mode 100644 index 0000000..3bd4d7d --- /dev/null +++ b/app/jobs/alexa_delivery_notification_job.rb @@ -0,0 +1,23 @@ +class AlexaDeliveryNotificationJob < ActiveJob::Base + queue_as :default + + def perform(shipment_id) + shipment = Spree::Shipment.find_by(id: shipment_id) + order = shipment.order + + params = { + amazonOrderReferenceId: order.amazon_order_reference_id, + deliveryDetails: [{ + trackingNumber: shipment.tracking, + carrierCode: shipment.amazon_carrier_code + }] + } + + # need to do this to make sure everything loads + Spree::Gateway::Amazon.for_currency(order.currency).load_amazon_pay + + response = AmazonPay::DeliveryTrackers.create(params) + + raise 'Could not update tracking info for Alexa DN' unless response.success? + end +end diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 153fb10..bb532c7 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -21,6 +21,10 @@ def amazon_order_reference_id amazon_transaction.try(:order_reference) end + def amazon_pay_order? + amazon_transaction.try(:success) + end + def confirmation_required? spree_confirmation_required? || payments.valid.map(&:payment_method).compact.any? { |pm| pm.is_a? Spree::Gateway::Amazon } end diff --git a/app/models/spree/shipment_decorator.rb b/app/models/spree/shipment_decorator.rb new file mode 100644 index 0000000..dc9fd69 --- /dev/null +++ b/app/models/spree/shipment_decorator.rb @@ -0,0 +1,19 @@ +Spree::Shipment.class_eval do + delegate :amazon_pay_order?, to: :order + + self.state_machine.after_transition( + to: :shipped, + do: :alexa_delivery_notification, + if: :amazon_pay_order? + ) + + def alexa_delivery_notification + AlexaDeliveryNotificationJob.set(wait: 2.seconds).perform_later(id) + end + + # override this if your carrier codes don't match amazon carrier codes + # https://eps-eu-external-file-share.s3.eu-central-1.amazonaws.com/Alexa/Delivery+Notifications/amazon-pay-delivery-tracker-supported-carriers-v2.csv + def amazon_carrier_code + shipping_method.code + end +end diff --git a/lib/amazon/delivery_trackers.rb b/lib/amazon/delivery_trackers.rb new file mode 100644 index 0000000..455fb81 --- /dev/null +++ b/lib/amazon/delivery_trackers.rb @@ -0,0 +1,7 @@ +module AmazonPay + class DeliveryTrackers + def self.create(params) + AmazonPay.request('post', 'deliveryTrackers', params) + end + end +end diff --git a/lib/amazon_pay.rb b/lib/amazon_pay.rb index 4df2e3b..3c9ffe3 100644 --- a/lib/amazon_pay.rb +++ b/lib/amazon_pay.rb @@ -9,6 +9,7 @@ require 'amazon/checkout_session' require 'amazon/refund' require 'amazon/response' +require 'amazon/delivery_trackers' module AmazonPay @@amazon_signature_algorithm = 'AMZN-PAY-RSASSA-PSS'.freeze From 1e68df451e1f95079a97ca97e06abb7da414a694 Mon Sep 17 00:00:00 2001 From: hf2186 Date: Sun, 28 Jun 2020 23:48:51 -0400 Subject: [PATCH 38/39] Adding Error Checking for random edge cases --- app/controllers/spree/amazonpay_controller.rb | 6 +++++- app/models/spree_amazon/user.rb | 14 +++++++++----- lib/amazon/response.rb | 6 +++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/controllers/spree/amazonpay_controller.rb b/app/controllers/spree/amazonpay_controller.rb index 23bb539..b38896d 100644 --- a/app/controllers/spree/amazonpay_controller.rb +++ b/app/controllers/spree/amazonpay_controller.rb @@ -23,7 +23,11 @@ def create response = AmazonPay::CheckoutSession.create(params) - render json: response.body + if response.success? + render json: response.body + else + render json: {} + end end def confirm diff --git a/app/models/spree_amazon/user.rb b/app/models/spree_amazon/user.rb index b7c47ee..99f68ad 100644 --- a/app/models/spree_amazon/user.rb +++ b/app/models/spree_amazon/user.rb @@ -8,11 +8,15 @@ def from_response(response) private def attributes_from_response(buyer_params) - { - name: buyer_params[:name], - email: buyer_params[:email], - uid: buyer_params[:buyerId] - } + if buyer_params.present? + { + name: buyer_params[:name], + email: buyer_params[:email], + uid: buyer_params[:buyerId] + } + else + {} + end end end diff --git a/lib/amazon/response.rb b/lib/amazon/response.rb index 0cef369..3f10488 100644 --- a/lib/amazon/response.rb +++ b/lib/amazon/response.rb @@ -5,13 +5,17 @@ class Response def initialize(response) @type = self.class.name.demodulize @response = response - @body = JSON.parse(response.body, symbolize_names: true) + @body = JSON.parse(response.body, symbolize_names: true) unless failure? end def success? response_code == 200 || response_code == 201 end + def failure? + response_code >= 500 + end + def response_code response.code.to_i end From 3b9d64134f563d336a9141480b140f9354b3f40b Mon Sep 17 00:00:00 2001 From: hf2186 Date: Thu, 2 Jul 2020 23:27:14 -0400 Subject: [PATCH 39/39] Fixes issue with null tracking or code --- app/models/spree/shipment_decorator.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/spree/shipment_decorator.rb b/app/models/spree/shipment_decorator.rb index dc9fd69..2a91929 100644 --- a/app/models/spree/shipment_decorator.rb +++ b/app/models/spree/shipment_decorator.rb @@ -4,9 +4,13 @@ self.state_machine.after_transition( to: :shipped, do: :alexa_delivery_notification, - if: :amazon_pay_order? + if: :send_alexa_delivery_notification? ) + def send_alexa_delivery_notification? + amazon_pay_order? && amazon_carrier_code.present? && tracking.present? + end + def alexa_delivery_notification AlexaDeliveryNotificationJob.set(wait: 2.seconds).perform_later(id) end @@ -14,6 +18,6 @@ def alexa_delivery_notification # override this if your carrier codes don't match amazon carrier codes # https://eps-eu-external-file-share.s3.eu-central-1.amazonaws.com/Alexa/Delivery+Notifications/amazon-pay-delivery-tracker-supported-carriers-v2.csv def amazon_carrier_code - shipping_method.code + shipping_method.try(:code) end end