sharetribe/sharetribe

View on GitHub
app/services/paypal_service/merchant_actions.rb

Summary

Maintainability
D
1 day
Test Coverage
module PaypalService
  module MerchantActions

    module_function

    # Convert between a Money instance and corresponding Paypal API presentation
    # pp API present amounts as hash-like objects, e.g. : { value: "17.12", currencyID: "EUR" }

    def from_money(m)
      { value: MoneyUtil.to_dot_separated_str(m), currencyID: m.currency.iso_code }
    end

    def to_money(pp_amount)
      pp_amount.value.to_money(pp_amount.currency_id) unless (pp_amount.nil? || pp_amount.value.nil?)
    end


    def hook_url(ipn_hook)
      ipn_hook[:url] unless ipn_hook.nil?
    end

    def append_useraction_commit(url_str)
      URLUtils.append_query_param(url_str, "useraction", "commit")
    end


    PAYMENT_ACTIONS = { sale: "Sale", order: "Order", authorization: "Authorization" }

    def payment_action_str(payment_action_symbol)
      str = PAYMENT_ACTIONS[payment_action_symbol]
      if str.nil?
        raise ArgumentError.new("Unsupported payment action: #{payment_action_symbol}")
      end

      str
    end


    # Use either the default old checkout UI or the new paypal checkout experience
    NEW_CHECKOUT_UI = false

    # URLs for the new paypal checkout UI
    SANDBOX_EC_URL = "https://www.sandbox.paypal.com/checkoutnow"
    LIVE_EC_URL = "https://www.paypal.com/checkoutnow"
    TOKEN_PARAM = "token"

    def express_checkout_url(api, token)
      if NEW_CHECKOUT_UI
        endpoint = api.config.mode.to_sym
        if (endpoint == :sandbox)
          URLUtils.append_query_param(SANDBOX_EC_URL, TOKEN_PARAM, token)
        else
          URLUtils.append_query_param(LIVE_EC_URL, TOKEN_PARAM, token)
        end
      else
        api.express_checkout_url(token)
      end
    end


    MERCHANT_ACTIONS = {
      setup_billing_agreement: PaypalAction.def_action(
        input_transformer: -> (req, config) {
          {
            SetExpressCheckoutRequestDetails: {
              ReturnURL: req[:success],
              CancelURL: req[:cancel],
              ReqConfirmShipping: 0,
              NoShipping: 1,
              AllowNote: 0,
              PaymentDetails: [{
                  OrderTotal: { value: "0.0" },
                  NotifyURL: hook_url(config[:ipn_hook]),
                  PaymentAction: PAYMENT_ACTIONS[:authorization]
                }],
              BillingAgreementDetails: [{
                  BillingType: "ChannelInitiatedBilling",
                  BillingAgreementDescription: req[:description]
                }]
            }
          }
        },
        wrapper_method_name: :build_set_express_checkout,
        action_method_name: :set_express_checkout,
        output_transformer: -> (res, api) {
          DataTypes::Merchant.create_setup_billing_agreement_response({
            token: res.token,
            redirect_url: express_checkout_url(api, res.token),
            username_to: api.config.subject || api.config.username
          })
        }
      ),

      create_billing_agreement: PaypalAction.def_action(
        input_transformer: -> (req, _) { { Token: req[:token] } },
        wrapper_method_name: :build_create_billing_agreement,
        action_method_name: :create_billing_agreement,
        output_transformer: -> (res, _) {
          DataTypes::Merchant.create_create_billing_agreement_response({
            billing_agreement_id: res.billing_agreement_id
          })
        }
      ),

      do_reference_transaction: PaypalAction.def_action(
        input_transformer: -> (req, config) {
          {
            DoReferenceTransactionRequestDetails: {
              ReferenceID: req[:billing_agreement_id],
              PaymentAction: PAYMENT_ACTIONS[:sale],
              PaymentType: "InstantOnly",
              PaymentDetails: {
                ButtonSource: config[:button_source],
                NotifyURL: hook_url(config[:ipn_hook]),
                OrderTotal: from_money(req[:payment_total]),
                InvoiceID: req[:invnum],
                PaymentDetailsItem: [{
                    ItemCategory: "Digital", #Commissions are always digital goods, enables also micropayments
                    Name: req[:name],
                    Description: req[:desc],
                    Number: 0,
                    Quantity: 1,
                    Amount: from_money(req[:payment_total])
                }]
              },
              MsgSubID: req[:msg_sub_id]
            }
          }
        },
        wrapper_method_name: :build_do_reference_transaction,
        action_method_name: :do_reference_transaction,
        output_transformer: -> (res, api) {
          details = res.do_reference_transaction_response_details
          DataTypes::Merchant.create_do_reference_transaction_response({
            billing_agreement_id: details.billing_agreement_id,
            payment_id: details.payment_info.transaction_id,
            payment_total: to_money(details.payment_info.gross_amount),
            payment_date: details.payment_info.payment_date.to_s,
            fee: to_money(details.payment_info.fee_amount),
            payment_status: details.payment_info.payment_status,
            pending_reason: details.payment_info.pending_reason,
            username_to: api.config.subject || api.config.username
          })
        }
      ),

      get_express_checkout_details: PaypalAction.def_action(
        input_transformer: -> (req, _) { { Token: req[:token] } },
        wrapper_method_name: :build_get_express_checkout_details,
        action_method_name: :get_express_checkout_details,
        output_transformer: -> (res, api) {
          details = res.get_express_checkout_details_response_details
          shipping_address = details.payment_details[0].ship_to_address
          DataTypes::Merchant.create_get_express_checkout_details_response(
            {
              token: details.token,
              checkout_status: details.checkout_status,
              billing_agreement_accepted: !!details.billing_agreement_accepted_status,
              payer: details.payer_info.payer,
              payer_id: details.payer_info.payer_id,
              order_total: to_money(details.payment_details[0].order_total),
              shipping_address_status: shipping_address.address_status,
              shipping_address_city: shipping_address.city_name,
              shipping_address_country: shipping_address.country_name,
              shipping_address_country_code: shipping_address.country,
              shipping_address_name: shipping_address.name,
              shipping_address_phone: shipping_address.phone,
              shipping_address_postal_code: shipping_address.postal_code,
              shipping_address_state_or_province: shipping_address.state_or_province,
              shipping_address_street1: shipping_address.street1,
              shipping_address_street2: shipping_address.street2
            }
          )
        }
      ),

      # Deprecated - Order flow will be removed soon
      #
      set_express_checkout_order: PaypalAction.def_action(
        input_transformer: -> (req, config) {
          req_details = {
            cppcartbordercolor: "FFFFFF",
            cpplogoimage: req[:merchant_brand_logo_url] || "",
            ReturnURL: req[:success],
            CancelURL: req[:cancel],
            ReqConfirmShipping: 0,
            NoShipping: req[:require_shipping_address] ? 0 : 1,
            SolutionType: "Sole",
            LandingPage: "Billing",
            InvoiceID: req[:invnum],
            AllowNote: 0,
            MaxAmount: from_money(req[:order_total]),
            PaymentDetails: [{
              NotifyURL: hook_url(config[:ipn_hook]),
              OrderTotal: from_money(req[:order_total]),
              ItemTotal: from_money(req[:item_price] * req[:item_quantity]),
              PaymentAction: PAYMENT_ACTIONS[:order],
              PaymentDetailsItem: [{
                Name: req[:item_name],
                Quantity: req[:item_quantity],
                Amount: from_money(req[:item_price])
              }]
            }]
          }

          if(req[:shipping_total])
             req_details[:PaymentDetails][0][:ShippingTotal] = from_money(req[:shipping_total])
          end

          { SetExpressCheckoutRequestDetails: req_details }
        },
        wrapper_method_name: :build_set_express_checkout,
        action_method_name: :set_express_checkout,
        output_transformer: -> (res, api) {
          DataTypes::Merchant.create_set_express_checkout_order_response({
            token: res.token,
            redirect_url: append_useraction_commit(express_checkout_url(api, res.token)),
            receiver_username: api.config.subject || api.config.username
          })
        }
      ),
      #
      # /Deprecated

      set_express_checkout_authorization: PaypalAction.def_action(
        input_transformer: -> (req, config) {
          req_details = {
            cppcartbordercolor: "FFFFFF",
            cpplogoimage: req[:merchant_brand_logo_url] || "",
            ReturnURL: req[:success],
            CancelURL: req[:cancel],
            ReqConfirmShipping: 0,
            NoShipping: req[:require_shipping_address] ? 0 : 1,
            SolutionType: "Sole",
            LandingPage: "Billing",
            InvoiceID: req[:invnum],
            AllowNote: 0,
            MaxAmount: from_money(req[:order_total]),
            PaymentDetails: [{
              NotifyURL: hook_url(config[:ipn_hook]),
              OrderTotal: from_money(req[:order_total]),
              ItemTotal: from_money(req[:item_price] * req[:item_quantity]),
              PaymentAction: PAYMENT_ACTIONS[:authorization],
              PaymentDetailsItem: [{
                Name: req[:item_name],
                Quantity: req[:item_quantity],
                Amount: from_money(req[:item_price])
              }]
            }]
          }

          if(req[:shipping_total])
             req_details[:PaymentDetails][0][:ShippingTotal] = from_money(req[:shipping_total])
          end

          { SetExpressCheckoutRequestDetails: req_details }
        },
        wrapper_method_name: :build_set_express_checkout,
        action_method_name: :set_express_checkout,
        output_transformer: -> (res, api) {
          DataTypes::Merchant.create_set_express_checkout_order_response({
            token: res.token,
            redirect_url: append_useraction_commit(express_checkout_url(api, res.token)),
            receiver_username: api.config.subject || api.config.username
          })
        }
      ),

      do_express_checkout_payment: PaypalAction.def_action(
        input_transformer: -> (req, config) {
          req_details = {
            PaymentAction: payment_action_str(req[:payment_action]),
            Token: req[:token],
            PayerID: req[:payer_id],
            ButtonSource: config[:button_source],
            PaymentDetails: [{
              InvoiceID: req[:invnum],
              NotifyURL: hook_url(config[:ipn_hook]),
              OrderTotal: from_money(req[:order_total]),
              ItemTotal: from_money(req[:item_price] * req[:item_quantity]),
              PaymentDetailsItem: [{
                Name: req[:item_name],
                Quantity: req[:item_quantity],
                Amount: from_money(req[:item_price])
              }]
            }]
          }

          if(req[:shipping_total])
            req_details[:PaymentDetails][0][:ShippingTotal] = from_money(req[:shipping_total])
            req_details[:PaymentDetails][0][:ShipToAddress] = {
              Name: req[:shipping_address_name],
              Phone: req[:shipping_address_phone],
              Street1: req[:shipping_address_street1],
              Street2: req[:shipping_address_street2],
              PostalCode: req[:shipping_address_postal_code],
              CityName: req[:shipping_address_city],
              StateOrProvince: req[:shipping_address_state_or_province],
              Country: req[:shipping_address_country_code]
            }
          end

          { DoExpressCheckoutPaymentRequestDetails: req_details }
        },
        wrapper_method_name: :build_do_express_checkout_payment,
        action_method_name: :do_express_checkout_payment,
        output_transformer: -> (res, api) {
          payment_info = res.do_express_checkout_payment_response_details.payment_info[0]

          if (payment_info.pending_reason == "order")
            DataTypes::Merchant.create_do_express_checkout_payment_response(
              {
                order_date: payment_info.payment_date.to_s,
                payment_status: payment_info.payment_status,
                pending_reason: payment_info.pending_reason,
                order_id: payment_info.transaction_id,
                order_total: to_money(payment_info.gross_amount)
              })
          else
            DataTypes::Merchant.create_do_express_checkout_payment_response(
              {
                authorization_date: payment_info.payment_date.to_s,
                payment_status: payment_info.payment_status,
                pending_reason: payment_info.pending_reason,
                authorization_id: payment_info.transaction_id,
                authorization_total: to_money(payment_info.gross_amount)
              })
          end
        }
      ),

      do_authorization: PaypalAction.def_action(
        input_transformer: -> (req, _) {
          {
            MsgSubID: req[:msg_sub_id],
            TransactionID: req[:order_id],
            Amount: from_money(req[:authorization_total])
          }
        },
        wrapper_method_name: :build_do_authorization,
        action_method_name: :do_authorization,
        output_transformer: -> (res, api) {
          DataTypes::Merchant.create_do_authorization_response({
            authorization_id: res.transaction_id,
            payment_status: res.authorization_info.payment_status,
            pending_reason: res.authorization_info.pending_reason,
            authorization_total: to_money(res.amount),
            authorization_date: res.timestamp.to_s,
            msg_sub_id: res.msg_sub_id
          })
        }
      ),

      do_capture: PaypalAction.def_action(
        input_transformer: -> (req, _) {
          {
            AuthorizationID: req[:authorization_id],
            Amount: from_money(req[:payment_total]),
            InvoiceID: req[:invnum],
            CompleteType: "Complete"
          }
        },
        wrapper_method_name: :build_do_capture,
        action_method_name: :do_capture,
        output_transformer: -> (res, api) {
          payment_info = res.do_capture_response_details.payment_info
          DataTypes::Merchant.create_do_full_capture_response(
            {
              authorization_id: res.do_capture_response_details.authorization_id,
              payment_id: payment_info.transaction_id,
              payment_status: payment_info.payment_status,
              pending_reason: payment_info.pending_reason,
              payment_total: to_money(payment_info.gross_amount),
              fee_total: to_money(payment_info.fee_amount),
              payment_date: payment_info.payment_date.to_s
            }
          )
        }
      ),

      do_void: PaypalAction.def_action(
        input_transformer: -> (req, _) {
          {
            AuthorizationID: req[:transaction_id],
            Note: req[:note],
            MsgSubID: req[:msg_sub_id]
          }
        },
        wrapper_method_name: :build_do_void,
        action_method_name: :do_void,
        output_transformer: -> (res, api) {
          DataTypes::Merchant.create_do_void_response(
            {
              voided_id: res.authorization_id,
              msg_sub_id: res.msg_sub_id
            }
          )
        }
      ),

      refund_transaction: PaypalAction.def_action(
        input_transformer: -> (req, _) {
          {
            TransactionID: req[:payment_id],
            RefundType: "Full",
            RefundSource: "default",
            MsgSubID: req[:msg_sub_id]
          }
        },
        wrapper_method_name: :build_refund_transaction,
        action_method_name: :refund_transaction,
        output_transformer: -> (res, api) {
          DataTypes::Merchant.create_refund_transaction_response(
            {
              refunded_id: res.RefundTransactionID,
              refunded_fee_total: to_money(res.FeeRefundAmount),
              refunded_net_total: to_money(res.NetRefundAmount),
              refunded_gross_total: to_money(res.GrossRefundAmount),
              refunded_total: to_money(res.TotalRefundedAmount),
              msg_sub_id: res.MsgSubID
            }
          )
        }
      ),

      get_transaction_details: PaypalAction.def_action(
        input_transformer: -> (req, _) {
          {
            TransactionID: req[:transaction_id]
          }
        },
        wrapper_method_name: :build_get_transaction_details,
        action_method_name: :get_transaction_details,
        output_transformer: -> (res, api) {
          payment_info = res.payment_transaction_details.payment_info
          DataTypes::Merchant.create_get_transaction_details_response(
            {
              transaction_id: payment_info.transaction_id,
              payment_status: payment_info.payment_status,
              pending_reason: payment_info.pending_reason,
              transaction_total: to_money(payment_info.gross_amount)
            }
          )
        }
      )
    }

  end
end